做者:Roberto Hernandez
譯者:前端小智
來源:dev
點贊再看,養成習慣本文
GitHub
https://github.com/qq44924588... 上已經收錄,更多往期高贊文章的分類,也整理了不少個人文檔,和教程資料。歡迎Star和完善,你們面試能夠參照考點複習,但願咱們一塊兒有點東西。javascript
單鏈表是表示一系列節點的數據結構,其中每一個節點指向鏈表中的下一個節點。 相反,雙向鏈表具備指向其先後元素的節點。前端
與數組不一樣,鏈表不提供對鏈表表中特定索引訪問。 所以,若是須要鏈表表中的第三個元素,則必須遍歷第一個和第二個節點才能到獲得它。java
鏈表的一個好處是可以在固定的時間內從鏈表的開頭和結尾添加和刪除項。node
這些都是在技術面試中常常被問到的數據結構,因此讓咱們開始吧。git
另外,能夠對鏈表進行排序。 這意味着當每一個節點添加到鏈表中時,它將被放置在相對於其餘節點的適當位置。github
鏈表只是一系列節點,因此讓咱們從 Node 對象開始。面試
一個節點有兩條信息segmentfault
對於咱們的節點,咱們只須要建立一個函數,該函數接受一個值,並返回一個具備上面兩個信息的對象:指向下一個節點的指針和該節點的值。數組
注意,咱們能夠只聲明value
而不是value: value
。這是由於變量名稱相同(ES6 語法)
如今,讓咱們深刻研究 NodeList 類,如下就是節點鏈表樣子。數據結構
節點鏈表將包含五個方法:
printList():不是鏈表的原生方法,它將打印出咱們的鏈表,主要用於調試
構造函數中須要三個信息:
class LinkedList { constructor() { this.head = null; this.tail = null; this.length = 0; } }
isEmpty()
方法是一個幫助函數,若是鏈表爲空,則返回true
。
isEmpty() { return this.length === 0; }
這個實用程序方法用於打印鏈表中的節點,僅用於調試目的。
printList () { const nodes = []; let current = this.head; while (current) { nodes.push(current.value); current = current.next; } return nodes.join(' -> '); }
在添加新節點以前,push
方法須要檢查鏈表是否爲空。如何知道鏈表是否爲空? 兩種方式:
isEmpty()
方法返回true
(鏈表的長度爲零)head
指針爲空對於這個例子,咱們使用 head
是否爲null
來判斷鏈表是否爲空。
若是鏈表中沒有項,咱們能夠簡單地將head
指針和tail
指針都設置爲新節點並更新鏈表的長度。
if (this.head === null) { this.head = node; this.tail = node; this.length++; return node; }
若是鏈表不是空的,咱們必須執行如下操做:
tail.next
指向新節點tail
指向新節點
如下是完整的 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
方法須要檢查如下兩項內容:
可使用isEmpty
方法檢查鏈表是否包含節點。
if (this.isEmpty()) { return null; }
咱們如何知道鏈表中只有一個節點? 若是 head
和tail
指向同一個節點。可是在這種狀況下咱們須要作什麼呢? 刪除惟一的節點意味着咱們實際上要從新設置鏈表。
if (this.head === this.tail) { this.head = null; this.tail = null; this.length--; return nodeToRemove; }
若是鏈表中有多個元素,咱們能夠執行如下操做
當鏈表中有節點時, 若是鏈表中的下一個節點是 tail 更新 tail 指向當前節點 當前節點設置爲 null, 更新鏈表的長度 返回前一個 tail 元素
它看起來像這樣:
1 let currentNode = this.head; 2 let secondToLastNode; 3 4 //從前面開始並迭代直到找到倒數第二個節點 5 6 while (currentNode) { 7 if (currentNode.next === this.tail) { 8 // 將第二個節點的指針移動到最後一個節點 9 secondToLastNode = currentNode; 10 break; 11 } 12 currentNode = currentNode.next; 13 } 14 // 彈出該節點 15 secondToLastNode.next = null; 16 // 將 tail 移動到倒數第二個節點 17 this.tail = secondToLastNode; 18 this.length--; 19 20 // 初始化 this.tail 21 return nodeToRemove;
若是你沒法想象這一點,那麼讓咱們來看看它。
第6-10行:若是鏈表中的下一個節點是最後一個項,那麼這個當前項目就是新tail
,所以咱們須要保存它的引用。
if (currentNode.next === this.tail) { secondToLastNode = currentNode; }
第15行:將secondToLastNode
更新爲null
,這是從鏈表中「彈出」最後一個元素的行爲。
secondToLastNode.next = null;
第17行:更新tail
以指向secondToLastNode
。
this.tail = secondToLastNode;
第18行:更新鏈表的長度,由於咱們剛刪除了一個節點。
第21行:返回剛剛彈出的節點。
如下是完整的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
方法必須檢查三種狀況:
若是鏈表中不存在請求的索引,則返回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
方法須要考慮到三個地方
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; }
若是以上都 不是,則刪除節點的邏輯以下:
循環遍歷正在查找的索引 增長索引值 將前一個和當前指針向上移動一個 將當前值保存爲要刪除的節點 更新上一個節點的指針以指向下一個節點 若是下一個值爲 `null` 將`tail`設置爲新的最後一個節點 更新鏈表長度 返回已刪除的節點
若是你須要可視化圖片,請參考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; }
代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug。
原文:
https://dev.to/macmacky/70-ja...
乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。
https://github.com/qq44924588...
我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!
關注公衆號,後臺回覆福利,便可看到福利,你懂的。