03--圖解資料結構之雙鏈表實現集合
零、前言
連結串列是一種資料結構,用來承載資料,每個表節點裝載一個數據元素 雙鏈表是每個節點出來資料元素外還分別持有前、後兩個節點的引用 為了統一節點的操作,一般在真實連結串列的首尾各加一個虛擬節點,稱為頭節點和尾節點
一、連結串列的操作
下圖是一個三個節點的雙鏈表

雙鏈表.png
/** * 作者:張風捷特烈 * 時間:2018/9/18 0018:7:35 * 郵箱:[email protected] * 說明:雙鏈表 */ public class DoubleLink<T> { /** * 虛擬頭結點 */ private Node headNode; /** * 虛擬尾節點 */ private Node tailNode; /** * 連結串列長度(節點數) */ private int size; /** * 節點類 * @param <T> */ private static class Node<T> { /** * 資料 */ public T data; /** * 前節點 */ public Node prev; /** * 後節點 */ public Node next; public Node(Node prev, Node next, T data) { this.data = data; this.prev = prev; this.next = next; } } }
1.插入操作:addBefore()
假如在第二個元素處插入,會發生什麼:
1---新建一個node,將前、後指向分別指向目標前節點和目標節點
2---目標前節點next指向新節點
3---目標節點prev指向新節點
3---連結串列長度+1

