詳解陣列,連結串列和ADT
陣列
先由一個例子複習一下陣列的操作:
class HighArray
{
private long[] a;
private int nElems;
//-----------------------------------
public HighArray(int max) //建構函式
{
a=new long[max];
nElems = 0;
}
//-----------------------------------
public boolean find(long searchKey) //查詢元素
{
int j;
for(j=0;j<nElems;j++)
if(a[j]==searcheKey)
break; //退出for迴圈,記錄了j的值
if(j==nElems)
return false; //沒找到
else
return true; //找到了
}
//--------------------------------------
public void insert(long value ) //插入元素
{
a[nElems]=value;
nElems++;
}
//--------------------------------------
public boolean delete(long value) //刪除元素
{
int j;
for(j=0;j<nElems;j++)
if(value=a[j])
break;
if(j=nElems)
return false ; //不存在那個元素
else
{
for(int k=j;k<nElesm;k++)
a[k]=a[k+1]; //遍歷,從查詢的那個位置起,把陣列中的元素向前挪動一位
nElems--; //總長度減一
return true;
}
} //end delete()
//----------------------------------------
public void display()
{
for(int j=0;j<nElems;j++)
System.out.println(a[j]+" ");
System.out.rpintln("");
}
}
接著在客戶端始測試 陣列的 增 刪 查 操作
class HighArrayApp
{
public static void main(String[] args)
{
int maxSize=100;
HighArray arr;
arr= new HightArray(maxSize);
arr.insert(57);
arr.insert(27);
arr.insert(17);
arr.insert(67);
arr.insert(87);
arr.insert(33);
arr.insert(22);
arr.insert(11);
arr.insert(66);
arr.insert(66);
int searchKey=35;
if(arr.find(searchKey))
System.out.println("Found"+searchKey);
else
System.out.println("Can't found"+searchKey);
arr.delete(55);
arr.delete(66);
arr.display();
}
}
上例演示了一個數組增刪查的過程,它的特點是高度的抽象,操作“增刪改”都被封裝到 HightArray類中,並且暴露出介面(insert(),delete(),display())供使用者使用,這裡的使用者就是HightArrayApp,它只需呼叫方法完成操。
接著複習一下陣列的二分查詢
二分查詢適用於有序陣列,關於排序我在上一節博文有寫,那麼今天在這裡預設陣列有序的情況學習一下二分查詢把
class OrdArray
{
private long[] a;
private int nElems;
//-----------------------------------
public HighArray(int max)
{
a=new long[max];
nElems = 0;
}
//-----------------------------------
public int size()
{
return nElems
}
//-----------------------------------
public int find(long searchKey) //查詢元素
{
int lowerBound=0;
int upperBound=nElems-1;
int curIn;
while(true)
{
curIn=(lowerBound+upperBound)/2;
if(a[curIn]==searchKey)
return curIn;
else if(lowerBound>upperBound)
return nElems;
else
{
if(a[curIn]<searchKey)
lowerBound=curIn+1;
else
upperBound=curIn-1;
}
}
}
//--------------------------------------
public void insert(long value) //插入元素
{
int j;
for(j=0;j<nElmes;j++)
if(a[j]>value);
break;
for(int k=nElems;k>j;k--)
a[k]=a[k-1];
a[j]=value;
nElems++;
}
//--------------------------------------
public boolean delete(long value) //刪除元素
{
int j;
for(j=0;j<nElems;j++)
if(value=a[j])
break;
if(j=nElems)
return false; //不存在那個元素
else
{
for(int k=j;k<nElesm;k++)
a[k]=a[k+1]; //遍歷,從查詢的那個位置起,把陣列中的元素向前挪動一位
nElems--; //總長度減一
return true;
}
} //end delete()
//----------------------------------------
public void display()
{
for(int j=0;j<nElems;j++)
System.out.println(a[j]+" ");
System.out.rpintln("");
}
}
接著在客戶端測試 陣列的 增 刪 查 操作
class HighArrayApp
{
public static void main(String[] args)
{
int maxSize=100;
HighArray arr;
arr= new HightArray(maxSize);
arr.insert(57);
arr.insert(27);
arr.insert(17);
arr.insert(67);
arr.insert(87);
arr.insert(33);
arr.insert(22);
arr.insert(11);
arr.insert(66);
arr.insert(66);
int searchKey=35;
if(arr.find(searchKey)!=arr.size())
System.out.println("Found"+searchKey);
else
System.out.println("Can't found"+searchKey);
arr.display();
arr.delete(55);
arr.delete(66);
arr.display();
}
}
陣列的問題
- 在一個無序陣列中插入只需要O(1)的時間,查詢卻需要O(N)
- 在一個有序陣列中查詢只需要O(logN),但插入卻需要O(N)
- 不論是有序還是無序,刪除操作,都需要花費O(N)
- 一旦陣列被建立,大小就被固定住,陣列初始化的大小不容易控制
連結串列
既然陣列作為資料儲存結構有一定的缺陷,那麼接下來將介紹一種新的資料儲存結構:連結串列。
鏈結點
在連結串列中,每個資料項都obeisance包含在鏈結點Link中。一個鏈結點是某個類的物件,這個類就叫做Link,因為一個連結串列中有許多類似的鏈結點,所以需要使用不同於連結串列的類來表達鏈結點。
每個Link物件都包含對下一個鏈結點引用的欄位,通常叫做next。
但是連結串列物件包含有對第一個鏈結點的引用。
關係而非位置
陣列是根據下標號直接訪問,在連結串列中,尋找一個特定元素的唯一方法就是沿著這個元素鏈從頭開始一直向下尋找。從第一項開始,刀第二個,然後到第三個。
單鏈表
這個連結串列僅有的操作是:
- 在連結串列頭插入一個數據線
- 在連結串列頭刪除一個數據線
- 遍歷連結串列顯示它的內容
下面我們來看一下程式碼描述:
Link類 是鏈結點的抽象描述
class Link
{
public int iData;//假設Link中的資料是int和double,實際有可能是Object物件
public double dData;
public Link next; //這裡看上去很突兀,但實際是一個鏈結點的引用,不是"Link中包含了一個Link"
//------------------------------
public Link(int id,double dd)
{
iData=id;
dData=dd;
}
//-------------------------------
public void display()
{
System.out.print("{"+iData+","+dData+"}");
}
} //end class Link
LinkList是連結串列的抽象
class LinkList
{
private Link first; //頭結點的引用
//-------------------------------
public LinkList()
{
first=null;
}
//-------------------------------
public void insertFirst(int id,double dd)
{
//插入新結點的過程:
//first已經指向了連結串列的第一個結點,插入新的結點:
//1.建立的newLink結點的next等於first;
//2.然後改變first的值,使得first指向新建立的鏈結點
Link newLink =new Link(id,dd);
newLink.next=first;
first=newLink;
}
//-------------------------------
public Link deletefirst()
{
//通過把first重新指向第二個鏈結點,刪除和第一個鏈結點的連結,記住first是連結串列的屬性,next是鏈結點的屬性
Link temp=first;
first=first.next; //如何刪除:first-->old next
return temp; //返回刪除的鏈結點Link
}
//---------------------------------
public Lind find(int key) //查詢某個元素
{
Link current =first;
while(current.iData!=key)
{
if(current.next==null)
return null;
else
current =current.next;
}
return current;
}
//--------------------------------
public Link delete(int key) //刪除某個元素
{
Link current =frist;
Link previous=first;
while(current.iData!=key)
{
if(current.next==null)
return null;
else
{
previous=current;
current=current.next;
}
}
if(current==first)
first=first.next;
else
previous.next=current.next;
return current;
}
//---------------------------------
public void displayList() //輸出
{
System.out.print("List(first-->last): ");
Link current=first;
while(current!=null)
{
current.displayLink();
current=current.next;
}
System.out.println(" ");
}
} //end class LinkList
LinkListApp是測試連結串列和鏈結點的客戶端
class LinkListApp
{
public static void main(String[] args)
{
LinkList theList=new LinkList();//建立新的連結串列
theList.insertFirst(22,2.33);
theList.insertFirst(44,4.33);
theList.insertFirst(55,3.33);
theList.insertFirst(88,34.33);
theList.displayList();
Link f=theList.find(44);
if(f!=null)
System.out.println("Found Link with key "+f.iData);
else
Systemm.out.println("Can't find link");
Link d=theList.delete(88);
theList.displayList();
while(!theList.isEmpety())
{
Link aLink=theList.deleteFirst();
System.out.print("Deleted");
aLink.displayLink();
System.out.println(" ");
}
theList.displayList();
} //end main()
}
雙端連結串列
雙端連結串列與單鏈表的唯一區別是,增加了對最後一個鏈結點的引用。
這樣就允許在表尾插入一個鏈結點。
當然我們也可以遍歷整個連結串列直到表尾再插入。顯然雙端連結串列這樣在末尾插入鏈結點效率更高。
有序連結串列
有序連結串列優於有序陣列的地方就是插入的速度,因為連結串列的插入是不需要移動元素的,連結串列也可以擴充套件記憶體,陣列的記憶體是固定的。
有序連結串列的缺點就是實現起來稍微複雜。
雙向連結串列
雙向連結串列的優點 就是反向遍歷非常簡單。
為什麼?
因為雙向連結串列的每個鏈結點都有兩個屬性,分別指向前一個結點和指向後一個結點。
關於有序連結串列和雙向連結串列的程式碼敘述有興趣的可以自行查閱
連結串列的效率
在表頭插入和刪除速度很快,花費O(1);
查詢,刪除和在指定鏈結點後面插入都需要搜尋表中一半以上的鏈結點,需要O(N)次比較,雖然陣列執行這些操作演示O(N)次比較,但是連結串列仍然要快一些,因為插入刪除鏈結點時,連結串列不需要移動,增加的效率是顯著的,特別是複製實際遠遠大於比較實際的時候。
連結串列比陣列的優越性還體現在:連結串列需要多少記憶體就可以用多少記憶體,並且可以擴充套件。
陣列太大導致效率低下,陣列太小,使用的時候有可能空指標;向量是可擴充套件的陣列,它改變長度的方式是增量擴充套件,擴大一倍。記憶體效率上比連結串列低的多,
ADT 抽象資料型別
接下來討論一個比連結串列更廣泛的話題:抽象資料型別
簡單來說,是一種考慮資料結構的方式:著重於它做了什麼,而忽略它 是怎麼做的。
棧和佇列都是ADT的例子,陣列和連結串列都可以實現它們。
用連結串列實現棧
棧的push()和pop()操作實際是通過陣列操作完成的
arr[++top]=data;
data=arr[top- -];
而連結串列是類似於這樣完成:
theList.insertFist(data);
data=theList.deleteFirst();
棧的使用者呼叫push()和pop()方法來插入和刪除棧中的元素,它們不需要知道棧是用連結串列還是陣列實現的
class Link
{
public long dData;
public Link next;
//--------------------------
public Link(long dd)
{
dData=dd;
}
//--------------------------
public void displayLink(){
System.out.print(dData+" ");
}
} //end class Link
class LinkList{
private Link first; //頭結點的引用
//--------------------------------------
public LinkList()
{
first=null;
}
//--------------------------------------
public boolean isEmpty()
{
return fisrt==null;
}
//-------------------------------------
public void insertFirst(long dd)
{
Link newLink=new Link(dd);
newLink.next=first;
first=newLink;
}
//--------------------------------------
pubic long deleteFirst()
{
Link temp=first;
first=first.next;
return temp.dData;
}
//-------------------------------------
public voi displayList()
{
Link current =first;
while(current!=null)
{
current.displayLink();
current=current.next;
}
System.out.println("");
}
} //end class
class Stack
{
private LinkList theList;
//------------------------------
public Stack()
{
theList=new LinkList();
}
//------------------------------
public void push(long i)
{
theList.insertFirst(j);
}
//------------------------------
public void pop()
{
return theList.deleteFirst();
}
//------------------------------
public boolean isEmpty()
{
return theList.isEmpty();
}
//-------------------------------
public void displayStack()
{
System.out.print("Stack(top-->bottom):");
theList.displayList();
}
} //end class
class LinkStackApp
{
public static void main(String[] args)
{
LinkStack theStack=new LinkStack();
theStack.push(20);
theStack.push(40);
theStack.displayStack();
theStack.push(60);
theStack.push(80);
theStack.displayStack();
theStack.pop();
theStack.pop();
theStack.displayStack();
} //end main()
} //end class
觀察:整個程式,LinkStackApp中的main方法只和LinkStack類有關,LinkStack類只和LinkList類有關。main()和LinkList類是不能通訊的。
資料型別和抽象
抽象資料型別分為兩步,首先看看“資料型別”再考慮“抽象”
資料型別可以表示內建的型別,比如int,double,也可以用類來建立自己的資料型別。
抽象這個詞的意思是“不考慮細節的描述和實現”,抽象是事物的本質和重要特徵。
當“抽象資料型別”用於描述棧和佇列這樣的資料額結構時,它的意義被進一步擴充套件了。它意味著類的使用者不知道方法是怎樣運作的,也不知道資料是如何儲存的。
對於棧來說,使用者只知道push()和pop()方法的存在是被使用者使用的,但不需要知道內部的實現,以及內部資料是如何儲存。
介面,在ADT中有一個經常被稱為“介面”的規範,同城是類的公有方法,在棧中,push() 和pop()就形成了介面。