資料結構與算法系列五(雙向連結串列)
阿新 • • 發佈:2020-02-27
1.引子
1.1.為什麼要學習資料結構與演算法?
有人說,資料結構與演算法,計算機網路,與作業系統都一樣,脫離日常開發,除了面試這輩子可能都用不到呀!
有人說,我是做業務開發的,只要熟練API,熟練框架,熟練各種中介軟體,寫的程式碼不也能“飛”起來嗎?
於是問題來了:為什麼還要學習資料結構與演算法呢?
#理由一: 面試的時候,千萬不要被資料結構與演算法拖了後腿 #理由二: 你真的願意做一輩子CRUD Boy嗎 #理由三: 不想寫出開源框架,中介軟體的工程師,不是好廚子
1.2.如何系統化學習資料結構與演算法?
我想好了,還是需要學習資料結構與演算法。但是我有兩個困惑:
1.如何著手學習呢?
2.有哪些內容要學習呢?
學習方法推薦:
#學習方法 1.從基礎開始,系統化學習 2.多動手,每一種資料結構與演算法,都自己用程式碼實現出來 3.思路更重要:理解實現思想,不要背程式碼 4.與日常開發結合,對應應用場景
學習內容推薦:
資料結構與演算法內容比較多,我們本著實用原則,學習經典的、常用的資料結構、與常用演算法
#學習內容: 1.資料結構的定義 2.演算法的定義 3.複雜度分析 4.常用資料結構 陣列、連結串列、棧、佇列 散列表、二叉樹、堆 跳錶、圖 5.常用演算法 遞迴、排序、二分查詢 搜尋、雜湊、貪心、分治 動態規劃、字串匹配
2.考考你
在上一篇【資料結構與算法系列四(單鏈表)】中,詳細給連結串列下了定義,並且比較了連結串列與陣列。你還記得什麼是連結串列嗎?連結串列就是通過指標,將一組零散的記憶體串聯起來使用,每一個零散的記憶體塊,我們稱為節點。實際開發常用的連結串列有:單鏈表、雙向連結串列、迴圈連結串列。
這一篇我們看一下雙向連結串列的實現。
#考考你: 1.你知道什麼是雙向連結串列嗎? 2.你知道HashMap的實現原理嗎(底層用了哪些資料結構)?
雙向連結串列:
3.案例
3.1.節點封裝
簡述:
單鏈表實現,每個節點Node只需要封裝資料: e,與指向下一個節點的後繼指標:next。在雙向連結串列中,還需要增加指向前一個節點的前驅指標:prev。
/** * 節點:Node<E> */ class Node<E> { protected E e; protected Node<E> prev; protected Node<E> next; public E getE() { return e; } public void setE(E e) { this.e = e; } public Node<E> getPrev() { return prev; } public void setPrev(Node<E> prev) { this.prev = prev; } public Node<E> getNext() { return next; } public void setNext(Node<E> next) { this.next = next; } }
3.2.完整程式碼
簡述:
實現連結串列小技巧,增加一個空頭節點,簡化連結串列程式碼實現複雜度。這樣有空頭節點的連結串列實現,稱為:帶頭節點連結串列
package com.anan.struct.linetable; /** * 雙向連結串列實現思路: * 1.空閒一個頭節點,即頭節點不儲存資料 * 2.這樣有利於簡化連結串列的實現 */ public class DoubleLinkedList<E> { // 連結串列大小 private int size; public int getSize() { return size; } // 頭節點 private Node<E> head; // 尾節點 private Node<E> tail; public DoubleLinkedList(){ head = new Node<E>(); tail = head; size ++; } /** * 新增元素到連結串列結尾 */ public boolean add(E e){ // 建立節點 Node<E> node = new Node<E>(); node.setE(e); node.setPrev(tail); tail.next = node; tail = node; size ++; return true; } /** * 在指定位置插入連結串列元素 */ public boolean insertPos(int pos,E e){ // 獲取位置節點 Node<E> posNode = get(pos); if(posNode == null){ return false; } // 建立新節點 Node<E> newNode = new Node<E>(); newNode.setE(e); newNode.setPrev(posNode.prev); newNode.setNext(posNode); posNode.prev.setNext(newNode); posNode.setPrev(newNode); size ++; return true; } /** * 刪除連結串列結尾元素 */ public boolean remove(){ tail = tail.prev; tail.next = null; size --; return false; } /** * 在指定位置刪除連結串列元素 */ public boolean delPos(int pos){ // 獲取指定位置節點 Node<E> node = get(pos); if(node == null){ return false; } // 刪除 node.prev.setNext(node.next); node.next.setPrev(node.prev); size --; return true; } /** * 獲取節點 */ public Node<E> get(int pos){ // 判斷位置有效性 if(pos < 1 || pos > size){ return null; } Node<E> node = head; for(int i = 1; i <= pos; i++){ node = node.next; } return node; } /** * 獲取節點資料 */ public E getValue(int pos){ // 獲取節點 Node<E> node = get(pos); if(node == null){ return null; }else{ return node.e; } } /** * 節點:Node<E> */ class Node<E> { protected E e; protected Node<E> prev; protected Node<E> next; public E getE() { return e; } public void setE(E e) { this.e = e; } public Node<E> getPrev() { return prev; } public void setPrev(Node<E> prev) { this.prev = prev; } public Node<E> getNext() { return next; } public void setNext(Node<E> next) { this.next = next; } } }
3.3.測試程式碼
package com.anan.struct.linetable; /** * 測試雙向連結串列 */ public class DoubleLinkedListTest { public static void main(String[] args) { // 建立連結串列 DoubleLinkedList<Integer> list = new DoubleLinkedList<Integer>(); // 新增元素 int size = 5; for (int i = 0; i < size; i++) { list.add(i); } // 1.初始化連結串列,列印連結串列元素 System.out.println("1.初始化連結串列,列印連結串列元素-----------------------------------------"); list(list); // 2.指定位置插入元素 System.out.println("2.指定位置插入元素-----------------------------------------"); list.insertPos(1,666); list(list); // 3.刪除連結串列結尾元素 System.out.println("3.刪除連結串列結尾元素-----------------------------------------"); list.remove(); list(list); // 刪除指定位置元素 System.out.println("5.刪除指定位置元素-----------------------------------------"); list.delPos(3); list(list); } public static void list(DoubleLinkedList<Integer> list){ System.out.println("當前連結串列大小,size:" + list.getSize()); for (int i = 1; i < list.getSize(); i++) { System.out.println(list.getValue(i)); } } }
測試結果:
1.初始化連結串列,列印連結串列元素----------------------------------------- 當前連結串列大小,size:6 0 1 2 3 4 2.指定位置插入元素----------------------------------------- 當前連結串列大小,size:7 666 0 1 2 3 4 3.刪除連結串列結尾元素----------------------------------------- 當前連結串列大小,size:6 666 0 1 2 3 5.刪除指定位置元素----------------------------------------- 當前連結串列大小,size:5 666 0 2 3 Process finished with exit code 0
4.討論分享
#考考你答案: 1.你知道什麼是雙向連結串列嗎? 1.1.雙向連結串列在單鏈表的基礎上,增加了前驅指標 1.2.有了前驅指標,便於連結串列從後往前遍歷查詢 1.3.雙向連結串列,與單鏈表對比,參考下圖: 2.你知道HashMap的實現原理嗎(底層用了哪些資料結構)? 2.1.HashMap是key、value對的對映表 2.2.它的底層基礎資料結構是:陣列 2.3.利用陣列支援下標隨機訪問特性,實現快速訪問,時間複雜度:O(1) 2.4.通過雜湊函式hash(key),計算原始key,與陣列下標(雜湊值)的對應關係 2.5.不同的key,經過hash(key),雜湊值有可能相同 2.6.雜湊值相同的情況,稱為:雜湊衝突 2.7.發生雜湊衝突的時候,通過連結串列,或者紅黑樹解決雜湊衝突 2.8.在HashMap中,同時應用了:陣列、連結串列
單鏈表與雙向連結串列:
&n