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