精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

「算法與數據結構」JavaScript中的鏈表

開發 前端 算法
此文會先探討下什么是鏈表以及在 JavaScript 中的鏈表,接著我們會使用 JavaScript 這門語言動手實現下各類鏈表的設計,最后我們會拋出一些常規疑問,并從各個方面一一解答,總之,目的就是完全搞定鏈表。

[[378875]]

 本文轉載自微信公眾號「不正經的前端」,作者 isboyjc   。轉載本文請聯系不正經的前端公眾號。

寫在前面

此文會先探討下什么是鏈表以及在 JavaScript 中的鏈表,接著我們會使用 JavaScript 這門語言動手實現下各類鏈表的設計,最后我們會拋出一些常規疑問,并從各個方面一一解答,總之,目的就是完全搞定鏈表

搞定概念之后我們可以去力扣上選擇鏈表分類,按照難易程度把它們刷完,其實力扣上鏈表的題目相對簡單,只要你完整的看完了此文的鏈表設計,最起碼可以輕松淦掉20題,同時鏈表題目數量也比較少,一共也就有50題左右,還有十來題需要會員,也就是說刷個40題,鏈表這種數據結構就可以初步掌握了,如果你不想找題排序,可以按照我的 GitHub 算法倉庫庫中的順序刷題,有不太懂的題目或者概念可以看我寫的題解,同時我也錄制了視頻,文末有鏈接,那么我們來開始學習鏈表,GO!

什么是鏈表

通常我們在程序中想要存儲多個元素,數組可能是最常用的數據結構,數組這種數據結構非常方便,它甚至可以通過非常簡單的方式即 [] 這種語法來訪問其元素

而鏈表存儲的也是有序的元素集合,但不同于數組的是,鏈表中的元素在內存中并不是連續的,每個元素由一個存儲元素本身的節點和一個指向下一個元素的引用(也可以稱為指針)組成

 

我們接著再來看數組這種數據結構,它有一個缺點,在大多數語言中數組的大小是固定的,從數組的起點或中間插入或移除項的成本很高,因為需要移動元素,如下圖

 

上圖數組刪除索引為 2 值為 3 的元素,那么我們首先要刪掉 3 這個元素,因為索引為 2 值為 3 的元素刪除了,索引 2 就空了,所以接著,我們要把索引 3 也就是元素 4 向前移動一位,與此同時后面的元素 5 也需要向前移動一位,向數組中插入一個元素也是這個道理,只要數組中少了一位或者多了一位,那么后面的元素都要依次向前或向后移動一位,那么可想而之,當數組長度很大的時候,插入及刪除的效率就會逐漸降低

我們再來看看鏈表

 

同樣是刪除元素 3,鏈表這里只需要迭代到值為 3 的節點,將節點 2 指向節點 4 就行了,節點 3 沒有了引用關系,就會被垃圾回收機制當作垃圾回收了,即使當節點非常多的情況下,依然只用改變一下引用關系即可刪除元素,而插入元素則是反過來,即先斷開插入位置兩邊的元素,然后讓前一個元素指向插入元素,插入元素指向后一個元素即可,元素越多對比數組的效率就會越高

相對于傳統的數組,鏈表的一個好處就在于,添加或移除元素的時候不需要移動其他元素,但是在數組中,我們可以直接訪問任何位置的任何元素,鏈表中是不行的,因為鏈表中每個節點只有對下一個節點的引用,所以想訪問鏈表中間的一個元素,必須要從起點(鏈表頭部節點)開始迭代鏈表直到找到所需的元素,這點需要注意

JavaScript中的鏈表

上面我們簡單介紹了常規鏈表的概念,但是在 JavaScript 這門語言中,我們怎么表示鏈表呢?

由于 JS 中沒有內置鏈表這種數據結構,所以我們需要使用對象來模擬實現鏈表,就如同我們上面介紹鏈表,它其實是一個單向鏈表,除此之外還有雙向鏈表、環形鏈表等等,我們接下來會一一介紹并使用 JavaScript 來實現下

單向鏈表

我們先來看基礎的單項鏈表,單向鏈表每個元素由一個存儲元素本身的節點和一個指向下一個元素的指針構成,如下圖

 

要實現鏈表這種數據結構,關鍵在于保存 head 元素(即鏈表的頭元素)以及每一個元素的 next 指針,有這兩部分我們就可以很方便地遍歷鏈表從而操作所有的元素,你可以把鏈表想象成一條鐵鏈,鐵鏈中的每一個節點都是相互連接的,我們只要找到鐵鏈的頭,整條鐵鏈就都可以找到了,那么單向鏈表在 JS 中究竟要如何來模擬呢,我們一步一步來

