數據結構JavaScript描述(二)

在上一篇文章中,咱們瞭解了隊列和棧的JavaScript描述,如今讓咱們來了解一下 單鏈表雙向鏈表 的實現。本文的代碼並不是全部都由本人所寫,只是出於學習目的,在此分享出來,並加上必定的解釋,便於你們學習。node

本系列文章的代碼可在 https://github.com/HolyZheng/...找到。

咱們直入話題:git


單鏈表

單鏈表 是存儲結構的一種,它具備如下特色:github

單鏈表的特色:

  • 單鏈表不可隨機訪問
  • 單鏈表不須要佔連續的存儲空間,可動態分配
  • 插入與刪除操做不須要移動多個元素
  • 每一個節點既存儲數據,又同時存儲指向下一節點的地址

如今咱們建立一個單鏈表,並給它添加<font color=#A52121>add、searchNode、remove</font> 三個方法。
建立一個單鏈表:函數

//單鏈表
function SinglyList () {
    this._length = 0;
    this.head = null;
}

這個單鏈表暫時又兩個屬性: _length 鏈表的長度,head 鏈表頭節點。學習

每個節點須要存儲數據,還要指向下一節點,因此爲每一個節點建立一個node類this

//結點
function Node (data) {
    this.data = data;
    this.next = null;
}

node類 具備兩個屬性,data 存儲數據,next 指向下一節點。
如今咱們爲它添加幾個基本的方法:add、searchNode 和 remove 函數。咱們先實現 add 方法,給單鏈表添加節點。在 add 方法中咱們須要考慮兩中狀況,分別爲:單鏈表爲空和單鏈表不爲空。prototype

//add方法

SinglyList.prototype.add = function (value) {
    var node = new Node(value),
        currentNode = this.head;
        
        //1st:當單鏈表爲空時
        if (!currentNode) {
            this.head = node;
            this._length++;
            return node;
        }

        //2nd:單鏈表不爲空
        while (currentNode.next) {
            currentNode = currentNode.next;
        }

        currentNode.next = node;
        this._length++;
        return node;
}

能夠看到在代碼中,咱們先定義了一個 currentNode 變量,指向 this.head ,而後判斷若是當前鏈表爲空,直接將新節點賦值給 this.head ,若是不爲空,先將currentNode指向最後的節點,而後再執行 currentNode.next = node將新節點添加到鏈表的末尾。
再來實現 searchNode 方法:
searchNode方法的做用是搜索特定位置的節點code

//searchNode方法

SinglyList.prototype.searchNode = function (position) {
    var currentNode = this.head,
        length = this._length,
        message = {failure: "Failure: non-existent node in this list"};
    
    //1st:位置position非法
    if (length === 0 || position < 1 || position > length) {
        throw new Error(message.failure);
    } 

    //2nd:位置position合法
    for (var i = 1; i < position; i++) {
        currentNode = currentNode.next;
    }
    return currentNode;
}

咱們很簡單就實現了這個方法,先檢測鏈表是否爲空和查詢的位置是否合法,不爲空且位置合法的話,利用一個循環,將currentNode指向特定的position,而後就能夠訪問到須要的節點了。隊列

咱們如今來看一下最後的一個方法: remove。 remove方法比起前兩個方法的話,要複雜一點,由於要考慮刪減了一個元素以後,還要保持整個鏈表的連續性。ip

//remove方法

SinglyList.prototype.remove = function (position) {
    var currentNode = this.head,
        length = this._length,
        message = {failure: "Failure: non-existent node in this list"},
        beforeNodeToDelete = null,
        nodeToDelete = null;
    
    //1st 位置position非法
    if (position < 0 || position > length) {
        throw new Error(message.failure);
    }

    //2nd 位置position爲 1
    if (position === 1) {
        this.head = currentNode.next;
        nodeToDelete = currentNode;
        currentNode = null;
        this._length--;
        return nodeToDelete;
    }

    //3rd position爲其餘位子
    for(var i = 1; i < position; i++) {
        beforeNodeToDelete = currentNode;
        nodeToDelete = currentNode.next;
        
        currentNode = currentNode.next;
    }

    beforeNodeToDelete.next = nodeToDelete.next;
    currentNode = null;
    this._length--;
    return nodeToDelete;
}

首先檢查 position 的值是否合法,而後看position的值是否爲 1,若是爲 1 那就好辦了,將 this.head 指向原this.head.next,而後長度減 1 便可。若是position爲其餘位置,那就要先拿到 要刪除節點的前一節點 <要刪除的節點 而後將前一節點的next指向要刪除節點的next,以保持刪除節點後,鏈表的連續。理解了這點,那就基本能夠理解代碼了。


