1. 程式人生 > >資料結構與算法系列五(雙向連結串列)

資料結構與算法系列五(雙向連結串列)

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