別說鏈表不重要(四):雙鏈表的添加刪除基本原理+Js實現一個雙鏈表類

雙鏈表的基本原理+

目錄

一、雙鏈表的基本原理

我們在前面的單鏈表的基本原理和實戰中介紹了單鏈表。

單鏈接列表中的結點具有 val 字段,以及用於順序鏈接結點的“Next”引用字段。

在本文中,我們將介紹另一種類型的鏈表:雙鏈表。

定義

雙鏈表以類似的方式工作,但還有一個引用字段,稱爲“prev”字段。有了這個額外的字段,您就能夠知道當前結點的前一個結點。

在這裏插入圖片描述

基本操作

與單鏈表類似,我們將介紹在雙鏈表中如何訪問數據、插入新結點或刪除現有結點。

我們可以與單鏈表相同的方式訪問數據:

  • 我們不能在常量級的時間內訪問隨機位置。
  • 我們必須從頭部遍歷才能得到我們想要的第一個結點。
  • 在最壞的情況下,時間複雜度將是 O(N),其中 N 是鏈表的長度。
  • 但是在增加和刪除操作時,我們還要考慮到prev節點

二、雙鏈表的添加原理

  • 節點1next指針重新指向添加的節點4
  • 節點4next指向原下一個節點2
  • 注意,節點4節點2的prev要更新指向到對應的前節點
    在這裏插入圖片描述

在頭節點添加

  • 新添加的節點next指向頭節點,並更新頭節點的prev

在尾節點添加

  • 尾節點的前一個節點next清空

三、雙鏈表的刪除原理

如果我們想刪除某一個節點cur,只需要知道當前節點即可,因爲cur.prev節點就是前節點

cur.prev.next = cur.next

在這裏插入圖片描述

在頭節點刪除

  • 清空頭節點的next節點所保存的頭節點即可

在尾節點刪除

  • 清空尾節點的prev節點所保存的尾節點即可

四、設計雙鏈表+實現代碼

我們先來看一下設計要求

可以選擇使用單鏈表或雙鏈表。單鏈表中的節點應該具有兩個屬性:val 和 next。val 是當前節點的值,next 是指向下一個節點的指針/引用。如果要使用雙向鏈表,則還需要一個屬性 prev 以指示鏈表中的上一個節點。假設鏈表中的所有節點都是 0-index 的。

在鏈表類中實現這些功能:

  • get(index):獲取鏈表中第 index 個節點的值。如果索引無效,則返回-1。
  • addAtHead(val):在鏈表的第一個元素之前添加一個值爲 val 的節點。插入後,新節點將成爲鏈表的第一個節點。
  • addAtTail(val):將值爲 val 的節點追加到鏈表的最後一個元素。
  • addAtIndex(index,val):在鏈表中的第 index 個節點之前添加值爲 val 的節點。如果 index 等於鏈表的長度,則該節點將附加到鏈表的末尾。如果 index 大於鏈表長度,則不會插入節點。如果index小於0,則在頭部插入節點。
  • deleteAtIndex(index):如果索引 index 有效,則刪除鏈表中的第 index 個節點。
function Node(val) {
    this.val = val //保存當前值
    this.prev = null //記錄前節點
    this.next = null //記錄後節點
}

/**
 * Initialize your data structure here.
 */
var MyLinkedList = function() {
    this.head = new Node('head') // 定義一個頭節點,保證初始化時不爲空
};

/**
 * Get the value of the index-th node in the linked list. If the index is invalid, return -1.
 * @param {number} index
 * @return {number}
 */
MyLinkedList.prototype.get = function(index) {
    var cur = this.head
    var count = -1 // 因爲創建了head這個哨兵節點,所以count下標默認尾-1
    if(index < 0) return -1;
    while(cur){
        if(count == index){
            return cur.val
        }
        cur = cur.next
        count++
    }
    return -1
};

/**
 * Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtHead = function(val) {
    var cur = this.head
    var node = new Node(val)
    // 要注意head節點後是不是存在節點
    if(cur.next){
        node.next = cur.next
        node.prev = cur

        cur.next.prev = node
        cur.next = node
    }else{
        cur.next = node
        node.prev = cur
    }
};


/**
 * Append a node of value val to the last element of the linked list.
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtTail = function(val) {
    var cur = this.head
    var node = new Node(val)
    // 只需要找到尾節點
    while(cur.next){
        cur = cur.next
    }
    cur.next = node
    node.prev = cur
};

/**
 * Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted.
 * @param {number} index
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtIndex = function(index, val) {
    var cur = this.head
    var node = new Node(val)
    var count = -1 // 因爲創建了一個哨兵節點,所以記錄下標
    if(index < 0){ // 插入位置<0時,直接加到頭節點
        node.next = cur.next
        node.prev = cur

        cur.next.prev = node // 維護prev節點
        node.prev = cur //維護prev節點
        return;
    }
    while(cur){
        // 如果在鏈表中間匹配到
        if(count == index){
            node.next = cur // 添加節點next->當前節點
            node.prev = cur.prev // 添加節點prev-> 當前節點prev

            cur.prev.next = node // 前節點next->添加節點
            cur.prev = node // 前節點->添加節點
            return ;
        }
        // 如果走到了尾節點,且下標+1 = index = 數組長度
        if(!cur.next && count+1 == index){
            cur.next = node
            node.prev = cur
            return;
        }
        // 沒走到尾節點 && 下標未匹配上,則向後跳
        cur = cur.next
        count++
    }
    return;
};

/**
 * Delete the index-th node in the linked list, if the index is valid.
 * @param {number} index
 * @return {void}
 */
MyLinkedList.prototype.deleteAtIndex = function(index) {
    var cur = this.head
    var count = -1
    if(index < 0) return -1; // 下標無效
    while(cur){
        if(count == index){
            // 如果不是尾節點,如果改變cur.prev節點next的指向
            // 同時更新cur.next節點prev的指向,做到忽略cur節點
            if(cur.next){
                cur.prev.next = cur.next
                cur.next.prev = cur.prev
            }else{
                cur.prev.next = null
            }
            return;
        }
        cur = cur.next
        count++
    }
};

注意

  1. 我們在創建實例時,默認創建了一個head哨兵節點,爲了防止初始化爲空的情況,這導致之後涉及到下標的問題都需要多考慮一步
  2. 涉及到尾節點添加和刪除時可以類比單鏈表,其他節點的增加和刪除都要注意維護前節點的next後節點的prev

到這裏涉及到鏈表的絕大部分基本原理就總結完了。之後我會繼續討論堆棧的原理和基本實現。

關於我

既然已經讀到末尾,不妨點個star鼓勵一下吧。如果還想了解算法、LeetCode、前端方面的文章,不妨逛逛我的CSDN

瞧一瞧(求Star!!!)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章