Algorithms_基礎數據結構(03)_線性表之鏈表_雙向鏈表

在這裏插入圖片描述


大綱圖

在這裏插入圖片描述


雙向鏈表

Algorithms_基礎數據結構(02)_鏈表&鏈表的應用案例之單向鏈表中梳理了 單向鏈表的基本操做,接下來咱們繼續來看下雙向鏈表吧。java


雙向鏈表的基本結構

在這裏插入圖片描述

單向鏈表只有一個方向,結點只有一個後繼指針next指向後面的結點。node

雙向鏈表,顧名思義,它支持兩個方向,每一個結點不止有一個後繼指針next指向後面的結點,還有一個前驅指針prev指向前面的結點。web

雙向鏈表須要額外的兩個空間來存儲後繼結點前驅結點的地址。因此,若是存儲一樣多的數據,雙向鏈表要比單鏈表佔用更多的內存空間。sql

雖然兩個指針比較浪費存儲空間,但能夠支持雙向遍歷,這樣也帶來了雙向鏈表操做的靈活性。那相比單鏈表,雙向鏈表適合解決哪一種問題呢?數組

-----> B+Tree:Mysql索引 葉子節點 雙向鏈表緩存


雙向鏈表的基本操做

頭插

在這裏插入圖片描述


尾插

在這裏插入圖片描述


中間部位插入

在這裏插入圖片描述


刪除頭部

在這裏插入圖片描述


刪除尾部

在這裏插入圖片描述


刪除中間位置的數據

在這裏插入圖片描述


查找

一般,雙向鏈表同單鏈表同樣,都僅有一個頭指針。因此雙鏈表查找指定元素的實現同單鏈表相似,都是從表頭依次遍歷表中元素,直到找到對應的元素爲止。數據結構


更新

更改雙鏈表中指定結點數據域的操做那必需要先查找到該節點,所以是在查詢的基礎上完成的。------>即:經過遍歷找到存儲有該數據元素的結點,直接更改其數據域便可。svg


Code

/** * @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.動態擴容:數組需再申請一個更大的內存空間,把原數組拷貝進去,很是費時。鏈表自己沒有大小的限制,自然地支持動態擴容,使用的時候也須要考慮佔用內存的問題。