摘要:相反,雙向鏈表具有指向其前后元素的節(jié)點(diǎn)。另外,可以對(duì)鏈表進(jìn)行排序。這個(gè)實(shí)用程序方法用于打印鏈表中的節(jié)點(diǎn),僅用于調(diào)試目的。第行將更新為,這是從鏈表中彈出最后一個(gè)元素的行為。如果鏈表為空,則返回。
什么是鏈表
單鏈表是表示一系列節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu),其中每個(gè)節(jié)點(diǎn)指向鏈表中的下一個(gè)節(jié)點(diǎn)。 相反,雙向鏈表具有指向其前后元素的節(jié)點(diǎn)。
與數(shù)組不同,鏈表不提供對(duì)鏈表表中特定索引訪問(wèn)。 因此,如果需要鏈表表中的第三個(gè)元素,則必須遍歷第一個(gè)和第二個(gè)節(jié)點(diǎn)才能到得到它。
鏈表的一個(gè)好處是能夠在固定的時(shí)間內(nèi)從鏈表的開(kāi)頭和結(jié)尾添加和刪除項(xiàng)。
這些都是在技術(shù)面試中經(jīng)常被問(wèn)到的數(shù)據(jù)結(jié)構(gòu),所以讓我們開(kāi)始吧。
另外,可以對(duì)鏈表進(jìn)行排序。 這意味著當(dāng)每個(gè)節(jié)點(diǎn)添加到鏈表中時(shí),它將被放置在相對(duì)于其他節(jié)點(diǎn)的適當(dāng)位置。
節(jié)點(diǎn)鏈表只是一系列節(jié)點(diǎn),所以讓我們從 Node 對(duì)象開(kāi)始。
一個(gè)節(jié)點(diǎn)有兩條信息
指向鏈表中下一項(xiàng)的指針或引用(對(duì)于單鏈表)
節(jié)點(diǎn)的值
對(duì)于我們的節(jié)點(diǎn),我們只需要?jiǎng)?chuàng)建一個(gè)函數(shù),該函數(shù)接受一個(gè)值,并返回一個(gè)具有上面兩個(gè)信息的對(duì)象:指向下一個(gè)節(jié)點(diǎn)的指針和該節(jié)點(diǎn)的值。
注意,我們可以只聲明 value 而不是 value: value。這是因?yàn)樽兞棵Q相同(ES6 語(yǔ)法)節(jié)點(diǎn)鏈表
現(xiàn)在,讓我們深入研究 NodeList 類,以下就是節(jié)點(diǎn)鏈表樣子。
節(jié)點(diǎn)鏈表將包含五個(gè)方法:
push(value): 將值添加到鏈表的末尾
pop() :彈出鏈表中的最后一個(gè)值
get(index):返回給定索引中的項(xiàng)
delete(index):從給定索引中刪除項(xiàng)
isEmpty(): 返回一個(gè)布爾值,指示鏈表是否為空
printList():不是鏈表的原生方法,它將打印出我們的鏈表,主要用于調(diào)試
構(gòu)造函數(shù)構(gòu)造函數(shù)中需要三個(gè)信息:
head:對(duì)鏈表開(kāi)頭節(jié)點(diǎn)的引用
tail:對(duì)鏈表末尾節(jié)點(diǎn)的引用
length:鏈表中有多少節(jié)點(diǎn)
class LinkedList { constructor() { this.head = null; this.tail = null; this.length = 0; } }IsEmpty
isEmpty() 方法是一個(gè)幫助函數(shù),如果鏈表為空,則返回true。
isEmpty() { return this.length === 0; }printList
這個(gè)實(shí)用程序方法用于打印鏈表中的節(jié)點(diǎn),僅用于調(diào)試目的。
printList () { const nodes = []; let current = this.head; while (current) { nodes.push(current.value); current = current.next; } return nodes.join(" -> "); }Push
在添加新節(jié)點(diǎn)之前,push 方法需要檢查鏈表是否為空。如何知道鏈表是否為空? 兩種方式:
isEmpty()方法返回true(鏈表的長(zhǎng)度為零)
head 指針為空
對(duì)于這個(gè)例子,我們使用 head是否為null來(lái)判斷鏈表是否為空。
如果鏈表中沒(méi)有項(xiàng),我們可以簡(jiǎn)單地將head 指針和tail指針都設(shè)置為新節(jié)點(diǎn)并更新鏈表的長(zhǎng)度。
if (this.head === null) { this.head = node; this.tail = node; this.length++; return node; }
如果鏈表不是空的,我們必須執(zhí)行以下操作:
將tail.next 指向新節(jié)點(diǎn)
將 tail 指向新節(jié)點(diǎn)
更新鏈表長(zhǎng)度
以下是完整的 push 方法:
push(value) { const node = Node(value); // The list is empty if (this.head === null) { this.head = node; this.tail = node; this.length++; return node; } this.tail.next = node; this.tail = node; this.length++; }Pop
在刪除鏈表中的最后一項(xiàng)之前,我們的pop方法需要檢查以下兩項(xiàng)內(nèi)容:
檢查鏈表是否為空
檢查鏈表中是否只有一項(xiàng)
可以使用isEmpty方法檢查鏈表是否包含節(jié)點(diǎn)。
if (this.isEmpty()) { return null; }
我們?nèi)绾沃梨湵碇兄挥幸粋€(gè)節(jié)點(diǎn)? 如果 head 和tail 指向同一個(gè)節(jié)點(diǎn)。但是在這種情況下我們需要做什么呢? 刪除唯一的節(jié)點(diǎn)意味著我們實(shí)際上要重新設(shè)置鏈表。
if (this.head === this.tail) { this.head = null; this.tail = null; this.length--; return nodeToRemove; }
如果鏈表中有多個(gè)元素,我們可以執(zhí)行以下操作
當(dāng)鏈表中有節(jié)點(diǎn)時(shí), 如果鏈表中的下一個(gè)節(jié)點(diǎn)是 tail 更新 tail 指向當(dāng)前節(jié)點(diǎn) 當(dāng)前節(jié)點(diǎn)設(shè)置為 null, 更新鏈表的長(zhǎng)度 返回前一個(gè) tail 元素
它看起來(lái)像這樣:
1 let currentNode = this.head; 2 let secondToLastNode; 3 4 //從前面開(kāi)始并迭代直到找到倒數(shù)第二個(gè)節(jié)點(diǎn) 5 6 while (currentNode) { 7 if (currentNode.next === this.tail) { 8 // 將第二個(gè)節(jié)點(diǎn)的指針移動(dòng)到最后一個(gè)節(jié)點(diǎn) 9 secondToLastNode = currentNode; 10 break; 11 } 12 currentNode = currentNode.next; 13 } 14 // 彈出該節(jié)點(diǎn) 15 secondToLastNode.next = null; 16 // 將 tail 移動(dòng)到倒數(shù)第二個(gè)節(jié)點(diǎn) 17 this.tail = secondToLastNode; 18 this.length--; 19 20 // 初始化 this.tail 21 return nodeToRemove;
如果你無(wú)法想象這一點(diǎn),那么讓我們來(lái)看看它。
第6-10行:如果鏈表中的下一個(gè)節(jié)點(diǎn)是最后一個(gè)項(xiàng),那么這個(gè)當(dāng)前項(xiàng)目就是新tail,因此我們需要保存它的引用。
if (currentNode.next === this.tail) { secondToLastNode = currentNode; }
第15行:將secondToLastNode更新為null,這是從鏈表中“彈出”最后一個(gè)元素的行為。
secondToLastNode.next = null;
第17行:更新tail以指向secondToLastNode。
this.tail = secondToLastNode;
第18行:更新鏈表的長(zhǎng)度,因?yàn)槲覀儎倓h除了一個(gè)節(jié)點(diǎn)。
第21行:返回剛剛彈出的節(jié)點(diǎn)。
以下是完整的pop方法:
pop() { if (this.isEmpty()) { return null; } const nodeToRemove = this.tail; // There"s only one node! if (this.head === this.tail) { this.head = null; this.tail = null; this.length--; return nodeToRemove; } let currentNode = this.head; let secondToLastNode; // Start at the front and iterate until // we find the second to last node while (currentNode) { if (currentNode.next === this.tail) { // Move the pointer for the second to last node secondToLastNode = currentNode; break; } currentNode = currentNode.next; } // Pop off that node secondToLastNode.next = null; // Move the tail to the second to last node this.tail = secondToLastNode; this.length--; // Initialized to this.tail return nodeToRemove; }Get
get方法必須檢查三種情況:
索引是否超出了鏈表的范圍
鏈表是否為空
查詢第一個(gè)元素
如果鏈表中不存在請(qǐng)求的索引,則返回null。
// Index is outside the bounds of the list if (index < 0 || index > this.length) { return null; }
如果鏈表為空,則返回null。你可以把這些if語(yǔ)句組合起來(lái),但是為了保持清晰,我把它們分開(kāi)了。
if (this.isEmpty()) { return null; }
如果我們請(qǐng)求第一個(gè)元素,返回 head。
// We"re at the head! if (index === 0 ) { return this.head; }
否則,我們只是一個(gè)一個(gè)地遍歷鏈表,直到找到要查找的索引。
let current = this.head; let iterator = 0; while (iterator < index) { iterator++; current = current.next; } return current;
以下是完整的get(index)方法:
get(index) { // Index is outside the bounds of the list if (index < 0 || index > this.length) { return null; } if (this.isEmpty()) { return null; } // We"re at the head! if (index === 0 ) { return this.head; } let current = this.head; let iterator = 0; while (iterator < index) { iterator++; current = current.next; } return current; }Delete
delete方法需要考慮到三個(gè)地方
刪除的索引超出了鏈表的范圍
鏈表是否為空
我們想要?jiǎng)h除 head
如果鏈表中不存在我們要?jiǎng)h除的索引,則返回 null。
// Index is outside the bounds of the list if (index < 0 || index > this.length) { return null; }
如果我們想刪除head,將head設(shè)置為鏈表中的下一個(gè)值,減小長(zhǎng)度,并返回我們剛剛刪除的值。
if (index === 0) { const nodeToDelete = this.head; this.head = this.head.next; this.length--; return nodeToDelete; }
如果以上都 不是,則刪除節(jié)點(diǎn)的邏輯如下:
循環(huán)遍歷正在查找的索引 增加索引值 將前一個(gè)和當(dāng)前指針向上移動(dòng)一個(gè) 將當(dāng)前值保存為要?jiǎng)h除的節(jié)點(diǎn) 更新上一個(gè)節(jié)點(diǎn)的指針以指向下一個(gè)節(jié)點(diǎn) 如果下一個(gè)值為 `null` 將`tail`設(shè)置為新的最后一個(gè)節(jié)點(diǎn) 更新鏈表長(zhǎng)度 返回已刪除的節(jié)點(diǎn)
如果你需要可視化圖片,請(qǐng)參考Pop部分中的圖表。
以下是完整的 delete 方法:
delete(index) { // Index is outside the bounds of the list if (index < 0 || index > this.length - 1) { return null; } if (this.isEmpty()) { return null; } if (index === 0) { const nodeToDelete = this.head; this.head = this.head.next; this.length--; return nodeToDelete; } let current = this.head; let previous; let iterator = 0; while (iterator < index) { iterator++; previous = current; current = current.next; } const nodeToDelete = current; // Re-direct pointer to skip the element we"re deleting previous.next = current.next; // We"re at the end if(previous.next === null) { this.tail = previous; } this.length--; return nodeToDelete; }
你的點(diǎn)贊是我持續(xù)分享好東西的動(dòng)力,歡迎點(diǎn)贊!
歡迎加入前端大家庭,里面會(huì)經(jīng)常分享一些技術(shù)資源。文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/109658.html
摘要:需求實(shí)現(xiàn)一個(gè)函數(shù)對(duì)鏈表進(jìn)行升序排列插入排序。關(guān)于插入排序插入排序的介紹可以看,大體邏輯為建立一個(gè)新的空鏈表。遍歷完成,返回新鏈表。代碼如下總結(jié)因?yàn)橛猩蟼€(gè)的函數(shù)的幫助,這個(gè)插入排序?qū)崿F(xiàn)起來(lái)非常簡(jiǎn)單。 TL;DR 2016 年末最后一篇,對(duì)鏈表進(jìn)行插入排序。系列目錄見(jiàn) 前言和目錄 。 需求 實(shí)現(xiàn)一個(gè) insertSort() 函數(shù)對(duì)鏈表進(jìn)行升序排列(插入排序)。實(shí)現(xiàn)過(guò)程中可以使用 上一個(gè) ...
摘要:需求實(shí)現(xiàn)函數(shù)把兩個(gè)鏈表合并成一個(gè)。新鏈表的節(jié)點(diǎn)是交叉從兩個(gè)鏈表中取的。通過(guò)行的調(diào)換指針,我們可以保證下一次循環(huán)就是對(duì)另一個(gè)鏈表進(jìn)行操作了。這樣一直遍歷到兩個(gè)鏈表末尾,返回結(jié)束。參考資料的代碼實(shí)現(xiàn)的測(cè)試 TL;DR 把兩個(gè)鏈表洗牌合并成一個(gè),系列目錄見(jiàn) 前言和目錄 。 需求 實(shí)現(xiàn)函數(shù) shuffleMerge() 把兩個(gè)鏈表合并成一個(gè)。新鏈表的節(jié)點(diǎn)是交叉從兩個(gè)鏈表中取的。這叫洗牌合并。舉...
摘要:需求實(shí)現(xiàn)函數(shù)進(jìn)行歸并排序。解法歸并排序的運(yùn)行方式是,遞歸的把一個(gè)大鏈表切分成兩個(gè)小鏈表。切分到最后就全是單節(jié)點(diǎn)鏈表了,而單節(jié)點(diǎn)鏈表可以被認(rèn)為是已經(jīng)排好序的。這時(shí)候再兩兩合并,最終會(huì)得到一個(gè)完整的已排序鏈表。用把排好序的兩個(gè)鏈表合并起來(lái)。 TL;DR 對(duì)鏈表進(jìn)行歸并排序,系列目錄見(jiàn) 前言和目錄 。 需求 實(shí)現(xiàn)函數(shù) mergeSort() 進(jìn)行歸并排序。注意這種排序法需要使用遞歸。在 fr...
摘要:需求實(shí)現(xiàn)一個(gè)函數(shù),把一個(gè)鏈表切分成兩個(gè)。子鏈表的節(jié)點(diǎn)應(yīng)該是在父鏈表中交替出現(xiàn)的。所以整個(gè)算法的解法就能很容易地用表示。唯一需要考慮的就是在每個(gè)循環(huán)體中判斷節(jié)點(diǎn)該插入哪個(gè)鏈表。也有人使用持續(xù)增長(zhǎng)的配合取余來(lái)做,比如。 TL;DR 把一個(gè)鏈表交替切分成兩個(gè),系列目錄見(jiàn) 前言和目錄 。 需求 實(shí)現(xiàn)一個(gè) alternatingSplit() 函數(shù),把一個(gè)鏈表切分成兩個(gè)。子鏈表的節(jié)點(diǎn)應(yīng)該是在父鏈...
摘要:需求實(shí)現(xiàn)函數(shù)取兩個(gè)已排序的鏈表的交集,交集指兩個(gè)鏈表都有的節(jié)點(diǎn),節(jié)點(diǎn)不一定連續(xù)。每個(gè)鏈表應(yīng)該只遍歷一次。結(jié)果鏈表中不能包含重復(fù)的節(jié)點(diǎn)。當(dāng)我們對(duì)比和的值時(shí),有這幾種情況,這時(shí)節(jié)點(diǎn)肯定交集,加入結(jié)果鏈表中。 TL;DR 一次遍歷取兩個(gè)排序鏈表的交集,系列目錄見(jiàn) 前言和目錄 。 需求 實(shí)現(xiàn)函數(shù) sortedIntersect() 取兩個(gè)已排序的鏈表的交集,交集指兩個(gè)鏈表都有的節(jié)點(diǎn),節(jié)點(diǎn)不一定...
摘要:寫兩個(gè)幫助函數(shù)來(lái)創(chuàng)建鏈表。用于把一個(gè)節(jié)點(diǎn)插入到鏈表的頭部。這個(gè)方法應(yīng)該始終返回一個(gè)新的鏈表。接收一個(gè)數(shù)組為參數(shù),創(chuàng)建對(duì)應(yīng)的鏈表。參考資料的代碼實(shí)現(xiàn)的測(cè)試 TL;DR 寫兩個(gè)幫助函數(shù)來(lái)創(chuàng)建鏈表。系列目錄見(jiàn) 前言和目錄 。 需求 寫兩個(gè)方法 push 和 buildList 來(lái)初始化鏈表。嘗試在 buildList 中使用 push 。下面的例子中我用 a -> b -> c 來(lái)表示鏈表,...
閱讀 2958·2023-04-26 01:49
閱讀 2085·2021-10-13 09:39
閱讀 2299·2021-10-11 11:09
閱讀 939·2019-08-30 15:53
閱讀 2830·2019-08-30 15:44
閱讀 935·2019-08-30 11:12
閱讀 2996·2019-08-29 17:17
閱讀 2390·2019-08-29 16:57