首先,我們要創建一個類,這個類的作用就是描述鏈表的節點,它很簡單,只需要有兩個屬性就可以了,一個用來保存此節點的值,一個用來保存指向下一個節點的指針,如下

  1. /** 
  2.  * @description: 創建鏈表單節點類 
  3.  * @param {*} val 節點值 
  4.  * @return {*} 
  5.  */ 
  6. function ListNode(val) { 
  7.   this.val = val 
  8.   this.next = null 

接著,我們需要先寫一個鏈表類,其中 length屬性 代表鏈表長度,head屬性 代表鏈表頭部節點

  1. /** 
  2.  * @description: 創建鏈表類 
  3.  * @param {*} 
  4.  * @return {*} 
  5.  */ 
  6. function LinkedList() { 
  7.   this.length = 0 
  8.   this.head = null 

我們思考下,既然是來模擬一個鏈表類,那么就應該把它所有可能會用到的特性都塞進這個類里,就比如數組有 push/splice/indexOf/... 等等這些好用的方法我們鏈表必須也得有啊,我們先仔細構思下要給鏈表添加哪些實用的特性或者說方法,先搭一個基礎骨架,這里我列出了很多,我們來一一實現下,也歡迎補充

  1. // 向鏈表中追加節點 
  2. LinkedList.prototype.append = function (val) { } 
  3.  
  4. // 在鏈表的指定位置插入節點 
  5. LinkedList.prototype.insert = function (index, val) { } 
  6.  
  7. // 刪除鏈表中指定位置的元素,并返回這個元素的值 
  8. LinkedList.prototype.removeAt = function (index) { } 
  9.  
  10. // 刪除鏈表中對應的元素 
  11. LinkedList.prototype.remove = function (val) { } 
  12.  
  13. // 獲取鏈表中給定元素的索引 
  14. LinkedList.prototype.indexOf = function (val) { } 
  15.  
  16. // 獲取鏈表中某個節點 
  17. LinkedList.prototype.find = function (val) { } 
  18.  
  19. // 獲取鏈表中索引所對應的元素 
  20. LinkedList.prototype.getElementAt = function (index) { } 
  21.  
  22. // 判斷鏈表是否為空 
  23. LinkedList.prototype.isEmpty = function () { } 
  24.  
  25. // 獲取鏈表的長度 
  26. LinkedList.prototype.size = function () { } 
  27.  
  28. // 獲取鏈表的頭元素 
  29. LinkedList.prototype.getHead = function () { } 
  30.  
  31. // 清空鏈表 
  32. LinkedList.prototype.clear = function () { } 
  33.  
  34. // 序列化鏈表 
  35. LinkedList.prototype.join = function (string) { } 

getElementAt(index)

我們先來實現獲取鏈表中索引所對應的元素即 getElementAt 方法以及通過節點值獲取鏈表元素即 find 方法,因為后面要多次用到它們,我們先說 getElementAt 方法,上面我們說想要找一個元素,我們必須從頭迭代,所以我們直接根據傳入的索引進行迭代即可

首先判斷參數 index 的邊界值,如果值超出了索引的范圍(小于 0 或者大于 length - 1),則返回null,我們從鏈表的 head 節點開始,遍歷整個鏈表直到找到對應索引位置的節點,然后返回這個節點,是不是很簡單?和所有有序數據集合一樣,鏈表的索引也是從 0 開始,只要有鏈表的頭節點,就可以遍歷找到索引所在位置的元素,所以我們在構造函數即 LinkedList 類中保存了 head 值

  1. // 獲取鏈表中索引所對應的元素 
  2. LinkedList.prototype.getElementAt = function (index) { 
  3.   if (index < 0 || index >= this.length) return null 
  4.  
  5.   let cur = this.head 
  6.   while (index--) { 
  7.     cur = cur.next 
  8.   } 
  9.   return cur 

find(val)

find 方法和 getElementAt 方法類似,一個通過索引找元素,一個通過節點值找元素,所以我們直接迭代查找對比即可

  1. // 獲取鏈表中某個節點 
  2. LinkedList.prototype.find = function (val) { 
  3.   let cur = this.head 
  4.   while (cur) { 
  5.     if (cur.val == val) return cur 
  6.     cur = cur.next 
  7.   } 
  8.   return null 

append(val)

有了 getElementAt 方法后,接下來我們就可以很方便地實現 append 方法,此方法的作用是在鏈表末尾追加元素

此方法傳入的是一個值,我們可以通過上面的構造函數 ListNode 來創建一個新節點

而后,我們需要考慮,如果鏈表的 head 為 null 時,這種情況表示鏈表為空,所以需要將 head 節點指向新添加的元素,以此來確保存儲頭節點,如果不為空,我們通過getElementAt 方法找到鏈表的最后一個節點,最后一個節點的索引就是構造函數中的我們存的鏈表長度 length 屬性減去 1,再將最后一個節點的 next 指針指向新添加的元素即可

新添加的元素 next 指針默認為 null,鏈表最后一個元素的 next 值也就為 null,另外,將節點掛到鏈表上之后,還需將鏈表的長度加 1,保證 length 屬性等于鏈表長度,如下

  1. // 向鏈表中追加節點 
  2. LinkedList.prototype.append = function (val) { 
  3.   let node = new ListNode(val) 
  4.  
  5.   if (!this.head) { 
  6.     this.head = node 
  7.   } else { 
  8.     let cur = this.getElementAt(this.length - 1) 
  9.     cur.next = node 
  10.   } 
  11.   this.length++ 

insert(index, val)

接下來我們要實現 insert 方法,即在鏈表的任意位置添加節點

在指定位置插入元素,首先我們還是需要先判斷下傳入 index 索引是否超出邊界

接著我們分兩種情況考慮

當 index 的值為 0 時,表示要在鏈表的頭部插入新節點,將新插入節點的 next 指針指向現在的 head,然后更新 head 的值為新插入的節點即可,如下圖

當 index 的值不為 0 時,即插入的節點在鏈表的中間或者尾部,我們首先找到待插入位置的前一個節點 prevNode,然后將新節點 newNode 的 next 指針指向 prevNode 的 next 所對應的節點,再將 prevNode 的 next 指針指向 newNode,這樣就把新節點插入鏈表中了,當插入的節點在鏈表的尾部,這種方法也同樣適用,如下圖 

最后,我們插入了節點,還需要將鏈表的長度即 length 長度加 1,代碼如下

  1. // 在鏈表的指定位置插入節點 
  2. LinkedList.prototype.insert = function (index, val) { 
  3.   if (index < 0 || index > this.length) return false 
  4.  
  5.   let node = new ListNode(val) 
  6.  
  7.   if (index === 0) { 
  8.     node.next = this.head 
  9.     this.head = node 
  10.   } else { 
  11.     let prev = this.getElementAt(index - 1) 
  12.     node.next = prev.next 
  13.     prev.next = node 
  14.   } 
  15.  
  16.   this.length++ 
  17.   return true 

removeAt(index)

相同的方式,我們可以很容易地寫出 removeAt 方法,用來刪除鏈表中指定位置的節點

依然還是先判斷下傳入 index 索引是否超出邊界

還是分兩種情況

如果要刪除的節點是鏈表的頭部,將 head 移到下一個節點即可,如果當前鏈表只有一個節點,那么下一個節點為 null,此時將 head 指向下一個節點等同于將 head 設置成 null,刪除之后鏈表為空

如果要刪除的節點在鏈表的中間部分,我們需要找出 index 所在位置的前一個節點,將它的 next 指針指向 index 所在位置的下一個節點,總之,刪除節點只需要修改相應節點的指針,斷開刪除位置左右相鄰的節點再重新連接上即可

image-20201227180444604

 被刪除的節點沒有了引用關系,JavaScript 垃圾回收機制會處理它,關于垃圾回收機制,同樣不在此文討論范圍內,知道即可,刪除節點元素,我們還需將鏈表的長度減 1,最終代碼如下

  1. // 刪除鏈表中指定位置的元素,并返回這個元素的值 
  2. LinkedList.prototype.removeAt = function (index) { 
  3.   if (index < 0 || index >= this.length) return null 
  4.  
  5.   let cur = this.head 
  6.  
  7.   if (index === 0) { 
  8.     this.head = cur.next 
  9.   } else { 
  10.     let prev = this.getElementAt(index - 1) 
  11.     cur = prev.next 
  12.     prev.next = cur.next 
  13.   } 
  14.  
  15.   this.length-- 
  16.   return cur.val 

indexOf(val)

獲取鏈表中給定元素的索引,這個比較簡單,直接迭代即可,匹配到了返回對應索引,匹配不到返回 -1

  1. // 獲取鏈表中給定元素的索引 
  2. LinkedList.prototype.indexOf = function (val) { 
  3.   let cur = this.head 
  4.  
  5.   for (let i = 0; i < this.length; i++) { 
  6.     if (cur.val === val) return i 
  7.     cur = cur.next 
  8.   } 
  9.  
  10.   return -1 

remove(val)

刪除鏈表中對應的元素,有了之前的鋪墊,這里就比較簡單了,我們可以直接用 indexOf 方法拿到對應索引,再使用 removeAt 方法刪除節點即可

  1. // 刪除鏈表中對應的元素 
  2. LinkedList.prototype.remove = function (val) { 
  3.   let index = this.indexOf(val) 
  4.   return this.removeAt(index

isEmpty()

判斷鏈表是否為空,只需要我們判斷一下鏈表長度 length 等不等于 0 即可

  1. // 判斷鏈表是否為空 
  2. LinkedList.prototype.isEmpty = function () { 
  3.   return !this.length 

size()

獲取鏈表長度就是取其 length

  1. // 獲取鏈表的長度 
  2. LinkedList.prototype.size = function () { 
  3.   return this.length 

getHead()

獲取鏈表的頭元素即返回 head 屬性即可

  1. // 獲取鏈表的頭元素 
  2. LinkedList.prototype.getHead = function () { 
  3.   return this.head 

clear()

清空鏈表,我們只需要將 head 置空,然后讓 length 等于 0,等待垃圾回收機制回收無引用的廢棄鏈表即可

  1. // 清空鏈表 
  2. LinkedList.prototype.clear = function () { 
  3.   this.head = null 
  4.   this.length = 0 

join(string)

序列化鏈表即使用指定格式輸出鏈表,類似于數組中 join 方法,此舉旨在便于我們測試

  1. // 序列化鏈表 
  2. LinkedList.prototype.join = function (string) { 
  3.   let cur = this.head 
  4.   let str = '' 
  5.   while (cur) { 
  6.     str += cur.val 
  7.  
  8.     if (cur.next) str += string 
  9.  
  10.     cur = cur.next 
  11.   } 
  12.   return str 

那么到此,我們的單向鏈表類就設計完成了,快來測試一下吧,我們輸入下面代碼進行測試

  1. let linkedList = new LinkedList() 
  2. linkedList.append(10) 
  3. linkedList.append(20) 
  4. linkedList.append(30) 
  5.  
  6. console.log(linkedList.join("--")) 
  7.  
  8. linkedList.insert(0, 5) 
  9. linkedList.insert(2, 15) 
  10. linkedList.insert(4, 25) 
  11. console.log(linkedList.join("--")) 
  12.  
  13. console.log(linkedList.removeAt(0)) 
  14. console.log(linkedList.removeAt(1)) 
  15. console.log(linkedList.removeAt(2)) 
  16. console.log(linkedList.join("--")) 
  17.  
  18. console.log(linkedList.indexOf(20)) 
  19.  
  20. linkedList.remove(20) 
  21.  
  22. console.log(linkedList.join("--")) 
  23.  
  24. console.log(linkedList.find(10)) 
  25.  
  26. linkedList.clear() 
  27. console.log(linkedList.size()) 

最終輸出如下

  1. // 10--20--30 
  2. // 5--10--15--20--25--30 
  3. // 5 
  4. // 15 
  5. // 25 
  6. // 10--20--30 
  7. // 1 
  8. // 10--30 
  9. // ListNode { val: 10, next: ListNode { val: 30, nextnull } } 
  10. // 0 

上面代碼中少了一些參數校驗,不過夠我們學習用了,完成代碼文末附鏈接

雙向鏈表

上面我們說了單向鏈表,接下來我們來說雙向鏈表,那么什么是雙向鏈表呢?其實聽名字就可以聽出來,單向鏈表中每一個元素只有一個 next 指針,用來指向下一個節點,我們只能從鏈表的頭部開始遍歷整個鏈表,任何一個節點只能找到它的下一個節點,而不能找到它的上一個節點,雙向鏈表中的每一個元素擁有兩個指針,一個用來指向下一個節點,一個用來指向上一個節點,雙向鏈表中,除了可以像單向鏈表一樣從頭部開始遍歷之外,還可以從尾部進行遍歷,如下圖

同單向鏈表,我們首先創建鏈表節點類,不同的是,它需要多一個 prev 屬性用來指向前一個節點

  1. /** 
  2.  * @description: 創建雙向鏈表單節點類 
  3.  * @param {*} val 節點值 
  4.  * @return {*} 
  5.  */ 
  6. function ListNode(val) { 
  7.   this.val = val 
  8.   this.next = null 
  9.   this.prev = null 

雙向鏈表類同單向鏈表多增加了一個尾部節點 tail

  1. /** 
  2.  * @description: 創建雙向鏈表類 
  3.  * @param {*} 
  4.  * @return {*} 
  5.  */ 
  6. function DoubleLinkedList() { 
  7.   this.length = 0 
  8.   this.head = null 
  9.   this.tail = null 

接下來我們來實現雙向鏈表的原型方法

getElementAt(index)

首先就是,獲取雙向鏈表中索引所對應的元素,雙向鏈表由于可以雙向進行迭代查找,所以這里 getElementAt 方法我們可以進行優化,當索引大于鏈表長度 length/2 時,我們可以從后往前找,反之則從前向后找,這樣可以更快找到該節點元素

  1. // 獲取雙向鏈表中索引所對應的元素 
  2. DoubleLinkedList.prototype.getElementAt = function (index) { 
  3.   if (index < 0 || index >= this.length) return null 
  4.   
  5.   let cur = null 
  6.   if(index > Math.floor(this.length / 2)){ 
  7.     // 從后往前 
  8.     cur = this.tail 
  9.     let i = this.length - 1 
  10.     while (i > index) { 
  11.       cur = cur.prev 
  12.       i-- 
  13.     } 
  14.   }else
  15.     // 從前往后 
  16.     cur = this.head 
  17.     while (index--) { 
  18.       cur = cur.next 
  19.     } 
  20.   } 
  21.   return cur 

find(val)

find 方法和 getElementAt 方法是類似的,getElementAt 方法可以優化,那么find 再變成雙向鏈表后也可優化,我們想,既然雙向都可以進行迭代,那么我們兩邊同時迭代豈不是更快,雙向迭代的情況下,只有找不到時才會迭代整個鏈表,效率更高

  1. // 獲取雙向鏈表中某個節點 
  2. DoubleLinkedList.prototype.find = function (val) { 
  3.  let curHead = this.head 
  4.   let curTail = this.tail 
  5.   while (curHead) { 
  6.     if (curHead.val == val) return curHead 
  7.     curHead = curHead.next 
  8.  
  9.     if (curTail.val == val) return curTail 
  10.     curTail = curTail.prev 
  11.   } 
  12.   return null 

append(val)

又來到了我們的追加節點元素,雙向鏈表追加與單向鏈表還是有些區別的

當鏈表為空時,除了要將 head 指向當前添加的節點外,還要將 tail 也指向當前要添加的節點

當鏈表不為空時,直接將 tail 的 next 指向當前要添加的節點 node,然后修改node 的 prev 指向舊的 tail,最后修改 tail 為新添加的節點

雙向鏈表的追加操作我們不需要從頭開始遍歷整個鏈表,通過 tail 可以直接找到鏈表的尾部,這一點比單向鏈表的操作更方便,最后將 length 值加 1,修改鏈表的長度即可

  1. // 向雙向鏈表中追加節點 
  2. DoubleLinkedList.prototype.append = function (val) { 
  3.   let node = new ListNode(val) 
  4.  
  5.   if (this.head === null) { 
  6.     // 鏈表為空,head 和 tail 都指向當前添加的節點 
  7.     this.head = node 
  8.     this.tail = node 
  9.   } 
  10.   else { 
  11.     // 鏈表不為空,將當前節點添加到鏈表的尾部 
  12.     this.tail.next = node 
  13.     node.prev = this.tail 
  14.     this.tail = node 
  15.   } 
  16.  
  17.   this.length++ 

insert(index, val)

接著是插入節點元素方法,同樣思路一致,并不困難,我們注意 tail 及 prev 指針分情況討論,插入后長度加 1 即可

  1. // 在雙向鏈表的指定位置插入節點 
  2. DoubleLinkedList.prototype.insert = function (index, val) { 
  3.   if (index < 0 || index > this.length) return false 
  4.  
  5.   // 插入到尾部 
  6.   if (index === this.length) { 
  7.     this.append(val) 
  8.   } else { 
  9.     let node = new ListNode(val) 
  10.  
  11.     if (index === 0) { // 插入到頭部 
  12.       if (this.head === null) { 
  13.         this.head = node 
  14.         this.tail = node 
  15.       } else { 
  16.         node.next = this.head 
  17.         this.head.prev = node 
  18.         this.head = node 
  19.       } 
  20.     } else { // 插入到中間位置 
  21.       let curNode = this.getElementAt(index
  22.       let prevNode = curNode.prev 
  23.       node.next = curNode 
  24.       node.prev = prevNode 
  25.       prevNode.next = node 
  26.       curNode.prev = node 
  27.     } 
  28.     this.length++ 
  29.   } 
  30.   return true 

removeAt(index)

刪除雙向鏈表中指定位置的元素,同樣是注意 tail 及 prev 指針分情況討論,最后刪除后長度減 1 即可

  1. // 刪除雙向鏈表中指定位置的元素,并返回這個元素的值 
  2. DoubleLinkedList.prototype.removeAt = function (index) { 
  3.   if (index < 0 || index >= this.length) return null 
  4.  
  5.   let current = this.head 
  6.   let prevNode 
  7.  
  8.   if (index === 0) { // 移除頭部元素 
  9.     this.head = current.next 
  10.     this.head.prev = null 
  11.     if (this.length === 1) this.tail = null 
  12.   } else if (index === this.length - 1) { // 移除尾部元素 
  13.     current = this.tail 
  14.     this.tail = current.prev 
  15.     this.tail.next = null 
  16.   } else { // 移除中間元素 
  17.     current = this.getElementAt(index
  18.     prevNode = current.prev 
  19.     prevNode.next = current.next 
  20.     current.next.prev = prevNode 
  21.   } 
  22.  
  23.   this.length-- 
  24.   return current.val 

indexOf(val)

在雙向鏈表中查找元素索引,有了上面的 find 方法做鋪墊,這里就簡單了,思路一致,

  1. // 獲取雙向鏈表中給定元素的索引 
  2. DoubleLinkedList.prototype.indexOf = function (val) { 
  3.   let curHead = this.head 
  4.   let curTail = this.tail 
  5.   let idx = 0 
  6.   while (curHead !== curTail) { 
  7.     if (curHead.val == val) return idx 
  8.     curHead = curHead.next 
  9.  
  10.     if (curTail.val == val) return this.length - 1 - idx 
  11.     curTail = curTail.prev 
  12.  
  13.     idx++ 
  14.   } 
  15.   return -1 

joinstring)

序列化鏈表我們還是和上面單向鏈表一致即可

  1. // 序列化雙向鏈表 
  2. DoubleLinkedList.prototype.join = function (string) { 
  3.   let cur = this.head 
  4.   let str = '' 
  5.   while (cur) { 
  6.     str += cur.val 
  7.  
  8.     if (cur.next) str += string 
  9.  
  10.     cur = cur.next 
  11.   } 
  12.   return str 

雙向鏈表我們就介紹這么多,剩下的方法比較簡單,就不贅述了,文末雙向鏈表案例中有完整代碼

同樣,我們來簡單測試一下對與否

  1. let doubleLinkedList = new DoubleLinkedList() 
  2. doubleLinkedList.append(10) 
  3. doubleLinkedList.append(15) 
  4. doubleLinkedList.append(20) 
  5. doubleLinkedList.append(25) 
  6. console.log(doubleLinkedList.join("<->")) 
  7.  
  8. console.log(doubleLinkedList.getElementAt(0).val) 
  9. console.log(doubleLinkedList.getElementAt(1).val) 
  10. console.log(doubleLinkedList.getElementAt(5)) 
  11.  
  12. console.log(doubleLinkedList.join("<->")) 
  13. console.log(doubleLinkedList.indexOf(10)) 
  14. console.log(doubleLinkedList.indexOf(25)) 
  15. console.log(doubleLinkedList.indexOf(50)) 
  16.  
  17. doubleLinkedList.insert(0, 5) 
  18. doubleLinkedList.insert(3, 18) 
  19. doubleLinkedList.insert(6, 30) 
  20. console.log(doubleLinkedList.join("<->")) 
  21.  
  22. console.log(doubleLinkedList.find(10).val) 
  23. console.log(doubleLinkedList.removeAt(0)) 
  24. console.log(doubleLinkedList.removeAt(1)) 
  25. console.log(doubleLinkedList.removeAt(5)) 
  26. console.log(doubleLinkedList.remove(10)) 
  27. console.log(doubleLinkedList.remove(100)) 
  28.  
  29. console.log(doubleLinkedList.join("<->")) 

上面代碼輸出如下

  1. // 10<->15<->20<->25 
  2. // 10 
  3. // 15 
  4. // null 
  5. // 10<->15<->20<->25 
  6. // 0 
  7. // 3 
  8. // -1 
  9. // 5<->10<->15<->18<->20<->25<->30 
  10. // 10 
  11. // 5 
  12. // 15 
  13. // null 
  14. // 10 
  15. // null 
  16. // 18<->20<->25<->30 

嗯,沒有報錯,簡單對比一下,是正確的,No Problem

環形鏈表

我們再來看另一種鏈表,環形鏈表,顧名思義,環形鏈表的尾部節點指向它自己的頭節點

環形鏈表有單向環形鏈表,也可以有雙向環形鏈表,如下圖

 

單雙環形鏈表這里我們就不再一一的寫了,你可以嘗試自己寫一下,對比上面我們環形鏈表只需要注意下尾部節點要指向頭節點即可

為什么JavaScript中不內置鏈表?

根據我們上面所說,鏈表有這么多優點,那么為什么 JavaScript 這門語言不內置鏈表這種數據結構呢?

其實 JS 中,數組幾乎實現了鏈表的所有功能,所以沒那個必要去再麻煩一次了,聽到這里你可能會疑惑,上面不是說,數組在某些情況(例如頭部插入等等)下性能不如鏈表嗎?

我們來用事實說話,現在我們用上面完成的單向鏈表類 LinkedList,同原生數組做一個簡單的的時間測試

  1. let linkedList = new LinkedList() 
  2. let arr = [] 
  3.  
  4. // 測試 分別嘗試 「總數100 插入節點50」/「總數100000 插入節點50000」 
  5. let count = 100 
  6. let insertIndex = 50 
  7. // let count = 100000 
  8. // let insertIndex = 50000 
  9.  
  10. console.time('鏈表push操作'
  11. for (let i = 0; i < count; i++) { 
  12.   linkedList.append(i) 
  13. console.timeEnd('鏈表push操作'
  14.  
  15. console.time('數組push操作'
  16. for (let i = 0; i < count; i++) { 
  17.   arr.push(i) 
  18. console.timeEnd('數組push操作'
  19.  
  20.  
  21. console.time('鏈表insert操作'
  22. linkedList.insert('test節點', insertIndex) 
  23. console.timeEnd('鏈表insert操作'
  24.  
  25. console.time('數組insert操作'
  26. arr.splice(insertIndex, 0, 'test節點'
  27. console.timeEnd('數組insert操作'
  28.  
  29.  
  30. console.time('鏈表remove操作'
  31. linkedList.removeAt(insertIndex) 
  32. console.timeEnd('鏈表remove操作'
  33.  
  34. console.time('數組remove操作'
  35. arr.splice(insertIndex, 1) 
  36. console.timeEnd('數組remove操作'

我們來看下結果

追加 100 個數據,在索引 50 插入元素,再刪除插入的元素

追加 100000 個數據,在索引 50000 插入元素,再刪除插入的元素

What??????

我們從測試結果可以看到不論基數為 100 這樣的小量級或者基數為 100000 這樣一個很大的量級時,原生 Array 的性能都依然碾壓鏈表

也就是說鏈表效率高于數組效率這種話,事實上在 JS 中是不存在的,即使你創建一個長度為 1 億的數組,再創建一個長度為 10 的數組,并且向這兩個數組的中間添加元素,console.time 時間出來看看,你會發現所用時間與數組長度長度無關,這說明 JS 數組達到了鏈表的效率要求

而且數組中我們也可以用 splice() 方法向數組的指定位置去添加和刪除元素,經測試,所需時間同樣與數組長度無關,也能達到鏈表的要求,而數組的下標完全可以取代鏈表的 head,tail,next,prev 等方法,并且大多數情況下會更方便些,再加上工作中鏈表這種數據結構的使用場景不是太多,所以可以說 JS 中的數組是完爆鏈表的

當然,這只局限于 JavaScript 這門語言中,這和 JS 內部的數組實現機制有關,其實 JS 中的數組只是叫數組而已,它和常規語言中的數組概念就不同,那么關于數組概念以及內部實現,不在我們此章節討論范圍之內,先留一個疑問,過幾天有空了再另起一篇 JS 數組相關的文章吧,其實自己找去答案最好了,我們說 JS 是一門解釋型高級語言,它的底層實現并不像我們看起來那么簡單循規,有點打破常規的意思

講的這里,你可能會吐槽這一篇文章好不容易看完了,現在你給我說沒用。。。不要著急,收好臭雞蛋

JavaScript中鏈表無用?

如我們上面所說,難道 JavaScript 中的鏈表當真就毫無作用了嗎?

其實也不是,就比如三大法寶之一 React 中的 Fiber 架構,就用到了鏈表這種數據結構

Fiber 在英文中的意思為 纖維化,即細化,將任務進行細化,它把一個耗時長的任務分成很多小片,每一個小片的運行時間很短,雖然總時間依然很長,但是在每個小片執行完之后,都給其他任務一個執行的機會,這樣唯一的線程就不會被獨占,其他任務依然有運行的機會,React 中的 Fiber 就把整個 VDOM 的更新過程碎片化

在之前 React 中的 render() 方法會接收一個 虛擬DOM 對象和一個真實的 容器DOM 作為 虛擬DOM 渲染完成后的掛載節點,其主要作用就是將 虛擬DOM 渲染為真實DOM 并掛載到容器下,這個方法在更新的時候是進行遞歸操作的,如果在更新的過程中有大量的節點需要更新,就會出現長時間占用 JS 主線程的情況,并且整個遞歸過程是無法被打斷的,由于 JS 線程和 GUI 線程是互斥的(詳看👉「硬核JS」一次搞懂JS運行機制),所以大量更新的情況下你可能會看到界面有些卡頓

Fiber 架構其實就解決兩個問題,一是保證任務在瀏覽器空閑的時候執行,二是將任務進行碎片化,接下來我們簡單說下 Fiber

JS 中有一個實驗性質的方法 requestIdleCallback(callback) ,它可以傳入一個回調函數,回調函數能夠收到一個 deadline 對象,通過該對象的timeRemaining() 方法可以獲取到當前瀏覽器的空閑時間,如果有空閑時間,那么就可以執行一小段任務,如果時間不足了,則繼續 requestIdleCallback,等到瀏覽器又有空閑時間的時候再接著執行,這樣就實現了瀏覽器空閑的時候執行

但是 虛擬DOM 是樹結構,當任務被打斷后,樹結構無法恢復之前的任務繼續執行,所以需要一種新的數據結構,也就是我們的鏈表,鏈表可以包含多個指針,Fiber 采用的鏈表中就包含三個指針,parent 指向其父 Fiber 節點,child 指向其子 Fiber 節點,sibling 指向其兄弟 Fiber 節點,一個 Fiber 節點對應一個任務節點,這樣就可以輕易找到下一個節點,繼而也就可以恢復任務的執行

這簡簡單單的一段,就是大名鼎鼎的 Fiber 架構,那么你說鏈表有用嗎?

說了這么多,其實對于普通需求,我們 JS 確實不需要用到鏈表,數組能完爆它,但是特殊需求里,鏈表獨具它一定的優勢,總之三個字,看需求,再者,我們現在是在用 JS 來闡述鏈表,但是其它常規語言可沒有 JS 中的數組這么強悍,而且學會了鏈表,我們下一個學習樹結構時就更加得心應手了

最后

文中的案例完整代碼地址如下 👇

單雙鏈表DEMO[1]

此文介紹數據結構之一的鏈表,作為鏈表刷題前的小知識

上班摸魚水群不如摸魚刷道算法,百利無一害,堅持每天刷題吧,加油

GitHub建了個算法倉庫,從零更算法題/文字/視頻 題解ing,一塊來刷吧 👉 GitHub傳送門[2]

此文視頻版本詳見 👉 B站傳送門[3]

看到這里了,來個三連吧,如有錯誤請指正,也歡迎大家關注公眾號「不正經的前端」,和算法群的朋友們組團刷算法,效率更高

Reference

[1]單雙鏈表DEMO:

https://github.com/isboyjc/DailyAlgorithms/tree/master/demo/algorithm[2]GitHub傳送門:

https://github.com/isboyjc/DailyAlgorithms[3]B站傳送門:

https://www.bilibili.com/video/BV1aV411q7WF

 

責任編輯:武曉燕 來源: 不正經的前端
相關推薦

2021-01-06 08:03:00

JavaScript數據結構

2021-03-10 08:42:19

Java數據結構算法

2021-12-21 08:19:29

數據結構算法鏈表相交

2020-10-21 14:57:04

數據結構算法圖形

2023-03-08 08:03:09

數據結構算法歸并排序

2020-09-28 08:11:14

JavaScript數據

2021-03-11 08:53:20

Java數據結構算法

2023-10-27 07:04:20

2023-04-27 09:13:20

排序算法數據結構

2021-08-03 10:24:59

數據跳躍鏈表結構

2021-05-12 14:09:35

鏈表數據結構線性結構

2023-10-06 20:21:28

Python鏈表

2023-03-07 08:02:07

數據結構算法數列

2023-03-02 08:15:13

2023-03-10 08:07:39

數據結構算法計數排序

2023-09-25 12:23:18

Python

2014-04-04 11:14:18

JavaScriptStack遞歸

2023-02-08 07:52:36

跳躍表數據結構

2023-10-30 08:31:42

數據結構算法

2023-03-13 10:08:31

數據結構算法
點贊
收藏

51CTO技術棧公眾號

日本在线人成| 五月激情六月丁香| 日韩电影精品| 亚洲欧美日韩国产成人精品影院| 91精品一区二区| 九九热最新地址| 91成人午夜| 欧美性xxxx极品高清hd直播| 欧美一区二区三区在线播放| 在线观看免费视频一区| 亚洲精品一区二区妖精| 欧美不卡一区二区三区四区| 欧美爱爱视频免费看| 久久视频www| 狠狠色狠狠色综合系列| 欧美精品18videosex性欧美| 伊人网在线视频观看| 亚洲ww精品| 午夜精品福利在线| 亚洲国产高清国产精品| 丁香六月色婷婷| 三级亚洲高清视频| 九九久久久久久久久激情| 久久偷拍免费视频| 成人污污视频| 色偷偷成人一区二区三区91| 91制片厂免费观看| 奇米影视888狠狠狠777不卡| 国产一区二区三区精品视频| 96精品视频在线| 手机在线中文字幕| 亚洲小说图片| 久久69国产一区二区蜜臀| 欧美成人一二三| 韩国女同性做爰三级| 超碰成人97| 欧美人牲a欧美精品| 国产91在线视频观看| av毛片在线免费| 国产视频911| 国产精品一区二区三区免费观看| 中文字幕在线播放av| 亚洲一区免费| 欧美黑人国产人伦爽爽爽| a一级免费视频| 国产videos久久| 亚洲国产精品嫩草影院久久| 一级片免费在线观看视频| av在线一区不卡| 欧美日韩人人澡狠狠躁视频| 国产片侵犯亲女视频播放| 欧美黄色激情| 国产精品久久久久精k8| 欧美精品在线一区| 欧美扣逼视频| 99久久99久久精品国产片果冻| 亚洲影院污污.| 一级片视频网站| 全部av―极品视觉盛宴亚洲| 国产成人精品a视频一区www| 婷婷激情五月网| 99视频一区| 欧美激情视频网站| 精品视频久久久久| 欧美黄色aaaa| 欧美麻豆久久久久久中文| 疯狂撞击丝袜人妻| 91精品一区二区三区综合| 神马国产精品影院av| 粉嫩精品久久99综合一区| 欧美日韩在线观看视频小说| 亚洲欧美国产精品久久久久久久 | 日韩电影免费网址| 国产一区二区动漫| 国产精品久久免费观看| av亚洲免费| 中文字幕欧美国内| 少妇高潮在线观看| 欧美a级在线| 久久久免费观看| 久久亚洲成人av| 亚洲福利一区| 国产91精品久| 日韩精品一区二区亚洲av观看| 久久久久欧美精品| 国产精品一二三在线| 一级全黄少妇性色生活片| 久久国内精品自在自线400部| 国产欧美韩国高清| 精品毛片在线观看| www.欧美日韩国产在线| 日本亚洲导航| 黄色在线论坛| 亚洲成人自拍一区| 色综合av综合无码综合网站| 国产福利91精品一区二区| 欧美一区二视频| av免费观看不卡| 国产精品探花在线观看| 久久精品最新地址| 日韩xxx高潮hd| 视频一区视频二区在线观看| 91久久久久久| 视频污在线观看| 中文字幕乱码日本亚洲一区二区| 青青在线免费视频| 性欧美18~19sex高清播放| 欧美影院一区二区| 伊人av在线播放| 亚洲都市激情| 欧美成人精品在线播放| 中文字幕第15页| 精品一区二区三区视频在线观看| 国产精品国产亚洲精品看不卡15| 免费观看成年在线视频网站| 亚洲欧美日韩国产综合在线| 国产午夜大地久久| 亚洲视频自拍| 日韩精品视频免费| 午夜国产福利一区二区| 久久综合影视| 国产精品嫩草在线观看| 91看片在线观看| 精品国产91久久久| 亚洲天堂网站在线| 国内精品久久久久久99蜜桃| 欧美激情aaaa| 亚洲综合网av| 91麻豆成人久久精品二区三区| 做爰高潮hd色即是空| 中文字幕乱码在线播放| 日韩精品一区二区三区视频 | 人妻 日韩精品 中文字幕| 国产综合久久久久影院| 欧美日本韩国国产| 黄色美女视频在线观看| 欧美剧情片在线观看| 88久久精品无码一区二区毛片| 欧美激情一区| 成人看片人aa| av资源网在线观看| 99久久精品情趣| 亚洲在线不卡| 粉嫩一区二区三区| 日韩成人性视频| 国产一级理论片| 精品一区二区三区免费观看| 日韩三级电影网站| 亚洲欧美小说色综合小说一区| 日韩美一区二区三区| av黄色免费在线观看| 日本欧洲一区二区| 欧美日韩一区二区三区免费| h片在线观看视频免费免费| 欧美一卡2卡三卡4卡5免费| 久草手机视频在线观看| 美日韩一区二区| 色噜噜色狠狠狠狠狠综合色一| 成年人在线视频免费观看| 香蕉av福利精品导航| 中文字幕永久免费| 欧美女激情福利| 999视频在线免费观看| av毛片在线免费| 日韩一级在线观看| 久草网在线观看| 国产精品综合在线视频| 国产又黄又爽免费视频| 亚洲伦理一区二区| 欧美成人精品在线播放| 亚洲精品人妻无码| 亚洲成在人线在线播放| 艳妇乳肉亭妇荡乳av| 亚洲精品社区| 九九九九精品| 91久久国产综合久久91猫猫| 亚洲欧美激情一区| 日韩av免费播放| 国产精品国产自产拍高清av王其| 97人人爽人人| 欧美在线首页| 国产在线精品一区二区三区| 涩涩涩在线视频| 亚洲日韩中文字幕| 在线观看日韩一区二区| 亚洲男人的天堂在线aⅴ视频 | 中文字幕在线高清| 亚洲三级免费看| 亚洲天堂中文网| 亚洲精品视频一区二区| 国产精品一区二区人妻喷水| 香蕉久久a毛片| 婷婷久久青草热一区二区| 亚洲电影二区| 欧美精品video| 日色在线视频| 欧美精品在线观看播放| 免费视频网站www| 2024国产精品| www.午夜av| 国产欧美日韩一区二区三区在线| 日本午夜精品一区二区三区| 国产精品亚洲欧美日韩一区在线| 久久免费少妇高潮久久精品99| 免费黄色在线视频网站| 欧美一区二区三区不卡| 欧美bbbbbbbbbbbb精品| 国产精品久久久久久久久免费桃花 | 免费观看久久久4p| 国产视频在线观看网站| 亚洲尤物av| 91在线免费观看网站| 中文字幕在线直播| 美女啪啪无遮挡免费久久网站| 天天插天天干天天操| 777亚洲妇女| jizz国产在线观看| 一二三四区精品视频| av男人的天堂av| 国产成人av电影在线| 午夜dv内射一区二区| 国产精品porn| 亚洲国产精品毛片| 国偷自产av一区二区三区| 国产精品无码专区在线观看| 波多野一区二区| 久久九九免费视频| 国产污视频在线| 亚洲成人a**站| 国产又粗又猛又爽又黄91| 日韩欧美有码在线| 久久精品国产亚洲av高清色欲| 国产精品毛片高清在线完整版| 91黄色免费视频| 国产精品69毛片高清亚洲| 国产av人人夜夜澡人人爽| 99国产精品自拍| 日本人妻伦在线中文字幕| 日韩伦理一区| 日本一区二区三区免费观看| 久久久免费毛片| 成人欧美一区二区三区视频xxx| 欧美日韩免费电影| 国产91在线播放精品91| 国产在线天堂www网在线观看| 欧美男插女视频| 麻豆传媒在线观看| 日韩一级黄色av| 国产日本在线观看| 亚洲女在线观看| 嫩草研究院在线| 亚洲欧美一区二区三区在线 | 美女任你摸久久| 91看片就是不一样| 亚洲综合99| 黄色一级片播放| 99国产精品99久久久久久粉嫩| 国产资源在线免费观看| 欧美日韩精品免费观看视频完整| 国风产精品一区二区| 亚洲精品成人| 国产欧美123| 欧美日韩一区二区国产| 中文字幕日韩精品无码内射| 女主播福利一区| 国产成人一区二区三区别| 国产综合精品一区| 日韩a∨精品日韩在线观看| 亚洲黄页一区| 免费看日本毛片| 国产精品综合色区在线观看| 免费观看精品视频| 久久人人精品| 欧美大尺度做爰床戏| 日本亚洲视频在线| 91精品999| 国产白丝网站精品污在线入口| 国产成人精品综合久久久久99| 成人免费va视频| 亚洲av无码一区二区二三区| 国产午夜亚洲精品午夜鲁丝片| 日本少妇xxxxx| 亚洲欧洲日韩女同| 久久免费播放视频| 色综合天天在线| 中文字幕一二区| 欧美一区二区女人| 天堂在线视频网站| 国产一区二区动漫| 亚洲精品白浆| 国产91精品青草社区| 91成人抖音| 91国产丝袜在线放| 亚瑟一区二区三区四区| 亚洲欧美日韩精品久久久| 888久久久| 人妻精品无码一区二区三区| 美美哒免费高清在线观看视频一区二区| 中文字幕资源在线观看| www.av精品| 欧美黄色一级生活片| 亚洲免费av高清| 欧美videossex极品| 91麻豆精品国产自产在线| 日本黄色免费视频| 国产一区二区三区三区在线观看 | 欧美精品做受xxx性少妇| av免费不卡| 国产精品一区二区三区成人| 亚洲精品a区| 婷婷四房综合激情五月| 影音先锋亚洲电影| 一本一道久久a久久综合蜜桃| 成人一级片网址| 日本猛少妇色xxxxx免费网站| 亚洲综合在线第一页| 中文区中文字幕免费看| 欧美成人精品二区三区99精品| 国产三级电影在线| 97婷婷涩涩精品一区| 懂色av色香蕉一区二区蜜桃| 欧美精品尤物在线| 韩日精品在线| 五月天视频在线观看| 久久一夜天堂av一区二区三区| 国产乱国产乱老熟300| 欧美在线制服丝袜| 天天干天天爽天天操| 美女精品久久久| yy6080久久伦理一区二区| 久久精品国产精品国产精品污| 我不卡伦不卡影院| 污片在线免费看| 91污在线观看| 精品在线视频观看| 日韩一级完整毛片| 嫩草香蕉在线91一二三区| 日本欧美精品在线| 色婷婷狠狠五月综合天色拍 | 亚洲国产美女| 污视频在线观看免费网站| 亚洲国产高清不卡| 亚洲第一网站在线观看| 日韩av在线最新| 国精产品一区一区三区mba下载| 国产在线拍揄自揄视频不卡99| 国产一区二区在线| 人妻有码中文字幕| 久久这里只精品最新地址| 国产精品视频久久久久久久| 亚洲国产中文字幕久久网| 91www在线| 国产一区二区免费电影| 黄色免费成人| 99久久久无码国产精品性波多| 亚洲麻豆国产自偷在线| 国产毛片毛片毛片毛片毛片| 久久精品国产清自在天天线| 色999久久久精品人人澡69| 日韩欧美电影一区二区| 日韩高清国产一区在线| 中国女人特级毛片| 欧美自拍偷拍一区| 北岛玲日韩精品一区二区三区| 国产精品99一区| 国产一区二区三区四区五区传媒| 日韩 欧美 高清| 国产三级欧美三级| 岳乳丰满一区二区三区| 深夜福利一区二区| 精品午夜视频| 亚洲乱码日产精品bd在线观看| 国产福利一区二区| 国产精品不卡av| 日韩成人av网址| 日本免费久久| 亚洲乱码国产乱码精品天美传媒| 毛片不卡一区二区| 精品国产视频一区二区三区| 日韩女优制服丝袜电影| 超碰资源在线| 日韩精品欧美在线| 激情五月激情综合网| 激情四射综合网| 亚洲国产欧美一区二区三区同亚洲 | 日本怡春院一区二区| www.av免费| 精品国产凹凸成av人导航| 天堂资源在线| 污视频在线免费观看一区二区三区| 日韩精品亚洲专区| 永久免费未视频| 亚洲电影在线观看| 日韩免费福利视频| 看全色黄大色大片| 在线日韩中文| 中文字幕av网址| 欧美疯狂做受xxxx富婆| 91豆花视频在线播放| 午夜老司机精品|