上一篇總結完了順序表,這一篇要總結的是線性表之中的鏈表。我將會從如下幾點進行總結:html
一、爲何要使用鏈表?
二、鏈表的存儲結構?
三、鏈表的經常使用操做代碼實現?java
一、爲何要使用鏈表
經過上一篇的學習,咱們知道順序表存在一些問題,主要有如下兩個方面。node
一、順序表的長度是固定的,若是超出分配的長度就會形成溢出,若是存放的數據太少就會形成空間浪費。
二、在插入元素和刪除元素時(尤爲插入和刪除的位置不在尾部時),會移動大量的元素,形成性能和效率低下。性能
基於以上問題,使用鏈表能夠很好地避免順序表中出現的問題。這也是咱們要使用鏈表的緣由。學習
二、鏈表的存儲結構
一、從上圖能夠看出,單鏈表中的每一個節點都包含一個「數據域」和一個「指針域」。「數據域」中包含當前節點的數據,「指針域」中包含下一節點的存儲地址。測試
二、頭指針 head 是指向開始節點的,結束節點沒有後繼節點,因此結束節點的指針域爲空,即 null。 this
三、鏈表在物理存儲結構上不連續,邏輯上連續;大小不固定。spa
四、根據鏈表的構造方式的不一樣能夠分爲:指針
- 單向鏈表
- 單向循環鏈表
- 雙向鏈表
- 雙向循環鏈表
2-一、單向鏈表
鏈表的每一個節點中只包含一個指針域,叫作單向鏈表(即構成鏈表的每一個節點只有一個指向後繼節點的指針)code
單向鏈表中每一個節點的結構:
2-二、單向循環鏈表
單向循環鏈表和上面講的單向鏈表有點類似,都是經過節點的指針指向它的下一個節點,而後這樣鏈接起來,但不一樣的地方是單向鏈表的最後一個節點的指針爲null,而單向循環鏈表的最後一個節點的指針指向的是頭節點,這樣構成了一個循環的節點的環。下面是單向循環鏈表的示意圖:
2-三、雙向鏈表
聽名字可能就能猜到雙向鏈表就是鏈表的節點包含兩個指針,一個指針是指向它的下一個節點,另外一個指針指向它的上一個節點。這樣雙向鏈表就能夠經過第一個節點找到最後一個節點,也能夠經過最後一個節點找到第一個節點,雙向鏈表的示意圖:
2-四、雙向循環鏈表
雙向循環鏈表相對於上面的雙向鏈表多了「循環」兩個字,咱們就能夠結合單向循環鏈表聯想到,其實雙向循環鏈表就是雙向鏈表夠成了一個環。雙向循環鏈表的示意圖:
三、鏈表的經常使用操做
鏈表經常使用的操做有:(以單向鏈表爲例)
3-一、插入節點
思路:須要循環查找此節點應該插入的位置。因此時間複雜度爲O(n)
示意圖:
3-二、刪除節點
思路:循環查找要刪除的節點的位置,因此時間複雜度爲O(n)
示意圖:
3-三、查找節點
思路:與插入節點和刪除節點的方法相似,須要遍歷鏈表中的節點,因此時間複雜度爲O(n)
3-四、獲取鏈表的長度
思路:不像順序表那樣連續存儲,獲取表的長度很是容易;在鏈表中,數據不是連續存儲的,所以須要循環遍歷才能求得鏈表的長度,因此時間複雜度爲O(n)
四、鏈表的經常使用操做的代碼實現
4-一、插入節點
在代碼裏面用到了一個變量 position,它的含義以下圖所示:
注意:
一、頭節點不存儲數據,它的數據域爲null,它的地址域存儲了第1個節點的地址
二、頭節點不算進鏈表的長度,position 從頭節點後面的節點開始算起
三、每一個節點裏面分別有數據域和地址域
下面是具體的實現代碼:
先建立一個節點類: Node.java
package com.demo; /** * 節點類 */ public class Node { Object element; // 數據域 Node next; // 地址域 // 節點的構造方法 public Node(Object element, Node next) { this.element = element; this.next = next; } // Gettet and Setter public Node getNext() { return this.next; } public void setNext(Node next) { this.next = next; } public Object getElement() { return this.element; } public void setElement(Object element) { this.element = element; } }
注意每一個節點裏面分別有數據域和地址域!
定義鏈表類:MyLinkedList.java
package com.demo; /** * 本身定義的一個鏈表類 */ public class MyLinkedList { // 頭節點 private Node headNode; // 用來遍歷鏈表的臨時節點 private Node tempNode; // 鏈表的初始化方法 public MyLinkedList() { // 初始化時,鏈表裏面只有1個節點,因此這個節點的地址域爲null Node node = new Node("王重陽", null); // 頭節點不存儲數據,它的數據域爲null,它的地址域存儲了第1個節點的地址 headNode = new Node(null, node); } /** * 一、插入節點:時間複雜度爲O(n) * @param newNode 須要插入的新節點 * @param position 這個變量的範圍能夠從0到鏈表的長度,注意不要越界。 * 頭節點不算進鏈表的長度, * 因此從頭節點後面的節點開始算起,position爲0 */ public void insert(Node newNode, int position) { // 經過position變量,讓tempNode節點從頭節點開始遍歷,移動到要插入位置的前一個位置 tempNode = headNode; int i = 0; while (i < position) { tempNode = tempNode.next; i++; } newNode.next = tempNode.next; tempNode.next = newNode; } // 遍歷鏈表,打印出全部節點的方法 public void showAll() { tempNode = headNode.next; while (tempNode.next != null) { System.out.println(tempNode.element); tempNode = tempNode.next; } System.out.println(tempNode.element); } }
測試類:TestMyLinkedList.java
package com.demo; public class TestMyLinkedList { public static void main(String[] args) { MyLinkedList myLinkedList = new MyLinkedList(); Node newNode1 = new Node("歐陽鋒", null); Node newNode2 = new Node("黃藥師", null); Node newNode3 = new Node("洪七公", null); myLinkedList.insert(newNode1, 0); myLinkedList.insert(newNode2, 0); myLinkedList.insert(newNode3, 0); myLinkedList.showAll(); } }
運行結果:
王重陽是初始化的節點,以後又往鏈表裏插入了3個節點。注意它們插入的位置和打印出來的順序!
4-二、刪除節點
在 MyLinkedList.java 中添加刪除節點的方法
/** * 二、刪除節點:時間複雜度爲O(n) * @param position */ public void delete(int position) { // 這裏一樣須要用tempNode從頭開始向後查找position tempNode = headNode; int i = 0; while (i < position) { tempNode = tempNode.next; i++; } tempNode.next = tempNode.next.next; }
測試代碼:TestMyLinkedList.java
package com.demo; public class TestMyLinkedList { public static void main(String[] args) { MyLinkedList myLinkedList = new MyLinkedList(); Node newNode1 = new Node("歐陽鋒", null); Node newNode2 = new Node("黃藥師", null); Node newNode3 = new Node("洪七公", null); myLinkedList.insert(newNode1, 0); myLinkedList.insert(newNode2, 0); myLinkedList.insert(newNode3, 0); myLinkedList.showAll(); myLinkedList.delete(0); System.out.println("------刪除以後------"); myLinkedList.showAll(); } }
運行結果:
4-三、查找節點
在 MyLinkedList.java 中添加查找節點的方法
/** * 三、查找節點:時間複雜度爲O(n) * @param position * @return 返回查找的節點 */ public Node find(int position) { // 這裏一樣須要用tempNode從頭開始向後查找position tempNode = headNode; int i = 0; while (i < position) { tempNode = tempNode.next; i++; } return tempNode.next; }
測試代碼:TestMyLinkedList.java
package com.demo; public class TestMyLinkedList { public static void main(String[] args) { MyLinkedList myLinkedList = new MyLinkedList(); Node newNode1 = new Node("歐陽鋒", null); Node newNode2 = new Node("黃藥師", null); Node newNode3 = new Node("洪七公", null); myLinkedList.insert(newNode1, 0); myLinkedList.insert(newNode2, 0); myLinkedList.insert(newNode3, 0); myLinkedList.showAll(); System.out.println("----查找position爲2的節點----"); Node result = myLinkedList.find(2); System.out.println(result.element); } }
運行結果:
4-四、獲取鏈表的長度
在 MyLinkedList.java 中添加方法
/** * 四、獲取鏈表的長度:時間複雜度爲O(n) * @return */ public int size() { tempNode = headNode.next; int size = 0; while (tempNode.next != null) { size = size + 1; tempNode = tempNode.next; } size = size + 1; // tempNode的地址域爲null時,size記得加上最後一個節點 return size; }
測試代碼:TestMyLinkedList.java
package com.demo; public class TestMyLinkedList { public static void main(String[] args) { MyLinkedList myLinkedList = new MyLinkedList(); Node newNode1 = new Node("歐陽鋒", null); Node newNode2 = new Node("黃藥師", null); Node newNode3 = new Node("洪七公", null); myLinkedList.insert(newNode1, 0); myLinkedList.insert(newNode2, 0); myLinkedList.insert(newNode3, 0); myLinkedList.showAll(); System.out.println("----------"); System.out.println("鏈表的長度爲:" + myLinkedList.size()); } }
運行結果:
五、總結
一、鏈表插入和刪除一個元素的時間複雜度均爲O(n),另外,鏈表讀取一個數據元素的時間複雜度也爲O(n)
二、順序表和單鏈表的比較:
①順序表:
優勢:主要優勢是讀取元素的速度較快,以及內存空間利用效率高;
缺點:主要缺點是須要預先給出順序表的最大元素個數,而這一般很難準確作到。當實際的元素個數超過了預先給出的個數,會發生異常。另外,順序表插入和刪除操做時須要移動較多的數據元素。
②單鏈表:
優勢:主要優勢是不須要預先給出最大元素個數。另外,單鏈表插入和刪除操做時不須要移動數據元素;
缺點:主要缺點是每一個節點都須要分爲地址域和數據域,所以單鏈表的空間利用率略低於順序表。另外,單鏈表讀取一個元素的時間複雜度爲O(n) ;而順序表讀取一個元素的時間複雜度爲O(1)
歡迎轉載,但請保留文章原始出處