簡介
一種邏輯結構,相同數據類型的n個數據元素的有限序列,除第一個元素外,每個元素有且僅有一個直接前驅,除最後一個元素外,每個元素有且僅有一個直接後繼。
線性表的特點:
(1)元素個數有限 (2)邏輯上元素有先後次序
(3)數據類型相同 (4)僅討論元素間的邏輯關系
注:線性表是邏輯結構,順序表和鏈表是存儲結構。
1.順序存儲
順序表,使用數組實現,一組地址連續的存儲單元,數組大小有兩種方式指定,一是靜態分配,二是動態擴展。
注:線性表從1開始,而數組從0開始。
優點:隨機訪問特性,查找O(1)時間,存儲密度高;邏輯上相鄰的元素,物理上也相鄰;
缺點:插入刪除需移動大量元素。
順序表相關的操作跟數組有關,一般都是移動數組元素。
這裡說一下插入和刪除時的邊界條件,首先線性表從1開始,數組從0開始,單純的文件說明不夠直接,來看圖說話吧。
插入時:對於線性表來說最小能插入的位置是1,最大能插入的位置是8(=7+1),所以 1<= index <=(7+1);移動數組元素時要注意,for (int i = count; i >= index; i--) { items[i] = items[i-1];}
刪除時:只能在藍色方塊之間尋找節點刪除,即1 <= index <= 7。移動元素,for (i = index; i < count; i++) { items[i-1] = items[i];}
2.鏈式存儲
鏈表的定義是遞歸的,它或者為空null,或者指向另一個節點node的引用,這個節點含有下一個節點或鏈表的引用。
與順序存儲相比,允許存儲空間不連續,插入刪除時不需要移動大量的元素,只需修改指針即可,但查找某個元素,只能從頭遍歷整個鏈表。
Java中使用嵌套類來定義節點的抽象數據類型:
private class Node{ // 鏈表節點的嵌套類 T item; // 節點內容 Node next; // 後繼節點 }
2.1 單鏈表
使用任意存儲單元來存儲線性表中的數據元素,節點類型如上。
單鏈表分為帶頭結點和不帶頭結點兩種,不管有沒有頭結點,頭指針都指向鏈表的第一個節點(有頭結點指向頭結點)。
頭結點:數值域可不設任何信息,頭結點的指針域指向鏈表的第一個元素。
帶頭節點的好處有:
(1)鏈表第一位置節點上的操作和其它位置上的操作一致
(2)無論鏈表是否為空,頭指針都指向頭結點(非空),空表和非空表處理一樣
(這裡我沒有使用頭結點)
注:鏈表麻煩的地方是插入和刪除時指針的修改,保證不斷鏈,一般先斷後鏈。
基本操作
1. 頭插法
將新節點插入到當前鏈表的表頭,(頭結點之後),插入的順序與鏈表中的順序相反,關鍵點就是記住舊的表頭,生成一個新的放到舊表頭前面,如圖:
核心代碼: public void headInsert(T item) { Node old = first; first = new Node(); first.item = item; first.next = old; count++; }
2. 尾插法
增加一個尾指針,新節點插到鏈表的尾部,插入的順序和鏈表的順序一致,如圖:
核心代碼: public void tailInsert(T item) { Node old = last; last = new Node(); last.item = item; last.next = null; if (isEmpty()) { first = last; } else { old.next = last; } count++; }
節點的插入和刪除,要點是先斷後連,關鍵就是不要斷鏈了,以插入為例(把s插入p和q之間),先斷意思是先把p->q斷了,變成s->q,後連,最後再把p和s連接起來。
3. 插入節點
待插入節點為s,一般采用後插法,即先找到插入位置節點的前驅節點,然後插入,時間復雜度O(n)。
核心代碼為: p=getNodeByIndex(i-1); s.next = p.next; p.next = s;
還有一種方法是,直接插入到位置的後面(前插法),然後交換兩個節點的值,插入的節點到了指定位置,時間復雜度O(1):
核心代碼: s.next = p.next; p.next = s; temp = p.item; // 交換內容 p.item = s.item; s.item = temp;
4. 刪除節點
待刪除節點為q,也是先找到前驅節點,修改指針域即可,時間復雜度O(n)。
核心代碼: P = getNodeByIndex(i-1); q = p.next; p.next = q.next; q = null;
刪除節點也能直接刪除其後繼節點,然後將後繼節點的內容賦給自己即可,時間復雜度為O(1):
核心代碼: q = p.next; p.item = p.next.item; p.next = q.next; q = null;
2.2 雙鏈表
單鏈表節點的缺點是只有一個後繼節點,訪問前驅節點只能從頭遍歷(如插入、刪除),時間復雜度為O(n)。雙鏈表,即添加一個指向前驅的節點,節點類型如下:
private class Node{ // 鏈表節點的嵌套類 T item; // 節點內容 Node prior, next; // 前驅節點和後繼節點 }
雙鏈表的查找和單鏈表的相同再次不在贅述,雙鏈表的構造也分為頭插和尾插,與單鏈表唯一不同的是修改前驅指針prior,具體見源碼。插入和刪除時不同,因為需要修改兩個指針,如果給定要操作的節點,插入和刪除的時間復雜度為O(1)。
注:插入刪除操作同樣也是先斷後連。
1. 插入節點
在p節點後插入s節點,先斷後連,先把p和原後繼節點的鏈條給斷了,使後繼節點只跟s節點有關:
①s.next = p.next; // 先斷了p的後繼 ②p.next.prior = s; // 在斷了p後繼的前驅 ③s.prior = p; // 讓s的前驅指向p ④p.next = s; // p的後繼指向s,重新連接上鏈條,此步必須在①②之後
2. 刪除節點
刪除節點p的後繼節點q,也是先斷後連,把q和其後繼節點的關系,轉讓給p即可:
①p.next = q.next; // 先斷了q的後繼 ②q.next.prior = p; // 在斷了q後繼的前驅 刪除節點q的前驅節點p,把p和去前驅節點的關系轉讓給q即可: ①q = p.prior.next; // 把p前驅節點的後繼改成q ②q.prior = p.prior; // 把q的前驅節點改成p的前驅節點
2.3 循環鏈表
1. 循環單鏈表
與單鏈表的區別在於,表中最後一個節點的指針不為null,而改為指向頭結點(第一個節點),從而整個鏈表形成一個環。判斷循環單鏈表是否為空,判斷是否等於頭指針。
只有一個尾指針的循環單例表,可以很方便的操作表頭和表尾,因為尾指針的後繼就是頭指針O(1) 。
2. 循環雙鏈表
與雙鏈表的區別在於,頭結點的prior指針指向尾節點,尾節點的next指針指向頭結點。
2.4 靜態鏈表
靜態鏈表是借助數組來描述線性表的鏈式存儲結構,節點也有數據域和指針域,這裡的指針是節點的相對地址(數組下標),也需要預先分配一塊連續的內存空間。
特點,插入刪除和動態鏈表一樣,以next==-1為結束標志。
2.5 順序表和鏈表的比較
1. 順序表可以順序存取,也支持隨機存取;鏈表只能順序存取。
2. 順序表邏輯上相鄰的物理上也相鄰;而鏈表不一定,它是用指針來描述元素之間的關系。
3. 順序表插入和刪除要移動大量元素;鏈表只需修改指針即可