前面兩節內容咱們詳細介紹了ArrayList,一是手寫實現ArrayList數據結構,而是經過分析ArrayList源碼看看內置實現,關於集合內容一如既往,本節課咱們繼續學習集合LinkedList,咱們首先入門LinkedList數據結構,而後再去看看LinkedList源碼是如何實現的,咱們開始吧。數據結構
LinkedList內置是經過雙鏈表數據結構來存儲數據,和ArrayList不一樣的是,ArrayList屬於真正意義物理意義上的線性結構,而LinkedList也屬於線性鏈表,只不過須要經過咱們手動來關聯先後節點數據,同時呢,雙鏈表和單鏈表只是在結構上有所不一樣而已,只是雙鏈表多了一個前驅節點,其餘無差別,那麼到底何爲雙鏈表呢?在咱們平常生活中處處都是這樣的例子,好比咱們音樂播放器應該算比較形象了,以下:app
接下來咱們來實現單鏈表,而後對單鏈表進行改形成雙鏈表,咱們看到如上播放器,單鏈表只是少了前驅節點,可是有後繼節點(如上寫錯了),因此咱們須要定義一個節點,而後在此節點上有鏈接下一節點的引用(在C或C++中爲指針),和當前節點所存儲的數據,因此咱們定義以下泛型節點類:ide
public class Node<T> { //當前節點值 public T data; //後繼節點 public Node next; public Node(T data) { this.data = data; } }
接下來則是定義鏈表來操做上述節點類並存儲數據了, 這裏咱們稍微作的簡單點,在鏈表中會存在頭節點和尾節點,這裏呢咱們經過來頭節點來操做,等咱們升級到雙鏈表時再來定義尾節點,因此在單鏈表中有頭節點和鏈表長度兩個變量,以下:性能
public class MyLinkedList<T> { //頭節點 private Node head; //鏈表元素長度 private int length; }
舒適提示:這裏我就不給你們畫圖演示了,自行腦補,實在感受繞的話本身在畫板或紙上畫一下就明白了,我也是在紙上畫了一番才動手寫代碼的。首先咱們須要考慮頭節點和尾節點即播放器中第一首歌和最後一首歌,而後針對指定位置添加歌曲經過next串聯就造成了歌曲列表,更爲形象的例子當屬咱們吃過的串串了。那麼接下來咱們完成往播放器列表中添加第一個首歌,此時咱們應該想,頭節點是否添加了第一首歌,若不存在則直接實例化頭節點便可,若已存在第一首歌,咱們則將從新實例化一首歌,而後將其已添加的第一首歌的引用賦值給新添加的歌曲的next,因此就有了以下方法:學習
//添加至頭結點 public void addToHead(T data) { if (head == null) { head = new Node(data); } else { Node temp = head; head = new Node(data); head.next = temp; } length++; }
好了,將新添加的歌曲放在第一首咱們已經徹底搞定了,而後咱們再來往歌曲列表中最後添加一首歌曲,這個時候咱們腫麼知道是最後一首呢,只要next爲空,說明就是最後一首歌曲,這就是判斷依據,這點就不用我再過多解釋了,那麼就有了以下方法:測試
//添加至尾節點 public void addToTail(T data) { Node temp = head; while (temp.next != null) { temp = temp.next; } temp.next = new Node(data); length++; }
單鏈表的肯定就在這裏,咱們只能循環遍歷才能找到最後一首,而後添加對應歌曲,因此當數據量足夠大時,可想其性能。接下來則是最重要的一塊了,咱們想要在指定歌曲下添加歌曲,這個時候就涉及到找到對應歌曲索引而後添加數據,ui
//添加到指定索引元素 public void add(int index, T data) { if (index < 0) { throw new RuntimeException("非法索引"); } if (index > length) { throw new RuntimeException("超出索引邊界"); } if (head == null || index == 0) { addToHead(data); return; } //頭節點 Node temp = head; //指定索引下一節點 Node holder; for (int i = 0; i < index - 1 && temp.next != null; i++) { temp = temp.next; } //未插入節點時指定索引下一節點 holder = temp.next; //指定索引節點下一節點即待插入的節點 temp.next = new Node(data); //將列表中指定索引節點下一節點引用指向指定待插入節點(此時指定索引下節點即爲待插入節點,而後再下一節點即爲待插入節點) temp.next.next = holder; length++; }
接下來則是根據指定索引查找元素,我就不解釋了,直接上代碼,以下this
//根據索引查找元素 public T find(int index) { if (index < 0) { throw new RuntimeException("非法索引"); } if (length == 0 || index > length) { throw new RuntimeException("超出索引邊界"); } Node temp = head; for (int i = 0; i < index; i++) { temp = temp.next; } return (T) temp.data; }
最後老規矩重寫toString方法,打印鏈表數據,以下:spa
//鏈表元素大小 public int size() { return length; } @Override public String toString() { StringBuilder sb = new StringBuilder(); Node temp = head; while (temp != null) { sb.append(temp.data); sb.append(","); temp = temp.next; } if (sb.charAt(sb.length() - 1) == ',') { sb.delete(sb.length() - 1, sb.length()); } return sb.toString(); }
最後咱們來往播放器列表中添加歌曲作個測試吧,走你,以下:3d
public class Main { public static void main(String[] args) { MyLinkedList<Integer> list = new MyLinkedList<>(); //添加元素11到頭節點 list.addToHead(11); System.out.println(list); //添加元素15到尾節點 list.addToTail(15); System.out.println(list); //添加元素12到頭節點 list.addToHead(12); System.out.println(list); //添加元素13到頭節點 list.addToHead(13); System.out.println(list); //添加元素8到尾節點 list.addToTail(8); //添加元素7到尾節點 list.addToTail(7); list.add(2, 9); System.out.println(list); //在索引2位置添加元素9 list.add(2, 9); System.out.println(list); //刪除索引爲4的元素 list.delete(4); System.out.println(list); } }
有了如上單鏈表的鋪墊,接下來咱們再來實現雙鏈表則是垂手可得了,只不過添加了前驅節點和鏈表中的尾結點而已,走你,咱們往節點類中添加前驅節點,以下:
public class Node<T> { //當前節點值 public T data; //前驅節點 public Node previous; //後繼節點 public Node next; public Node(T data) { this.data = data; } }
同理,咱們在鏈表類中添加尾節點字段,以下:
public class MyLinkedList<T> { //頭節點 private Node head; //尾節點 private Node tail; //鏈表元素長度 private int length; }
一樣,當添加歌曲至首位時,此時咱們也需初始化頭節點,只不過這時多了個尾節點,不要緊,這個時候頭節點就是尾節點,咱們封裝一個初始化頭節點和尾節點的方法,以下:
//初始化頭接點和尾節點 void initHead(T data) { //初始化頭節點 head = new Node(data); //此時尾節點即頭節點 tail = head; }
而後添加歌曲至頭節點時,只不過多了個前驅節點,也就相應多了一行代碼而已,就是將已添加首位歌曲的前驅節點賦給待添加的首位歌曲,以下:
//添加元素至頭結點 public void addToHead(T data) { if (head == null) { initHead(data); } else { Node temp = head; head = new Node(data); head.next = temp; temp.previous = head; } length++; }
而添加歌曲至末位時就和上述單鏈表就有些不一樣了,單鏈表中是直接循環遍歷,這裏咱們定義了尾節點,因此直接操做尾節點便可,以下:
//添加至尾節點 public void addToTail(T data) { if (size() == 0) { initHead(data); } else { Node temp = tail; tail = new Node(data); temp.next = tail; tail.previous = temp; } length++; }
接下來又是添加指定索引元素的核心方法了,其實也很是簡單,我都將註釋給你寫好了,仍是看不懂,建議到紙上畫畫哈。
//添加指定索引元素 public void add(int index, T data) { if (index < 0) { throw new RuntimeException("非法索引"); } if (index > length) { throw new RuntimeException("超出索引邊界"); } if (head == null || index == 0) { initHead(data); return; } //頭節點 Node temp = head; //定義獲取指定索引節點下一節點 Node holder; for (int i = 0; i < index - 1 && temp.next != null; i++) { temp = temp.next; } //當前節點的下一節點 holder = temp.next; //要添加的下一節點 temp.next = new Node(data); //插入節點的後繼節點爲當前節點下一節點 temp.next.next = holder; //當前節點下一前驅節點爲插入節點 temp.next.next.previous = temp.next; length++; }
不管是添加仍是刪除最重要的是咱們須要想清楚,添加時和刪除後前驅節點和後繼節點分別指向誰,把這個問題想明白了,那也就沒什麼了,走你,刪除方法:
//刪除指定索引元素 public void delete(int index) { if (index < 0) { throw new RuntimeException("非法索引"); } if (length == 0 || index > length) { throw new RuntimeException("超出索引邊界"); } Node temp = head; for (int i = 0; i < index - 1 && temp.next != null; i++) { temp = temp.next; } temp.next.next.previous = temp; temp.next = temp.next.next; length--; }
爲了驗證咱們所寫代碼,咱們打印出對應節點的前驅和後繼節點,以下:
public int size() { return length; } @Override public String toString() { StringBuilder sb = new StringBuilder(); Node temp = head; while (temp != null) { sb.append(temp.data); sb.append(","); if (temp.previous != null && temp.next != null) { System.out.println(temp.previous.data + "<-(" + temp.data + ")->" + temp.next.data); } temp = temp.next; } if (sb.charAt(sb.length() - 1) == ',') { sb.delete(sb.length() - 1, sb.length()); } return sb.toString(); }
控制檯測試數據和單鏈表中同樣,結果數據以下(固然咱們能夠分開打印對應節點前驅和後繼節點去驗證也是闊以的,這裏我也驗證過來,麼有任何問題):
本節咱們經過手寫代碼實現了單鏈表和雙鏈表,仍是很是簡單,下一節咱們詳細分析LinkedList源碼,感謝您的閱讀,咱們下節見