自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

理解JavaScript中的數(shù)據(jù)結(jié)構(gòu)(鏈表)

開發(fā) 前端
對于 JS 初學(xué)者,理解鏈表可能是一項(xiàng)比較困難的任務(wù),因?yàn)?JS 沒有提供內(nèi)置的鏈表。在像 JS 這樣的高級語言中,我們需要從頭開始實(shí)現(xiàn)此數(shù)據(jù)結(jié)構(gòu),如果你不熟悉此數(shù)據(jù)結(jié)構(gòu)的工作方式,則實(shí)現(xiàn)部分會變得更加困難 ?。

[[373957]]

對于 JS 初學(xué)者,理解鏈表可能是一項(xiàng)比較困難的任務(wù),因?yàn)?JS 沒有提供內(nèi)置的鏈表。在像 JS 這樣的高級語言中,我們需要從頭開始實(shí)現(xiàn)此數(shù)據(jù)結(jié)構(gòu),如果你不熟悉此數(shù)據(jù)結(jié)構(gòu)的工作方式,則實(shí)現(xiàn)部分會變得更加困難 ?。

在本文中,我們將討論如何將鏈表存儲在數(shù)據(jù)庫中,實(shí)現(xiàn)鏈表的添加和刪除,查找以及反轉(zhuǎn)鏈表等操作。在實(shí)現(xiàn)鏈表之前,需要知道相比數(shù)組和對象,鏈表的優(yōu)點(diǎn)是什么。

我們知道,數(shù)組中的元素以索引編號和順序存儲在數(shù)據(jù)庫中:

在使用數(shù)組時(shí),在開始或特定索引處添加/刪除元素這樣的操作可能是一項(xiàng)性能較低的任務(wù),因?yàn)槲覀儽仨氁苿铀衅渌氐乃饕?,造成這種原因是由數(shù)組的編號索引特性導(dǎo)致的。

使用對象可以解決上述問題。由于在對象中,元素存儲位置是隨機(jī)的,因此,在執(zhí)行諸如在開始處或特定索引處添加/刪除元素之類的操作時(shí),無需移動元素的索引:

盡管在對象中添加和刪除元素速度很快,但是從上圖可以看出,在進(jìn)行迭代操作時(shí),對象并不是最佳選擇,因?yàn)閷ο蟮脑卮鎯υ陔S機(jī)位置。因此,迭代操作可能需要很長時(shí)間。這是鏈表引出的原因。

那么什么是鏈表呢 ?

從名字本身可以看出它是一個(gè)以某種方式鏈表。那么它是如何鏈接的,列表包含什么呢?

鏈表由具有兩個(gè)屬性的節(jié)點(diǎn)組成:數(shù)據(jù)和指針。

節(jié)點(diǎn)內(nèi)的指針指向列表中的下一個(gè)節(jié)點(diǎn)。鏈表中的第一個(gè)節(jié)點(diǎn)稱為head。為了更好地理解,讓我們看一下描述鏈表圖示:

從上圖可以看出,每個(gè)節(jié)點(diǎn)都有兩個(gè)屬性,data和pointer。指針指向列表中的下一個(gè)節(jié)點(diǎn),最后一個(gè)節(jié)點(diǎn)的指針指向null,上圖是一個(gè)單鏈表 ?。

鏈表和對象時(shí)有很大的不同。在鏈表中,每個(gè)節(jié)點(diǎn)都通過指針(pointer)連接到下一個(gè)節(jié)點(diǎn)。因此,我們在鏈表的每個(gè)節(jié)點(diǎn)之間都有連接,而在對象中,鍵值對是隨機(jī)存儲的,彼此之間沒有連接。

本文由小智翻譯,歡迎關(guān)注《大遷世界》

