Algorithms_基礎數據結構(02)_鏈表&鏈表的應用案例之單向鏈表中梳理了 單向鏈表的基本操做,接下來咱們繼續來看下雙向鏈表吧。java
單向鏈表只有一個方向,結點只有一個後繼指針next指向後面的結點。node
雙向鏈表,顧名思義,它支持兩個方向,每一個結點不止有一個後繼指針next指向後面的結點,還有一個前驅指針prev指向前面的結點。web
雙向鏈表須要額外的兩個空間來存儲後繼結點和前驅結點的地址。因此,若是存儲一樣多的數據,雙向鏈表要比單鏈表佔用更多的內存空間。sql
雖然兩個指針比較浪費存儲空間,但能夠支持雙向遍歷,這樣也帶來了雙向鏈表操做的靈活性。那相比單鏈表,雙向鏈表適合解決哪一種問題呢?數組
-----> B+Tree:Mysql索引 葉子節點 雙向鏈表緩存
一般,雙向鏈表同單鏈表同樣,都僅有一個頭指針。因此雙鏈表查找指定元素的實現同單鏈表相似,都是從表頭依次遍歷表中元素,直到找到對應的元素爲止。數據結構
更改雙鏈表中指定結點數據域的操做那必需要先查找到該節點,所以是在查詢的基礎上完成的。------>即:經過遍歷找到存儲有該數據元素的結點,直接更改其數據域便可。svg
/** * @author 小工匠 * @version v1.0 * @create 2020-01-03 06:08 * @motto show me the code ,change the word * @blog https://artisan.blog.csdn.net/ * @description **/ public class ArtisanDoubleLinkedList { private ArtisanNode head; // head節點 private ArtisanNode tail; // tail節點 爲了方便直接獲取tail節點,省去每一次都要遍歷的操做 private int size; // 鏈表元素數量 /** * 雙向鏈表初始化 */ public ArtisanDoubleLinkedList() { this.head = null; this.tail = null; } /** * 頭插 * @param data */ public void add2Head(Object data) { ArtisanNode node = new ArtisanNode(data); // 新的Node if (this.head == null) { // 若是head節點爲null, head和tail節點均爲這個新的node節點 this.tail = node; } else {// 將原來的頭節點的前驅節點指向node, 將新節點的後驅節點指向head this.head.pre = node; node.next = head; } this.head = node; // 將新的節點置爲head節點 size++; } /** * 尾插 (低效) * * @param data */ public void add2Tail(Object data) {// 從頭部遍歷,找到最後的節點,而後加到尾部 ArtisanNode node = new ArtisanNode(data); // 要加入的節點 ArtisanNode currentNode = head; if (currentNode == null){ add2Head(data); } while(currentNode !=null){ if (currentNode.next == null){ // 說明找到了當前的tail節點 currentNode.next = node ;// 將當前tail節點的next指針指向新的tail節點 node.pre = currentNode; //新的tail節點的pre指向當前tail節點節點 this.tail = node; break; }else{ currentNode = currentNode.next; } } size++; } /** * 尾插 (利用tail 無需遍歷 效率更高) * * @param data */ public void add2Tail2(Object data) {// 已經設置tail了,直接用便可,效率更高 ArtisanNode node = new ArtisanNode(data); // 要加入的節點 if (this.head == null){ add2Head(data); }else { tail.next = node; node.pre = tail; tail = node; } } /** * * @param postition * @param data */ public void add2Nth(int postition ,Object data) { ArtisanNode newNode = new ArtisanNode(data); // 新的Node ArtisanNode currentNode = head; if (postition == 0 ){ // 若是是0 ,添加到頭節點 add2Head(data); }else { for (int i = 1; i < postition; i++) { // 找到要插入的位置的前面的節點 currentNode = currentNode.next; } // 與後繼節點創建雙層邏輯關係 newNode.next = currentNode.next; currentNode.next.pre = newNode; // 與前置節點創建雙層邏輯關係 currentNode.next = newNode; newNode.pre = currentNode; } size++; } /** * 根據value 查找元素 * @param data * @return */ public ArtisanNode find(Object data){ // 從頭遍歷 ArtisanNode currentNode = head; while(currentNode != null){ if (data.equals(currentNode.data)){ printPreAndNextInfo(currentNode); break; }else{ currentNode = currentNode.next; } } return currentNode; } /** * 刪除頭部節點 */ public void deleteHead(){ this.head = this.head.next; // 將當前頭節點的下一個節點置爲頭節點 this.head.pre = null; // 將前置節點置爲null size--; } /** * 刪除尾部節點 */ public void deleteTail(){ ArtisanNode currentNode = this.head; ArtisanNode previousNode = null; while (currentNode != null){ if (currentNode.next == null){ currentNode.pre = null;// 最後一個節點的pre置爲置爲null previousNode.next = null;// 前置節點的next指針置爲null this.tail = previousNode; // 將當前節點的前一個節點置爲tail節點 }else { // 若是當前節點的next指針指向不爲空,則把下個節點置爲當前節點,繼續遍歷 previousNode = currentNode;// 保存上一個節點的信息 currentNode = currentNode.next; } } } /** * 刪除指定位置的節點 * @param position */ public ArtisanNode deleteNth(int position){ ArtisanNode currentNode = this.head; if (position == 0 ){ deleteHead(); }else { for (int i = 1 ; i < position ; i++){// 找到要刪除節點的前一個節點 currentNode = currentNode.next; } currentNode.next.next.pre = currentNode; // 將 要刪除節點的後一個節點的前驅節點 指向 當前節點(要刪除的節點的前一個節點) currentNode.next = currentNode.next.next; // 將 要刪除節點的前一個節點的next指針指向 要刪除節點的後一個節點 } size--; return currentNode.next ; // 返回刪除的節點 } /** * 獲取tail節點 * @return tail節點 */ public ArtisanNode getTail(){ System.out.println("tail節點的值爲:" + this.tail.data ); return this.tail; } /** * 獲取head節點 * @return head節點 */ public ArtisanNode getHead(){ System.out.println("head節點的值爲:" + this.head.data ); return this.head; } /** * 打印鏈表中的數據 */ public void print() { ArtisanNode currentNode = this.head;// 從head節點開始遍歷 while (currentNode != null) { // 循環,節點不爲空 輸出當前節點的數據 System.out.print(currentNode.data + " -> "); currentNode = currentNode.next; // 將當前節點移動到下一個節點,循環直到爲null } System.out.print("null"); System.out.println(); } /** * 打印先後節點信息 * @param currentNode */ private void printPreAndNextInfo(ArtisanNode currentNode) { System.out.println("當前節點:" + currentNode.data); if (currentNode.pre != null){ System.out.println("當前節點【" + currentNode.data + "】的前驅節點:" + currentNode.pre.data); }else{ System.out.println("當前節點【"+ currentNode.data + "】爲head節點"); } if (currentNode.next != null){ System.out.println("當前節點【"+ currentNode.data + "】的後繼節點:" + currentNode.next.data); }else{ System.out.println("當前節點【"+ currentNode.data + "】爲tail節點"); } } public static void main(String[] args) { ArtisanDoubleLinkedList doubleLinkedList = new ArtisanDoubleLinkedList(); doubleLinkedList.add2Head("artisanData96"); doubleLinkedList.add2Head("artisanData97"); doubleLinkedList.add2Head("artisanData99"); doubleLinkedList.add2Head("artisanData98"); doubleLinkedList.getTail(); doubleLinkedList.add2Tail("artisanData100"); doubleLinkedList.getTail(); doubleLinkedList.print(); doubleLinkedList.getHead(); // doubleLinkedList.add2Nth(2,"addedDataByPos"); // doubleLinkedList.add2Tail2(1); // doubleLinkedList.add2Tail2(2); // doubleLinkedList.add2Tail2(3); // doubleLinkedList.add2Tail2(4); // doubleLinkedList.print(); // // System.out.println("tail:" + doubleLinkedList.tail.data); // // doubleLinkedList.find("artisanData98"); // doubleLinkedList.deleteHead(); // doubleLinkedList.print(); // doubleLinkedList.find("artisanData99"); // System.out.println("被刪除節點:" + doubleLinkedList.deleteNth(1).data); // doubleLinkedList.print(); // doubleLinkedList.find("artisanData96"); } /** * 雙向鏈表中的節點 */ class ArtisanNode { ArtisanNode pre; // 前驅結點 Object data; // 數據 ArtisanNode next;// 後繼節點 public ArtisanNode(Object data) { this.data = data; } } }
重要區別:post
1.數組簡單易用,在實現上使用的是連續的內存空間,能夠藉助CPU的緩存機制,預讀數組中的數據,因此訪問效率更高。this
2.鏈表在內存中並非連續存儲,因此對CPU緩存不友好,沒辦法有效預讀。
3.數組的缺點是大小固定,一經聲明就要佔用整塊連續內存空間。若是聲明的數組過大,系統可能沒有足夠的連續內存空間分配給它, 致使「內存不足(out ofmemory)」。若是聲明的數組太小,則可能出現不夠用的狀況。注意下標越界的問題。
4.動態擴容:數組需再申請一個更大的內存空間,把原數組拷貝進去,很是費時。鏈表自己沒有大小的限制,自然地支持動態擴容,使用的時候也須要考慮佔用內存的問題。