雙向鏈表

雙向鏈表就是在單鏈表的基礎上,添加了一個指向當前結點的前驅的變量,這樣就能夠方便的由後繼來找到其前驅,就能夠雙向的訪問鏈表。
一樣咱們先來建立一個 結點類

//節點類

function Node (value) {
    this.data = value;
    this.previous = null;
    this.next = null;
}

能夠看到這裏多了一個 this.previous ,做用就是指向它的前驅。

而後再來看一下雙向鏈表這個類:

function DoublyList () {
    this._length = 0;
    this.head = null;
    this.tail = null;
}

比起 單鏈表雙向鏈表 多了一個指向尾結點的 this.tail。
一樣,在這裏咱們實現 add、searchNode 和 remove 三個方法。先來看 add 方法:

//add方法

DoublyList.prototype.add = function (value) {
    var node = new Node(value);
    if(this._length) {
        this.tail.next = node;
        node.previous = this.tail;
        this.tail = node;
    } else {
        this.head = node;
        this.tail = node;
    }
    this._length++;
    return node;
}

在插入新結點的時候咱們一如既往的須要檢查鏈表是否爲空,若是鏈表爲空,就將 this.head 和 this.tail 都指向新結點,若是不爲空,那就將新結點添加到鏈表末尾,並將新結點的 previous 指向原 this.tail 。這樣就完成了 add 方法。

searchNode方法:

//searchNode

DoublyList.prototype.searchNode = function (position) {
    var currentNode = this.head,
        length = this._length,
        message = {failure: "Failure: non-existent node in this list"};
    if(length === 0 || position < 1 || position > length) {
        throw new Error(message.failure);
    }
    for (var i = 1; i < position; i++) {
        currentNode = currentNode.next;
    }
    return currentNode;
}

雙向鏈表的searchNode方法和單鏈表的差很少,都是藉助循環直接拿到要訪問的結點。

最後是最複雜的remove方法

//remove方法

DoublyList.prototype.remove = function (position) {
    var currentNode = this.head,
        length = this._length,
        message = {failure: "Failure: non-existent node in this list"},
        beforeNodeToDelete = null,
        nodeToDelete = null,
        afterNodeToDelete = null;
    
    //1st: 位置position非法
    if (length === 0 || position < 1 || position > length) {
        throw new Error(message.failure);
    }

    //2nd 位置爲第一個節點
    if (position === 1) {
        nodeToDelete = this.head;
        this.head = currentNode.next;
        if (this.head) {
            this.head.previous = null;
        } else {
            this.tail = null;
        }
    //3rd 位置爲最後一個節點
    } else if (position === this._length) {
        this.tail = this.tail.previous;
        this.tail.next = null;
    //4th 位置爲其餘節點
    } else {
        for (var i = 1; i < position; i++) {
            currentNode = currentNode.next;
        }
        beforeNodeToDelete = currentNode.previous;
        nodeToDelete = currentNode;
        afterNodeToDelete = currentNode.next;

        beforeNodeToDelete.next = afterNodeToDelete;
        afterNodeToDelete.previous = beforeNodeToDelete;
    }
    this._length--;
    return nodeToDelete;
}

remove方法要對傳進來的 position 進行判斷,分紅 4 種狀況,

分別爲:
  1. position非法,拋出錯誤。
  2. position爲 1,將this.head 指向下一個結點,而後將this.head.previous = null,這時要判斷一下 this.head 是否爲空,若是爲空就代表這個雙向鏈表本來只有一個結點,因此 remove 後 須要把 this.tail = null
  3. 當 position 爲最後一個結點時,咱們把 this.tail 前移this.tail = this.tail.previous,此時 this.tail 指向倒數第二個結點,再執行this.tail.next = null,就把最後一個結點remove掉了
  4. 最複雜的狀況,position 爲其餘位置,咱們先定位到要remove掉的結點,而後將要刪除結點的前一結點與要刪除結點的後一結點連接起來,就把要刪除的結點remove掉了,既beforeNodeToDelete.next = afterNodeToDelete ; afterNodeToDelete.previous = beforeNodeToDelete

總結

  1. 單鏈表和雙向鏈表,爲存儲結構的一種。
  2. 單鏈表和雙向鏈表具備如下特色:

    • 可動態分配空間,但不能隨機訪問。
    • 插入和刪除操做不須要移動多個元素
    • 每一個節點既存儲數據,又同時存儲指向下一節點的地址
  3. 雙向鏈表爲在單鏈表的基礎上,添加了一個指向當前結點的前驅的變量,這樣就能夠方便的由後繼來找到其前驅,就能夠雙向的訪問鏈表。
相關文章
相關標籤/搜索