接著,我們實(shí)現(xiàn)一個(gè)存儲整數(shù)的鏈表。由于 JS 不提供內(nèi)置的鏈表支持,因此我們將使用對象和類來實(shí)現(xiàn)鏈表 ?

  1. class Node { 
  2.   constructor (value) { 
  3.     this.value = value 
  4.     this.next = null 
  5.   } 
  6.  
  7. class LinkedList { 
  8.   constructor () { 
  9.     this.head = null 
  10.     this.tail = this.head 
  11.     this.length = 0 
  12.   } 
  13.   append (value) { 
  14.   } 
  15.  
  16.   prepend (value) { 
  17.  
  18.   } 
  19.  
  20.   insert (value, index) { 
  21.  
  22.   } 
  23.  
  24.   lookup (index) { 
  25.  
  26.   } 
  27.  
  28.   remove (index) { 
  29.  
  30.   } 
  31.  
  32.   reverse () { 
  33.      
  34.   } 

在上面的代碼中,我們創(chuàng)建了兩個(gè)類,一個(gè)用于來鏈表本身,一個(gè)是節(jié)點(diǎn)本身。如我們所討論的,每個(gè)節(jié)點(diǎn)將具有兩個(gè)屬性,一個(gè)值和一個(gè)指針(對應(yīng) next 字段)。

LinkedList類包含三個(gè)屬性,head(初始值為null),用于存儲鏈表的最后一個(gè)節(jié)點(diǎn)的tail(也指向null)和用于保存鏈表長度的length屬性。接著,我們來實(shí)現(xiàn)里面的方法 ?。

append (按順序添加值)

這個(gè)函數(shù)將一個(gè)節(jié)點(diǎn)添加到鏈表的末尾。為了實(shí)現(xiàn)這個(gè)函數(shù),我們需要理解它需要執(zhí)行的一些操作:

從上圖中,我們可以通過以下方式實(shí)現(xiàn)append函數(shù):

  1. append (value) { 
  2.   const newNode = new Node(value) 
  3.   if (!this.head) { 
  4.     this.head = newNode 
  5.     this.tail = newNode 
  6.   } else { 
  7.     this.tail.next = newNode 
  8.     this.tail = newNode 
  9.   } 
  10.   this.length++ 

簡單的對 append 方法解釋一下 ?:

  1. const linkedList1 = new LinkedList() 
  2. linkedList1.append(2) 

檢查head是否指向null,此時(shí)的head指向null,因此我們創(chuàng)建一個(gè)新對象,并將新對象分配給head和tail:

  1. let node = new Node(2) 
  2. this.head = newNode 
  3. this.tail = newNode 

現(xiàn)在,head 和 tail 都指向同一個(gè)對象,這一點(diǎn)很重要,要記住。

接著,我們再向鏈表添加兩個(gè)值:

  1. linkedList1.append(3) 
  2. linkedList1.append(4) 

現(xiàn)在,head 不指向null,所以我們進(jìn)入append函數(shù)的else分支:

  1. this.tail.next = node 

由于head 和tail 都指向同一個(gè)對象,tail的變化都會導(dǎo)致head對象的變化,這是JS 中對象的工作方式。在JavaScript中,對象是通過引用傳遞的,因此 head 和tail都指向存儲對象的相同地址空間。上面這行代碼相當(dāng)于

  1. this.head.next = node; 

下一行:

  1. this.tail = node 

現(xiàn)在,在執(zhí)行完上面的代碼行之后,this.head.next和this.tail指向同一對象,因此,每當(dāng)我們添加新節(jié)點(diǎn)時(shí),head對象都會自動更新。

執(zhí)行三次append之后,linkedList1 的結(jié)構(gòu)應(yīng)該是這樣的:

  1. head: {value: 2 , next: {value: 3, next: {value: 4,nextnull}}} 
  2. tail : {value: 4, nextnull
  3. length:3 

從上面的代碼中我們可以看到,鏈表的append函數(shù)的復(fù)雜度是O(1),因?yàn)槲覀兗炔恍枰苿铀饕膊恍枰闅v鏈表。

我們來看下一個(gè)函數(shù) ?

prepend (將值添加到鏈表的開頭)

為了實(shí)現(xiàn)此函數(shù),我們使用Node類創(chuàng)建一個(gè)新節(jié)點(diǎn),并將該新節(jié)點(diǎn)的下一個(gè)對象指向鏈表的head 。接下來,我們將新節(jié)點(diǎn)分配給鏈表的head:

與append函數(shù)一樣,這個(gè)函數(shù)的復(fù)雜度也是O(1)。

  1. prepend (value) { 
  2. const node = new Node(value) 
  3.  
  4. node.next = this.head 
  5. this.head = node 
  6. this.length++ 

就像append函數(shù)一樣,此函數(shù)的復(fù)雜度也為O(1)。

insert (在特定索引處添加值)

在實(shí)現(xiàn)此函數(shù)之前,我們先看看它的一個(gè)轉(zhuǎn)化過程。因此,出于理解目的,我們先創(chuàng)建一個(gè)值很少的鏈表,然后可視化insert函數(shù)。insert 函數(shù)接受兩個(gè)參數(shù),值和索引:

  1. let linkedList2 = new LinkedList() 
  2. linkedList2.append(23) 
  3. linkedList2.append(89) 
  4. linkedList2.append(12) 
  5. linkedList2.append(3) 
  6. linkedList2.insert(45,2) 

第1步:

遍歷鏈表,直到到達(dá)index-1位置:

第2步:

將索引為1的節(jié)點(diǎn)的指針(在本例中為89)分配給新節(jié)點(diǎn)(在本例中為45):

第3步:

將新節(jié)點(diǎn)(45)的 next 指向給下一個(gè)節(jié)點(diǎn)(12)

這就是執(zhí)行插入操作的方式。通過以上可視化,我們觀察到需要在index-1位置和index位置找到節(jié)點(diǎn),以便可以在它們之間插入新節(jié)點(diǎn)。在代碼中實(shí)現(xiàn):

  1. insert (value, index) { 
  2.   if (index >= this.length) { 
  3.   this.append(value) 
  4.  
  5.   const node = new Node(value) 
  6.  
  7.   const { prevNode, nextNode } = thisg.getPrevNextNodes(index
  8.   prevNode.next = node 
  9.   node.next = nextNode 
  10.  
  11.   this.length++ 

簡單分析一下上面的函數(shù):

如果index的值大于或等于length屬性,則將操作移交給append函數(shù)。對于 else 分支,我們使用 Node 類創(chuàng)建一個(gè)新節(jié)點(diǎn),接下來觀察一個(gè)新函數(shù)getPrevNextNodes(),通過該函數(shù)我們可以接收prevNode和nextNode的值。getPrevNextNodes函數(shù)的實(shí)現(xiàn)如下:

  1. getPrevNextNodes(index){ 
  2.    let count = 0; 
  3.    let prevNode = this.head; 
  4.    let nextNode = prevNode.next
  5.  
  6.    while(count < index - 1){ 
  7.      prevNode = prevNode.next
  8.      nextNode = prevNode.next
  9.      count++; 
  10.    } 
  11.  
  12.    return { 
  13.      prevNode, 
  14.      nextNode 
  15.    } 
  16.  } 

通過遍歷鏈表返回在index-1位置和index位置的節(jié)點(diǎn),并將prevNode的next屬性指向新節(jié)點(diǎn),并將新節(jié)點(diǎn)的next屬性指向nextNode。

鏈表的插入操作的復(fù)雜度為 O(n),因?yàn)槲覀儽仨毐闅v鏈表并在index-1和 index 位置搜索節(jié)點(diǎn)。盡管復(fù)雜度為O(n),但我們發(fā)現(xiàn)此插入操作比對數(shù)組的插入操作快得多,在數(shù)組中,我們必須將所有元素的索引移到特定索引之后,但是在鏈接中,我們僅操縱 index-1 和index 位置的節(jié)點(diǎn)的下一個(gè)屬性。

remove (刪除特定索引處的元素)

實(shí)現(xiàn)了插入操作之后,刪除操作就比較容易理解,因?yàn)樗鼛缀跖c插入操作相同,當(dāng)我們從getPrevNextNodes函數(shù)獲取prevNode和nextNode值時(shí),我們必須在remove中執(zhí)行以下操作:

  1. remove(index){ 
  2.   let {previousNode,currentNode} = this.getNodes(index
  3.   previousNode.next = currentNode.next 
  4.   this.length-- 

刪除操作的復(fù)雜度也為 O(n),類似于插入操作,鏈表中的刪除操作比數(shù)組中的刪除操作要快。

reverse (反轉(zhuǎn)鏈表)

雖然看起來很簡單,但反轉(zhuǎn)鏈表常常是實(shí)現(xiàn)起來最令人困惑的操作,因此,在面試中會經(jīng)常詢問這個(gè)操作。在實(shí)現(xiàn)這個(gè)函數(shù)之前,讓我們先把反轉(zhuǎn)鏈表的策略可視化一下。

為了反轉(zhuǎn)鏈表,我們需要跟蹤三個(gè)節(jié)點(diǎn),previousNode,currentNode和nextNode。

考慮下面的鏈表:

  1. let linkedList2 = new LinkedList() 
  2. linkedList2.append(67) 
  3. linkedList2.append(32) 
  4. linkedList2.append(44) 

第一步:

開始,previousNode的值為null,而currentNode的值為head:

第二步:

接下來,我們將nextNode分配給currentNode.next:

第三步:

接下來,我們將currentNode.next屬性指向previousNode:

第三步:

現(xiàn)在,我們將previousNode移至currentNode,將currentNode移至nextNode:

這個(gè)過程從步驟2重復(fù)操作,一直到currentNode 等于 null。

  1. reverse (){ 
  2.   let previousNode = null 
  3.   let currentNode = this.head 
  4.  
  5.   while(currentNode !== null) { 
  6.     let nextNode = currentNode.next 
  7.     currentNode.next = previousNode 
  8.     previousNode = currentNode 
  9.     currentNode = nextNode 
  10.   } 
  11.  
  12.   this.head = previousNode 

就像我們看到的一樣,直到currentNode === null,我們一直在遍歷和移動這些值。最后,我們將previousNode值分配給head。

反向運(yùn)算的復(fù)雜度為O(n)。

查找 (查找特定索引的值)

這個(gè)操作很簡單,我們只是遍歷鏈表并返回特定索引處的節(jié)點(diǎn)。這個(gè)操作的復(fù)雜度也是O(n)。

  1. lookup(index){ 
  2.     let counter = 0; 
  3.     let currentNode = this.head; 
  4.     while(counter < index){ 
  5.       currentNode = currentNode.next
  6.       counter++; 
  7.     } 
  8.     return currentNode; 
  9.   } 

好了,我們已經(jīng)完成了用javascript實(shí)現(xiàn)單個(gè)鏈表的基本操作。單鏈表和雙鏈表的區(qū)別在于,雙鏈表的節(jié)點(diǎn)具有指向前一個(gè)節(jié)點(diǎn)和下一個(gè)節(jié)點(diǎn)的指針。

總結(jié)

鏈表為我們提供了快速的append(末尾添加元素)和prepend(開頭添加元素)操作。盡管鏈表中的插入操作的復(fù)雜度為O(n),但比數(shù)組的插入操作要快得多。使用數(shù)組時(shí)我們面臨的另一個(gè)問題是大小復(fù)雜性,當(dāng)使用動態(tài)數(shù)組時(shí),在添加元素時(shí),我們必須將整個(gè)數(shù)組復(fù)制到另一個(gè)地址空間,然后添加元素,而在鏈表中,我們不需要 面對這樣的問題。

在使用對象時(shí),我們面臨的問題是元素在內(nèi)存中的隨機(jī)位置,而在鏈表中,節(jié)點(diǎn)是通過指針相互連接的,指針提供了一定的順序。

我是小智,我們下期見!

作者:Vivek Bisht 譯者:前端小智 來源:soshace

原文:https://blog.soshace.com/understanding-data-structures-in-javascript-linked-lists/

本文轉(zhuǎn)載自微信公眾號「大遷世界」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系大遷世界公眾號。

 

責(zé)任編輯:武曉燕 來源: 大遷世界
相關(guān)推薦

2021-01-28 07:33:34

JavaScript鏈表數(shù)據(jù)

2021-08-03 10:24:59

數(shù)據(jù)跳躍鏈表結(jié)構(gòu)

2021-05-12 14:09:35

鏈表數(shù)據(jù)結(jié)構(gòu)線性結(jié)構(gòu)

2021-07-15 06:43:12

Python數(shù)據(jù)結(jié)構(gòu)

2017-03-01 13:58:46

Python數(shù)據(jù)結(jié)構(gòu)鏈表

2021-07-13 07:52:03

Python數(shù)據(jù)結(jié)構(gòu)

2020-09-28 08:11:14

JavaScript數(shù)據(jù)

2021-03-29 08:01:20

JavaScript數(shù)據(jù)結(jié)構(gòu)

2021-10-29 11:27:52

鏈表數(shù)據(jù)結(jié)構(gòu)算法

2012-02-02 10:21:05

單鏈表nexthead

2021-04-12 15:47:00

數(shù)據(jù)結(jié)構(gòu)算法鏈表

2020-08-03 07:48:15

Javascript數(shù)據(jù)結(jié)構(gòu)

2022-09-01 16:27:19

JavaScriptWeb開發(fā)

2021-03-10 08:42:19

Java數(shù)據(jù)結(jié)構(gòu)算法

2011-07-04 10:32:37

JAVA

2022-03-31 11:17:58

JavaScript數(shù)組方法

2019-10-29 08:59:16

Redis底層數(shù)據(jù)

2021-12-21 08:19:29

數(shù)據(jù)結(jié)構(gòu)算法鏈表相交

2019-08-13 09:40:55

數(shù)據(jù)結(jié)構(gòu)算法JavasCript

2023-04-28 08:53:09

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號