1. 程式人生 > >JavaScript實現雙向連結串列

JavaScript實現雙向連結串列

## JavaScript實現雙向連結串列 ### 一、雙向連結串列簡介 **雙向連結串列**:既可以**從頭遍歷到尾**,又可以**從尾遍歷到頭**。也就是說連結串列連線的過程是**雙向**的,它的實現原理是:一個節點既有**向前連線的引用**,也有一個**向後連線的引用**。 **雙向連結串列的缺點:** * 每次在**插入或刪除**某個節點時,都需要處理四個引用,而不是兩個,實現起來會困難些; * 相對於單向連結串列,所佔**記憶體空間更大**一些; * 但是,相對於雙向連結串列的便利性而言,這些缺點微不足道。 **雙向連結串列的結構:** ![image-20200227204728456](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/1.png) * 雙向連結串列不僅有**head**指標指向第一個節點,而且有**tail**指標指向最後一個節點; * 每一個節點由三部分組成:**item**儲存資料、**prev**指向前一個節點、**next**指向後一個節點; * 雙向連結串列的第一個節點的prev指向**null**; * 雙向連結串列的最後一個節點的next指向**null**; **雙向連結串列常見的操作(方法):** * append(element):向連結串列尾部新增一個新的項; * inset(position,element):向連結串列的特定位置插入一個新的項; * get(element):獲取對應位置的元素; * indexOf(element):返回元素在連結串列中的索引,如果連結串列中沒有元素就返回-1; * update(position,element):修改某個位置的元素; * removeAt(position):從連結串列的特定位置移除一項; * isEmpty():如果連結串列中不包含任何元素,返回trun,如果連結串列長度大於0則返回false; * size():返回連結串列包含的元素個數,與陣列的length屬性類似; * toString():由於連結串列項使用了Node類,就需要重寫繼承自JavaScript物件預設的toString方法,讓其只輸出元素的值; * forwardString():返回正向遍歷節點字串形式; * backwordString():返回反向遍歷的節點的字串形式; ### 二、封裝雙向連結串列類 #### 2.0.建立雙向連結串列類 先建立雙向連結串列類DoubleLinklist,並新增基本屬性,再實現雙向連結串列的常用方法: ``` //封裝雙向連結串列類 function DoubleLinklist(){ //封裝內部類:節點類 function Node(data){ this.data = data this.prev = null this.next = null } //屬性 this.head = null this.tail ==null this.length = 0 } ``` #### 2.1.append(element) **程式碼實現:** ``` //append方法 DoubleLinklist.prototype.append = data => { //1.根據data建立新節點 let newNode = new Node(data) //2.新增節點 //情況1:新增的是第一個節點 if (this.length == 0) { this.tail = newNode this.head = newNode //情況2:新增的不是第一個節點 }else { newNode.prev = this.tail this.tail.next = newNode this.tail = newNode } //3.length+1 this.length += 1 } ``` **過程詳解:** 新增節點時分為多種情況: * 情況1:新增的是第一個節點:只需要讓head和tail都指向新節點即可; ![image-20200228094847845](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/2.png) * 情況2:新增的不是第一個節點,如下圖所示:只需要改變相關引用的指向即可。 * 通過:newNode.prev = this.tail:建立指向1; * 通過:this.tail.next = newNode:建立指向2; * 通過:this.tail = newNode:建立指向3 要注意改變變數指向的順序,最後修改tail指向,這樣未修改前tail始終指向原連結串列的最後一個節點。 ![image-20200228095048677](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/3.png) ![image-20200228095135301](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/4.png) **測試程式碼:** ``` //測試程式碼 //1.建立雙向連結串列 let list = new DoubleLinklist() //2.測試append方法 list.append('aaa') list.append('bbb') list.append('ccc') console.log(list); ``` **測試結果:** * next方向: ![image-20200305223911713](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/5.png) * prev方向: ![image-20200305224004626](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/6.png) #### 2.2.toString()彙總 **程式碼實現:** ``` //將連結串列轉變為字串形式 //一.toString方法 DoubleLinklist.prototype.toString = () => { return this.backwardString() } //二.forwardString方法 DoubleLinklist.prototype.forwardString = () => { //1.定義變數 let current =this.tail let resultString = "" //2.依次向前遍歷,獲取每一個節點 while (current) { resultString += current.data + "--" current = current.prev } return resultString } //三.backwardString方法 DoubleLinklist.prototype.backwardString = () => { //1.定義變數 let current = this.head let resultString = "" //2.依次向後遍歷,獲取每一個節點 while (current) { resultString += current.data + "--" current = current.next } return resultString } ``` **過程詳解:** 三種獲取字串的方法:**toString()**、**forwardString()**、**backwardString()**實現原理相似,僅以backWardString方法為例: * 定義current變數記錄當前指向的節點。首先讓current指向第一個節點,然後通過 current = current.next 依次向後遍歷。在while迴圈中以(current)作為條件遍歷連結串列,只要current != null就一直遍歷,由此可獲取連結串列所有節點的資料。 ![image-20200228100030713](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/7.png) **測試程式碼:** ``` //測試程式碼 //1.建立雙向連結串列 let list = new DoubleLinklist() //2.測試字串方法 list.append('aaa') list.append('bbb') list.append('ccc') console.log(list.toString()); console.log(list.forwardString()); console.log(list.backwardString()); ``` **測試結果:** ![image-20200305225437424](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/8.png) #### 2.3.insert(position,element) **程式碼實現:** ``` //insert方法 DoubleLinklist.prototype.insert = (position, data) => { //1.越界判斷 if (position < 0 || position > this.length) return false //2.根據data建立新的節點 let newNode = new Node(data) //3.插入新節點 //原連結串列為空 //情況1:插入的newNode是第一個節點 if (this.length == 0) { this.head = newNode this.tail = newNode //原連結串列不為空 }else { //情況2:position == 0 if (position == 0) { this.head.prev = newNode newNode.next = this.head this.head = newNode //情況3:position == this.length } else if(position == this.length){ this.tail.next = newNode newNode.prev = this.tail this.tail = newNode //情況4:0 < position < this.length }else{ let current = this.head let index = 0 while(index++ < position){ current = current.next } //修改pos位置前後節點變數的指向 newNode.next = current newNode.prev = current.prev current.prev.next = newNode current.prev = newNode } } //4.length+1 this.length += 1 return true//返回true表示插入成功 } ``` **過程詳解:** 插入節點可分為多種情況: **當原連結串列為空時**: * 情況1:插入的新節點是連結串列的第一個節點;只需要讓head和tail都指向newNode即可。 ![image-20200228102437899](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/9.png) **當原連結串列不為空時**: * 情況2:當position == 0,即在連結串列的首部新增節點:如下圖所示: ![image-20200228103942238](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/10.png) 首先,通過:this.head.prev = newNode,改變指向1; 然後,通過:newNode.next = this.head,改變指向2; 最後,通過:this.head = newNode,改變指向3; ![image-20200228110014565](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/11.png) * 情況3:position == this.length,即在連結串列的尾部新增節點,如下圖所示: ![image-20200228105207102](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/12.png) 首先,通過:this.tail.next = newNode,改變指向1;(注意這裡使用this.tail指向原連結串列最後一個節點,而不是this.head。因為當length>
1時,this.head != this.tail。) 然後,通過:newNode.prev = this.tail,改變指向2; 最後,通過:this.tail = newNode,改變指向3; ![image-20200228110745214](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/13.png) * 情況4:0 < position < this.length,即在連結串列的中間插入新節點,假設在position = 1的位置插入,如下圖所示: ![image-20200228112941682](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/14.png) 首先,需要定義變數current按照之前的思路,通過while迴圈找到position位置的後一個節點,迴圈結束後index = position ![image-20200228113257650](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/15.png) 如下圖所示:當position = 1時,current就指向了Node2。這樣操作current就等同於間接地操作Node2,還可以通過current.prev間接獲取Node1。得到了newNode的前一個節點和後一個節點就可以通過改變它們的prev和next變數的指向來插入newNode了。 ![image-20200228120701923](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/16.png) 通過:newNode.next = current,改變指向1; 通過:newNode.prev = current.prev,改變指向2; 通過:current.prev.next = newNode,改變指向3; > 注意必須最後才修改current.prev的指向,不然就無法通過current.prev獲取需要操作的Node1了。 通過:current.prev = current,改變指向4; ![image-20200228124931441](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/17.png) **測試程式碼:** ``` //測試程式碼 //1.建立雙向連結串列 let list = new DoubleLinklist() //2.測試insert方法 list.insert(0, '插入連結串列的第一個元素') list.insert(0, '在連結串列首部插入元素') list.insert(1, '在連結串列中間插入元素') list.insert(3, '在連結串列尾部插入元素') console.log(list); alert(list) ``` **測試結果:** ![image-20200228130649724](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/18.png) ![image-20200228130748735](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/19.png) #### 2.4.get(position) **程式碼實現:** ``` //get方法 DoubleLinklist.prototype.get = position =>
{ //1.越界判斷 if (position < 0 || position >= this.length) {//獲取元素時position不能等於length return null } //2.獲取元素 let current = null let index = 0 //this.length / 2 > position:從頭開始遍歷 if ((this.length / 2) > position) { current = this.head while(index++ < position){ current = current.next } //this.length / 2 =< position:從尾開始遍歷 }else{ current = this.tail index = this.length - 1 while(index-- > position){ current = current.prev } } return current.data } ``` **過程詳解:** 定義兩個變數current和index,按照之前的思路通過while迴圈遍歷分別獲取當前節點和對應的索引值index,直到找到需要獲取的position位置後的一個節點,此時index = pos =x,然後return current.data即可。 如果連結串列的節點數量很多時,這種查詢方式效率不高,改進方法為: > 一定要通過this.length來獲取連結串列的節點數否則就會報錯。 * 當this.length / 2 > position:從頭(head)開始遍歷; * 當this.length / 2 < position:從尾(tail)開始遍歷; ![image-20200228144005347](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/20.png) **測試程式碼:** ``` //測試程式碼 //1.建立雙向連結串列 let list = new DoubleLinklist() //2.測試get方法 list.append('a') list.append('b') list.append('b1') list.append('b2') list.append('b3') list.append('b4') list.append('b5') list.append('b6') list.append('b7') console.log(list.get(0)); console.log(list.get(7)); ``` **測試結果:** ![image-20200228145413524](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/21.png) #### 2.5.indexOf(element) **程式碼實現:** ``` //indexOf方法 DoubleLinklist.prototype.indexOf = data =>
{ //1.定義變數 let current = this.head let index = 0 //2.遍歷連結串列,查詢與data相同的節點 while(current){ if (current.data == data) { return index } current = current.next index += 1 } return -1 } ``` **過程詳解:** 以(current)作為條件,通過while迴圈遍歷連結串列中的所有節點(停止條件為current = null)。在遍歷每個節點時將current指向的當前節點的data和傳入的data進行比較即可。 ![image-20200228150427485](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/22.png) **測試程式碼:** ``` //測試程式碼 //1.建立雙向連結串列 let list = new DoubleLinklist() //2.測試indexOf方法 list.append('a') list.append('b') list.append('c') console.log(list.indexOf('a')); console.log(list.indexOf('c')); ``` **測試結果:** ![image-20200228150612681](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/23.png) #### 2.7.update(position,element) **程式碼實現:** ``` //update方法 DoubleLinklist.prototype.update = (position, newData) => { //1.越界判斷 if (position < 0 || position >= this.length) { return false } //2.尋找正確的節點 let current = this.head let index = 0 //this.length / 2 > position:從頭開始遍歷 if (this.length / 2 > position) { while(index++ < position){ current = current.next } //this.length / 2 =< position:從尾開始遍歷 }else{ current = this.tail index = this.length - 1 while (index -- > position) { current = current.prev } } //3.修改找到節點的data current.data = newData return true//表示成功修改 } ``` **過程詳解:** 以(index++ < position)為條件,通過while迴圈遍歷連結串列中的節點(停止條件為index = position)。迴圈結束後,current指向需要修改的節點。 ![image-20200228152136284](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/24.png) **測試程式碼:** ``` //測試程式碼 //1.建立雙向連結串列 let list = new DoubleLinklist() //2.測試update方法 list.append('a') list.append('b') console.log(list.update(1, 'c')); console.log(list); ``` **測試結果:** ![image-20200228151340638](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/25.png) #### 2.8.removeAt(position) **程式碼實現:** ``` //removeAt方法 DoubleLinklist.prototype.removeAt = position => { //1.越界判斷 if (position < 0 || position >= this.length) { return null } //2.刪除節點 //當連結串列中length == 1 //情況1:連結串列只有一個節點 let current = this.head//定義在最上面方便以下各種情況返回current.data if (this.length == 1) { this.head = null this.tail = null //當連結串列中length > 1 } else{ //情況2:刪除第一個節點 if (position == 0) { this.head.next.prev = null this.head = this.head.next //情況3:刪除最後一個節點 }else if(position == this.length - 1){ current = this.tail//該情況下返回被刪除的最後一個節點 this.tail.prev.next = null this.tail = this.tail.prev }else{ //情況4:刪除連結串列中間的節點 let index = 0 while(index++ < position){ current = current.next } current.next.prev = current.prev current.prev.next = current.next } } //3.length -= 1 this.length -= 1 return current.data//返回被刪除節點的資料 } ``` **過程詳解:** 刪除節點時有多種情況: **當連結串列的length = 1時**: * 情況1:刪除連結串列中的所有節點:只需要讓連結串列的head和tail指向null即可。 ![image-20200228153331976](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/26.png) **當連結串列的length > 1時**: * 情況2:刪除連結串列中的第一個節點: 通過:this.head.next.prev = null,改變指向1; 通過:this.head = this.head.next,改變指向2; 雖然Node1有引用指向其它節點,但是沒有引用指向Node1,那麼Node1會被自動回收。 ![image-20200228162347115](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/27.png) * 情況3:刪除連結串列中的最後一個節點: 通過:this.tail.prev.next = null,修改指向1; 通過:this.tail = this.tail.prev,修改指向2; ![image-20200228161946691](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/28.png) * 情況4:刪除連結串列中間的節點: 通過while迴圈找到需要刪除的節點,比如position = x,那麼需要刪除的節點就是Node(x+1),如下圖所示: ![image-20200228161648125](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/29.png) 通過:current.next.prev = current.prev,修改指向1; 通過:current.prev.next = current.next,修改指向2; 這樣就沒有引用指向Node(x+1)了(current雖指向Node(x+1),但current時臨時變數,該方法執行完就會被銷燬),隨後Node(x+1)就會被自動刪除。 ![image-20200228162415044](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/30.png) **測試程式碼:** ``` //測試程式碼 //1.建立雙向連結串列 let list = new DoubleLinklist() //2.測試removeAt方法 list.append('a') list.append('b') list.append('c') console.log(list.removeAt(1)); console.log(list); ``` **測試結果:** ![image-20200228163935060](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/31.png) #### 2.9.其他方法 其他方法包括:**remove(element)、isEmpty()、size()、getHead()、getTail()** **程式碼實現:** ``` /*--------------------其他方法-------------------*/ //八.remove方法 DoubleLinklist.prototype.remove = data => { //1.根據data獲取下標值 let index = this.indexOf(data) //2.根據index刪除對應位置的節點 return this.removeAt(index) } //九.isEmpty方法 DoubleLinklist.prototype.isEmpty = () => { return this.length == 0 } //十.size方法 DoubleLinklist.prototype.size = () => { return this.length } //十一.getHead方法:獲取連結串列的第一個元素 DoubleLinklist.prototype.getHead = () => { return this.head.data } //十二.getTail方法:獲取連結串列的最後一個元素 DoubleLinklist.prototype.getTail = () => { return this.tail.data } ``` **測試程式碼:** ``` //測試程式碼 //1.建立雙向連結串列 let list = new DoubleLinklist() /*------------其他方法的測試--------------*/ list.append('a') list.append('b') list.append('c') list.append('d') //remove方法 console.log(list.remove('a')); console.log(list); //isEmpty方法 console.log(list.isEmpty()); //size方法 console.log(list.size()); //getHead方法 console.log(list.getHead()); //getTead方法 console.log(list.getTail()); ``` **測試結果:** ![image-20200228165845014](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/32.png) #### 2.10.完整實現 //封裝雙向連結串列 function DoubleLinklist(){ //封裝內部類:節點類 function Node(data){ this.data = data this.prev = null this.next = null } //屬性 this.head = null this.tail ==null this.length = 0 //常見的操作:方法 //一.append方法 DoubleLinklist.prototype.append = data => { //1.根據data建立新節點 let newNode = new Node(data) //2.新增節點 //情況1:新增的是第一個節點 if (this.length == 0) { this.tail = newNode this.head = newNode //情況2:新增的不是第一個節點 }else { newNode.prev = this.tail this.tail.next = newNode this.tail = newNode } //3.length+1 this.length += 1 } //二.將連結串列轉變為字串形式 //2.1.toString方法 DoubleLinklist.prototype.toString = () => { return this.backwardString() } //2.2.forwardString方法 DoubleLinklist.prototype.forwardString = () => { //1.定義變數 let current =this.tail let resultString = "" //2.依次向前遍歷,獲取每一個節點 while (current) { resultString += current.data + "--" current = current.prev } return resultString } //2.3.backwardString方法 DoubleLinklist.prototype.backwardString = () => { //1.定義變數 let current = this.head let resultString = "" //2.依次向後遍歷,獲取每一個節點 while (current) { resultString += current.data + "--" current = current.next } return resultString } //三.insert方法 DoubleLinklist.prototype.insert = (position, data) => { //1.越界判斷 if (position < 0 || position > this.length) return false //2.根據data建立新的節點 let newNode = new Node(data) //3.插入新節點 //原連結串列為空 //情況1:插入的newNode是第一個節點 if (this.length == 0) { this.head = newNode this.tail = newNode //原連結串列不為空 }else { //情況2:position == 0 if (position == 0) { this.head.prev = newNode newNode.next = this.head this.head = newNode //情況3:position == this.length } else if(position == this.length){ this.tail.next = newNode newNode.prev = this.tail this.tail = newNode //情況4:0 < position < this.length }else{ let current = this.head let index = 0 while(index++ < position){ current = current.next } //修改pos位置前後節點變數的指向 newNode.next = current newNode.prev = current.prev current.prev.next = newNode current.prev = newNode } } //4.length+1 this.length += 1 return true//返回true表示插入成功 } //四.get方法 DoubleLinklist.prototype.get = position => { //1.越界判斷 if (position < 0 || position >= this.length) {//獲取元素時position不能等於length return null } //2.獲取元素 let current = null let index = 0 //this.length / 2 > position:從頭開始遍歷 if ((this.length / 2) > position) { current = this.head while(index++ < position){ current = current.next } //this.length / 2 =< position:從尾開始遍歷 }else{ current = this.tail index = this.length - 1 while(index-- > position){ current = current.prev } } return current.data } //五.indexOf方法 DoubleLinklist.prototype.indexOf = data => { //1.定義變數 let current = this.head let index = 0 //2.遍歷連結串列,查詢與data相同的節點 while(current){ if (current.data == data) { return index } current = current.next index += 1 } return -1 } //六.update方法 DoubleLinklist.prototype.update = (position, newData) => { //1.越界判斷 if (position < 0 || position >= this.length) { return false } //2.尋找正確的節點 let current = this.head let index = 0 //this.length / 2 > position:從頭開始遍歷 if (this.length / 2 > position) { while(index++ < position){ current = current.next } //this.length / 2 =< position:從尾開始遍歷 }else{ current = this.tail index = this.length - 1 while (index -- > position) { current = current.prev } } //3.修改找到節點的data current.data = newData return true//表示成功修改 } //七.removeAt方法 DoubleLinklist.prototype.removeAt = position => { //1.越界判斷 if (position < 0 || position >= this.length) { return null } //2.刪除節點 //當連結串列中length == 1 //情況1:連結串列只有一個節點 let current = this.head//定義在最上面方便以下各種情況返回current.data if (this.length == 1) { this.head = null this.tail = null //當連結串列中length > 1 } else{ //情況2:刪除第一個節點 if (position == 0) { this.head.next.prev = null this.head = this.head.next //情況3:刪除最後一個節點 }else if(position == this.length - 1){ current = this.tail//該情況下返回被刪除的最後一個節點 this.tail.prev.next = null this.tail = this.tail.prev }else{ //情況4:刪除連結串列中間的節點 let index = 0 while(index++ < position){ current = current.next } current.next.prev = current.prev current.prev.next = current.next } } //3.length -= 1 this.length -= 1 return current.data//返回被刪除節點的資料 } /*--------------------其他方法-------------------*/ //八.remove方法 DoubleLinklist.prototype.remove = data => { //1.根據data獲取下標值 let index = this.indexOf(data) //2.根據index刪除對應位置的節點 return this.removeAt(index) } //九.isEmpty方法 DoubleLinklist.prototype.isEmpty = () => { return this.length == 0 } //十.size方法 DoubleLinklist.prototype.size = () => { return this.length } //十一.getHead方法:獲取連結串列的第一個元素 DoubleLinklist.prototype.getHead = () => { return this.head.data } //十二.getTail方法:獲取連結串列的最後一個元素 DoubleLinklist.prototype.getTail = () => { return this.tail.data } } ### 三、連結串列結構總結 單向連結串列有head和next兩個屬性,雙向連結串列有head、tail、next、prev四個屬性。處理好它們的指向,相當於將它們正確地連線在一起,這樣就組成了一條鏈,這就是簡單鏈表的實現。 在實際開發中連結串列使用得非常多,比如Java中的**LinkList**就是雙向連結串列。 #### 3.1.注意點 * 在連結串列中current = current.next 可以從左往右看,看成是current --> current.next,即current指向current的下一個節點。 * 刪除節點的原理:只要沒有引用指向該物件,無論該物件是否有引用指向其他物件,該物件都會被回收(刪除)。 * 引數中凡是有position的都要進行越界判斷。 #### 3.2.連結串列的增刪改查 以雙向連結串列為例:**連結串列的增刪改查無非就是獲取連結串列中相應的節點改變其中的prev和next兩個變數的指向**。 * **情況一**:只需要**head**和**tail**兩個變數就可以獲取需要操作的變數(這裡指的是能夠輕鬆獲取,當然你想通過head.next.next...或tail.prev.prev...來獲取想要的節點也可以),在這種情況下連結串列的長度length:**0 <= length <=2**。 * **情況二**:不能靠tail和head來獲取到需要操作的變數時,可採用while迴圈遍歷的方式,找到需要操作的節點: ![image-20200228113257650](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/33.png) 在這種情況下,如果我們想要在連結串列的position = x的位置插入新節點,那麼可以通過current獲取position的後一個節點Node(x+1),通過current.prev獲取position位置的前一個節點Node(x);之後修改Node(x+1)和Node(x)中的prev和next兩個變數的指向即可在pos=x 的位置插入新節點。 ![image-20200228133450822](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/34.png) #### 3.3.修改連結串列引用指向 **應先修改newNode引用的指向,再修改其他引用** * 情況1:通過head和tail引用就能獲取需要操作的節點時,最後更改head或tail變數的指向(因為它們分別指向連結串列的第一個和最後一個節點,獲取其他節點時可能需要用到它們)。 * 情況2:使用current獲取到需要操作的節點時,最後更改curren.next或current.prev的指向。因為current.next和current.prev表示的是Node(x+2)和Node(x)這兩個節點,如下圖所示,一旦變更它們的指向就無法獲取Node(x)或Node(x+2)了, ![image-20200228133725909](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/35.png) #### 3.4.遍歷連結串列 **積累兩種遍歷思路** * 獲取指定的position = x 位置的後一個節點和索引值: ![image-20200228144005347](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/36.png) ![image-20200228113257650](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/37.png) 迴圈結束後index = position = x,變數current就指向了Node(x+1),變數index的值為Node(x+1)的索引值x。 * 遍歷連結串列中的所有節點: ![image-20200228132334514](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/38.png) ![image-20200228145930043](https://gitee.com/ahuntsun/BlogImgs/raw/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8/39.png) 當current.next = null時停止迴圈,此時current指向連結串列的最後一個節點。 > 參考資料:[JavaScript資料結構與演算法](https://www.bilibili.com/video/av86801505?from=search&seid=496776141191