1. 程式人生 > >js資料結構 -- 連結串列, 雙向連結串列,迴圈連結串列

js資料結構 -- 連結串列, 雙向連結串列,迴圈連結串列

陣列作為js常用的資料結構,存取元素都非常的方便,但是其內部的實現原理跟其他計算機語言對陣列的實現都是一樣的。

由於陣列在記憶體中的儲存是連續的,所以當在陣列的開頭或者中間插入元素時,內部都需要去移動其他元素的位置,這個移動元素的成本很高。

連結串列也是儲存有序的元素集合,但不同於陣列,連結串列中的元素在記憶體中的存放並不是連續的,每個元素有一個儲存元素自身的節點和一個指向下一個元素引用(也稱為指標)的節點組成。

優點:相對於傳統的陣列,向連結串列中插入和移除元素,都不要移動其他任何元素。

缺點:要想訪問連結串列中間的一個元素,需要從 (表頭)開始迭代列表直到找到 所需的元素。

連結串列的基本骨架:

function linkedList() {
    var Node = function(ele) {
        this.ele = ele;
        this.next = null;
    }
    var length = 0var head = null;  // 用來儲存第一個元素的引用

    // 對外暴露的方法
    this.append = (ele) => {};  //新增元素
    this.insert = (pos, ele) => {};  // 向固定位置插入元素
    this.removeAt
= (pos) =>
{}; // 移除固定位置的元素 this.remove = () => {}; // 移除結尾元素 this.indexOf = (ele) => {}; // 返回元素所在位置 this.isEmpty = () => {}; // 連結串列是否為空 this.size = () => {}; // 返回連結串列的長度 this.toString = () => {}; // 返回 this.print = () => {}; // 列印連結串列資料 }

連結串列的具體實現

向連結串列尾部追加元素


向LinkedList物件尾部新增一個元素時,可能有兩種場景:
1. 列表為空,新增的是第一個元素
2. 或者列表不為空,向其追加元素

this.append = function(element){
   var node = new Node(element), //{1}
              current; //{2}
   if (head === null){ // 列表中的第一個節點 //{3}
       head = node;
   } else {
       current = head; //{4}
       //  迴圈列表直到找到連結串列的最後一項,        
       while(current.next){
            current = current.next;
       }
        //   找到連結串列的最後一項,將其next賦為node,建立連線    
       current.next = node; //{5}
   }
   length++; // 更新連結串列的長度   //{6}
};

從連結串列中移除元素
從連結串列中移除某個元素,也可能有兩種場景:
1. 移除第一個元素
2. 移除第一個元素以外的其他元素

// 移除特定位置的元素
this.removeAt = (pos) => {
    // 檢查邊界條件
    if (pos > -1 && pos < length) {
        let current = head;  // head指向第一個元素
        let previous = null;  // 儲存前一個節點
        let index = 0;  // 計數

        // 移除第一項
        if (pos === 0) {
            head = current.next; // 第二個元素
        } else {
            while (index++ < pos) {
                previous = current;
                current = current.next;
            }
            previous.next = current.next; // 將目標刪除元素的前一個元素的next指向目標刪除元素的後一個元素
        }
        return current.ele;  // 返回要刪除節點的值
    } else {
        return null;  // 輸入不合法
    }
}

要從列表中移除當前元素,要做的就是將previous.next和current.next連線起來,當前元素就會被丟棄在計算機記憶體中,等待著被垃圾回收機制回收

在任意位置插入元素
同樣考慮兩種情況插入元素
1. 連結串列首部插入節點
2. 連結串列手部除外的其他位置插入節點

this.insert = (pos, ele) => {
    // 首先檢測邊界值
    if (pos >=0 && pos <= length) {
        const node = new Node(ele);  // 建立節點
        let previous = null;  // 儲存前一個節點
        let current = head; // 目標位置節點
        let index = 0;

        // 連結串列的起點插入一個值
        if (pos === 0) {
            node.next = current;
            head = node;
        } else {
            while (index++ < pos) {
                previous = current;
                current = current.next;
            }
            node.next = current;
            previous.next = node;
        }
        length++; // 更新連結串列的長度
        return true; // 插入成功
    } else {
        return false;
    }
}

其他方法的實現

this.toString = () => {
    let current = head;
    let rets = '';
    let index = 0;
    while (current) {
        rets += current.ele;
        current = current.next;
    }
    return rets;
}
this.indexOf = (ele) => {
    let current = head;
    let index = -1;
    while (current) {
        if (current.ele === ele) {
            return index++;
        }
        index++;
        current = current.next;
    }
    return index;
}
this.remove = (ele) => {
    var pos = this.indexOf(ele); // 返回元素所在的位置
    this.removeAt(pos);
}
this.isEmpty = () => {
    return length === 0;
}
this.size = () => {
    return length;
}

雙向連結串列

雙向連結串列和普通連結串列的區別在於,在普通連結串列中, 一個節點只有指向下一個節點的連線,而在雙向連結串列中,連結是雙向的:一個指向下一個元素, 另一個指向前一個元素

雙向連結串列的實現

function DoublyLinkedList() {
    var Node = function(ele) {
        this.ele = ele;
        this.prev = null; // 新增的
        this.next = null;
    }
    var length = 0var head = null;  // 用來儲存第一個元素的引用
    var tail = null;  // 用來儲存最後一個元素的引用  新增的

    // 對外暴露的方法
    this.append = (ele) => {};  //新增元素
    this.insert = (pos, ele) => {};  // 向固定位置插入元素
    this.removeAt = (pos) => {};  // 移除固定位置的元素
    this.remove = () => {};    // 移除結尾元素
    this.indexOf = (ele) => {};  // 返回元素所在位置
    this.isEmpty = () => {};  // 連結串列是否為空
    this.size = () => {};  // 返回連結串列的長度
    this.toString = () => {}; // 返回
    this.print = () => {};  // 列印連結串列資料
}

雙向連結串列提供了兩種迭代列表的方法:從頭到尾,或者反過來。我們也可以訪問一個特定節 的下一個或前一個元素。在單向連結串列中,如果迭代列表時錯過了要找的元素,就需要回到列表起點,重新開始迭代。這是雙向連結串列的一個優點。

插入方法實現

this.insert = (pos, ele) {
    // 檢查邊界條件
    if (pos > -1 && pos <= length) {
        let node = new Node(ele);
        let current = head; // 連結串列第一個節點
        let previous = null;
        let index = 0;
        // 連結串列首部新增
        if (pos === 0) {
            if(!head) {
               head = node;
               tail = node;
            } else {
                node.next = current;
                current.prev = node;
                head = node;
            }
        } else if(pos === length) {  // 最後一項
            current = tail;
            current.next = node;
            node.pre = current;

            tail = node; // 更新最後一項
        } else {
            // 中間位置
            while (index++ < pos) {
                previous = current;
                current = current.next;
            } 
            previous.next = node;
            node.next = current;

            current.prev = node; // 新增
            node.prev = previous;  // 新增
        }
        length++;   // 更新連結串列長度
        return true;
    } else {
        return false;
    }
}

從任意位置移除元素演算法實現
需要處理三種場景:
1. 刪除連結串列第一個元素
2. 刪除連結串列最後一個元素
3. 刪除連結串列中間某個元素

this.removeAt = (pos) => {
    // 檢查邊界值
    if (pos > -1 && pos <= length) {
        let current = head;
        let previous = null;
        let index = 0;

        // 移除第一個元素
        if (pos === 0) {
            head = current.next;
            if(!head){ // 只有一個元素
                tail = null;
            } else {
                head.prev = null;
            }
        } else if (pos === length) {
            // 移除最後一個元素
            current = tail;
            tail = current.prev;
            tail.next = null;
        } else {
            // 中間位置
            while (index++) {
                previous = current;
                current = current.next; 
            }
            previous.next = current.next;
            current.next.prev = previous;
        }
        length--;   // 更新連結串列長度
        return true;
    } else {
        return false;
    }
}

迴圈連結串列

迴圈連結串列可以向連結串列一樣單向引用,也可以像雙向連結串列一樣有雙向引用。迴圈連結串列於連結串列的唯一區別是:迴圈連結串列的最後一個元素指向下一個元素的指標不是null,而是指向第一個元素(head);

總結: 連結串列相比陣列最大的優點是,無需移動連結串列中的元素,就可以輕鬆實現元素的新增和刪除。因此,當需要新增和刪除很多元素時,最好的選擇是連結串列而非陣列