03 Javascript資料結構與演算法 之 連結串列

zhaoyezi發表於2018-08-23

1. 定義

在棧和佇列中,儲存物件都是通過陣列來實現的。但陣列也有一個缺點:陣列是定長的。如果從素組的起點或中間插入或移除資料,那麼成本會很高(表面看不出來,但是實際是Array類方法在背後幫我們進行移動了)。

而連結串列儲存有序的資料結構,不同於陣列,連結串列的元素在記憶體中不需要連續放置,而是將相關的兩個元素a、b,通過a節點的指標指向b節點的地址來進行引用。

03 Javascript資料結構與演算法 之 連結串列

連結串列好處在於新增元素,不需要移動元素,只需要將插入位置的前後元素的指標修改即可。
03 Javascript資料結構與演算法 之 連結串列


當移除元素也是一樣的,將需要移除元素的前後節點 關聯起來

03 Javascript資料結構與演算法 之 連結串列

2. linkList例項

連結串列是一個類,它包含了以下的方法屬性:

  • 節點node是一個單獨的物件,它應該擁有自己的屬性,不可缺少的是next屬性:指向下一個節點。
  • append(element): 向連結串列中新增node,新增到連結串列的尾巴上,但可能存在連結串列為空的情況
  • insert(position, element): 向指定位置插入新node,插入給定的位置,但可能存在插入位置是第一個位置
  • remove(element): 從列表中移除node
  • indexOf(element): 返回node在連結串列中的索引,沒有則返回-1
  • removeAt(position): 移除特定位置的node,移除特定位置,但可能移除的位置是頭部
  • isEmpty(): 連結串列不包含元素,則為空
  • size(): 連結串列的元素個數
  • toString(): 重寫toString()方法,讓其輸出你想要的值
function LinkList() {
    // 定義連結串列物件
    // 定義連結串列長度
    // 當前遍歷選擇的物件
    let head, length, current;
     
    // 定義Node節點類
    function Node(element, next) {
        this.element = element;
        this.next = next;
    }
    this.append = function(element) {
       
        let node = new Node(element);
        // 如果連結串列中還沒有物件,新增的節點就是第一個
        if (!head) {
            head = node;
        } else {
            // 獲取連結串列最尾巴上的節點
            current = head;
            while (current.next) {
                current = current.next;
            }
            // 將新增節點新增到尾巴上
            current.next = node;
        }
        // 連結串列長度加1
        length++;
    };

    this.insert = function(position, element) {
        // position 不能小於0,並且不能大於連結串列長度
        if (0 > position || position > length) {
            return false;
        }
        let node = new Node(element);
        let index = 0, previous; // 記錄current物件的索引,找到需要插入的位置
        current = head;
        // 如果插入位置是第一個位置,則直接將head賦值給node.next。並將node 設定為head
        if (position === 0) {
            node.next = head;
            head = node;
        } else {
            while(index++ < position) {
                // 記錄current所在位置的前一個物件
                previous = current;
                // 設定current
                current = current.next;
            }
            // previous 應當是插入node的前一個節點,current應該是被插入node的後一個節點
            previous.next = node;
            node.next = current;
        }
        length++;
        return true;
    };

    this.removeAt = function(position) {
         // position 不能小於0,並且不能大於連結串列長度
        if (0 > position || position > length) {
            return null;
        }
        // 如果是移除頭部
        if (position === 0) {
            head = head.next;
        } else {
            // 移除中間的或者尾部
            current = head;
            let index = 0, previous;
            // 找到需要移除的位置(current)
            while (index++ < position) {
                previous = current;
                current = current.next;
            }
            // 將current的前一個node節點的next屬性指向current的下一個node節點
            previous.next = current.next;
        }
        length--;
        return current.element;
    };

    this.indexOf = function(element){
        var current = head,
        index = -1;
        while (current) { 
            if (element === current.element) {
                return index;
            }
            index++;
            current = current.next; 
        }
        return -1;
    };

    this.remove = function(element){
        var index = this.indexOf(element);
        return this.removeAt(index);
    };

    this.isEmpty = function() {
        return length === 0;
    };

    this.size = function() {
        return length;
    };
    this.toString = function() {
        console.log(head)
    }
};
複製程式碼

3. 雙向連結串列

03 Javascript資料結構與演算法 之 連結串列

雙向連結串列和單向連結串列的區別在於:

  • 單向連結串列只能鏈向下一個節點,而雙向連結串列中next屬性鏈向下一個節點,prev屬性鏈向上一個節點
  • insert(position, element): 不僅要控制next,還得控制prev指標。插入指定位置,但可能存在插入位置是第一個位置,最後一個位置

03 Javascript資料結構與演算法 之 連結串列

03 Javascript資料結構與演算法 之 連結串列

  • removeAt(postion): 同單項鍊表一樣,只是多設定一個prev指標。移除特定位置,但可能移除的位置是頭部

03 Javascript資料結構與演算法 之 連結串列

03 Javascript資料結構與演算法 之 連結串列

function DoudlyLinkedList()  {

    // Node節點增加prev屬性,指向前一個node節點
    function Node(element) {
        this.element = element;
        this.next = null;
        this.prev = null;
    }

    // 新增tail,存放連結串列中最後一個節點
    let length = 0, head = null, tail = null, current = null;

    this.insert = function(element, position) {
        if (position < 0 || position > length) {
            return false;
        }
        // 需要插入的節點
        let node = new Node(element);

        // 如果插入節點是頭節點
        if (position === 0) {
             current = head;
            // 第一次插入,沒有node
            if (!head) {
                head = node;
                tail = node;
            } else {
                // 修改原head的prev 指向新node。 新node的next 等於原head
                // 並將新node設定為head
                node.next = current;
                current.prev = node;
                head = node;
            }
            // 插入位置為尾巴
        } else if (position === length) {
            current = tail;
            current.next = node;
            node.prev = current;
            tail = node;

        // 插入位置是任意中間位置
        } else {
            let previous = head;
            current = head;
            let i = 0;
            while (i++ < position) {
                previous = current;
                current = current.next;
            }
            // previous 是需要插入位置的前一個node, current是後一個節點
            node.prev = previous; // 新增的
            node.next = current;
            previous.next = node;
            current.prev = node; // 新增
        }

        length++;
        return true;
    };

    this.removeAt = function(position) {
        if (position > length || position < 0) {
            return null;
        }

        current = head;

        // 如果移除的是第一個
        if (position === 0 ) {
            
            head = current.next;
            // 如果只有一項,那麼tail 也得設定為null
            if (length === 1) {
                tail = null;
            } else {
                // 將headprev索引設定為null
                head.prev = null;
            }

            // 移除的是末尾的元素
        } else if (position === length-1) {
            current = tail;
            tail = tail.prev;
            tail.next = null;
        } else {
            let index = 0, previous;
            while (index++ < position) {
                previous = current;
                current = current.next;
            }
            // 移除current,就是講current前後兩個節點聯絡在一起
            previous.next = current.next;
           current.next.prev = previous;
        }

        length--;
        return current.element;
    };

    this.toString = function() {
        console.log(head)
    }
}
複製程式碼

4. 迴圈連結串列

迴圈連結串列 分為單項迴圈連結串列與雙向迴圈連結串列。不再編寫程式碼

  • 單項迴圈連結串列: 最後一個節點tailnext屬性 指向 第一個節點head

03 Javascript資料結構與演算法 之 連結串列

  • 雙向迴圈連結串列:最後一個節點tailnext屬性指向第一個節點head。第一個節點headprev屬性指向最後一個節點tail

03 Javascript資料結構與演算法 之 連結串列

相關文章