雙鏈表前插入.png
/** * 根據目標節點插入新節點 * @param target 目標節點 * @param data 新節點資料 */ private void addNodeBefore(Node<T> target, T data) { //新建一個node,將前、後指向分別指向目標前節點和目標節點 Node<T> newNode = new Node<>(target.prev, target, data); //目標前節點next指向新節點 target.prev.next = newNode; //目標節點prev指向新節點 target.prev = newNode; //連結串列長度+1 size++; }
2.移除操作:removeNode()
假如在刪除第二個元素,會發生什麼:
1---目標前節點的next指向目標節點後節點
2---目標後節點的prev指向目標節點前節點
3---連結串列長度-1
4---返回刪除的資料

雙鏈表移除節點.png
/** * 移除目標節點 * * @param target 目標節點 * @return 目標節點資料 */ private T removeNode(Node<T> target) { //目標前節點的next指向目標節點後節點 target.prev.next = target.next; //目標後節點的prev指向目標節點前節點 target.next.prev = target.prev; //連結串列長度-1 size--; return target.data; }
3.清空操作:clearNode()
思路和刪除一樣:首尾虛擬節點互指,中間的元素就被孤立了,從而從連結串列上全部刪除
1---例項化頭結點
2---例項化尾節點,並將prev指向頭
3---頭結點的next指向尾節點
4---連結串列長度置零

雙鏈表清空.png
/** * 清空所有節點 */ private void clearNode() { //例項化頭結點 headNode = new Node<T>(null, null, null); //例項化尾節點,並將prev指向頭 tailNode = new Node<T>(headNode, null, null); headNode.next = tailNode; //連結串列長度置零 size = 0; }
4.獲取操作:getNode
思路:連結串列查詢只能一個一個挨著找,就像排隊報數樣。
為了儘量高效,判斷一下索引在前半還是後半,來採取前報數,還是後報數。
/** * 根據索引獲取節點 * * @param index 索引 * @return 索引處節點 */ private Node<T> getNode(int index) { //宣告目標節點 Node<T> targetNode; //索引越界處理 if (index < 0 || index > size - 1) { throw new IndexOutOfBoundsException(); } //如果索引在前半,前序查詢 if (index < size / 2) { targetNode = headNode.next; for (int i = 0; i < index; i++) { targetNode = targetNode.next; } } else {//如果索引在後半,反序查詢 targetNode = tailNode.prev; for (int i = size - 1; i < index; i++) { targetNode = targetNode.prev; } } return targetNode; }
二、利用連結串列實現對資料的操作
連結串列只是對節點的操作,只是一種結構,並非真正目的,在集合類中要讓連結串列對外完全不可見,就像人的骨骼之於軀體
軀體的任何動作是骨骼以支撐,而骨骼並不可見,從外來看只是軀體的動作而已,資料結構就是這樣的骨架,資料便是軀體。
我們需要的是按照這種結構對資料進行增刪改查等操作,並暴露介面由外方呼叫
1.普通集合操作:增刪改查
public class DoubleLinkedGroup<T> extends Group<T> { /** * 虛擬頭結點 */ private Node headNode; /** * 虛擬尾節點 */ private Node tailNode; public DoubleLinkedGroup() { clear(); } @Override public void add(int index, T el) { if (index < 0 || index > size) { throw new IllegalArgumentException("Add failed. Illegal index"); } addNodeBefore(getNode(index), el); } @Override public T remove(int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("Remove failed. Illegal index"); } return removeNode(getNode(index)); } @Override public void clear() { clearNode(); } @Override public T set(int index, T el) { if (index < 0 || index > size) { throw new IllegalArgumentException("Set failed. Illegal index"); } Node<T> node = getNode(index); T oldData = node.el; node.el = el; return oldData; } @Override public T get(int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("Get failed. Illegal index"); } return getNode(index).el; } @Override public void addLast(T el) { add(size, el); } }
2.普通方法測試:
private static void baseTest() { DoubleLinkedGroup<String> list = new DoubleLinkedGroup<>(); //新增測試 list.addFirst("特"); list.addFirst("張"); list.add(1,"風"); list.add(2,"捷"); list.addLast("烈"); //輸出測試 System.out.println(list); //head: 張->風->捷->特->烈->null-> //移除測試 list.remove(3); System.out.println(list); //head: 張->風->捷->烈->null-> //修改測試 list.set(2,"神"); System.out.println(list); //head: 張->風->神->烈->null-> //獲取測試 for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i));//張風神烈 } //大小測試 System.out.println(list.size());//4 //是否為空測試 System.out.println(list.isEmpty());//false //清空測試 list.clear(); System.out.println(list.isEmpty());//true } }
二、其他方法測試
定元素查詢索引、刪除
兩個雙鏈表式集合定點連線
1.程式碼實現
@Override public int[] getIndex(T el) { //臨時陣列 int[] tempArray = new int[size]; //重複個數 int index = 0; int count = 0; Node node = headNode.next; while (node != null) { if (el.equals(node.el)) { tempArray[index] = -1; count++; } index++; node = node.next; } //將臨時陣列壓縮 int[] indexArray = new int[count]; int indexCount = 0; for (int i = 0; i < tempArray.length; i++) { if (tempArray[i] == -1) { indexArray[indexCount] = i; indexCount++; } } return indexArray; } @Override public Group<T> contact(int index, Group<T> group) { if (index < 0 || index > size) { throw new IllegalArgumentException("Contact failed. Illegal index"); } DoubleLinkedGroup linkedGroup = (DoubleLinkedGroup) group; Node targetNode = getNode(index); Node targetNextNode = targetNode.next; //目標節點的next指向待接連結串列的第一個節點 targetNode.next = linkedGroup.getHeadNode().next; //向待接連結串列的第一個節點的prev指向目標節點 linkedGroup.getHeadNode().next.prev = targetNode; //目標節點的下一節點指的prev向待接連結串列的最後一個節點 targetNextNode.prev = linkedGroup.getTailNode().prev; //向待接連結串列的最後一個節點的next指向目標節點的下一節點的 linkedGroup.getTailNode().prev.next = targetNextNode; return this; } public Node getHeadNode() { return headNode; } public Node getTailNode() { return tailNode; }
2.其他方法測試
/** * 其他方法測試 */ private static void otherTest() { DoubleLinkedGroup<String> linkedGroup = new DoubleLinkedGroup<>(); linkedGroup.addLast("a"); linkedGroup.addLast("b"); linkedGroup.addLast("a"); linkedGroup.addLast("c"); linkedGroup.addLast("a"); System.out.println(linkedGroup); //head: a->b->a->c->a->null-> //獲取a元素的所有索引位置 int[] as = linkedGroup.getIndex("a"); for (int a : as) { System.out.print(a + " ");//0 2 4 } //刪除a元素第一次出現的地方--- linkedGroup.removeEl("a"); System.out.println(linkedGroup); //head: b->a->c->a->null-> //檢視a元素是否存在 boolean b = linkedGroup.contains("a"); System.out.println(b);//true //刪除所有a元素出現的地方--- linkedGroup.removeEls("a"); System.out.println(linkedGroup); //head: b->c->NULL //雙鏈表合併測試 DoubleLinkedGroup<String> linkedGroup2 = new DoubleLinkedGroup<>(); linkedGroup2.addLast("1"); linkedGroup2.addLast("3"); linkedGroup2.addLast("2"); linkedGroup.contact(0, linkedGroup2); System.out.println(linkedGroup); //head: b->1->3->2->c->null-> }
後記、
1.宣告:
[1]本文由張風捷特烈原創,各圖均由本人親自所畫,轉載請註明
[2]歡迎廣大程式設計愛好者共同交流
[3]個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
[4]你的喜歡與支援將是我最大的動力
2.連線傳送門:
ofollow,noindex">更多資料結構知識歡迎訪問:圖解資料結構 專案原始碼均在我的github/DS:歡迎star 張風捷特烈個人網站,程式設計筆記請訪問: http://www.toly1994.com
3.聯絡我
QQ:1981462002
微信:zdl1994328
4.歡迎關注我的微信公眾號,最新精彩文章,及時送達:

公眾號.jpg