在上一篇文章中,咱們瞭解了隊列和棧的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 種狀況,
position非法
,拋出錯誤。 position爲 1
,將this.head 指向下一個結點,而後將this.head.previous = null
,這時要判斷一下 this.head 是否爲空,若是爲空就代表這個雙向鏈表本來只有一個結點,因此 remove 後 須要把 this.tail = null
。當 position 爲最後一個結點
時,咱們把 this.tail 前移this.tail = this.tail.previous
,此時 this.tail 指向倒數第二個結點,再執行this.tail.next = null
,就把最後一個結點remove掉了position 爲其餘位置
,咱們先定位到要remove掉的結點,而後將要刪除結點的前一結點與要刪除結點的後一結點連接起來,就把要刪除的結點remove掉了,既beforeNodeToDelete.next = afterNodeToDelete ; afterNodeToDelete.previous = beforeNodeToDelete
單鏈表和雙向鏈表具備如下特色: