你們都用過js中的數組,數組實際上是一種線性表的順序存儲結構,它的特色是用一組地址連續的存儲單元依次存儲數據元素。而它的缺點也正是其特色而形成,好比對數組作刪除或者插入的時候,可能須要移動大量的元素。前端
這裏大體模擬一下數組的插入操做:node
function insert(arr, index, data) { for (let i = arr.length; i >index; i--) { arr[i] = arr[i - 1]; } arr[index] = data; }
從上面的代碼能夠看出數組的插入以及刪除都有可能會是一個O(n)的操做。從而就引出了鏈表這種數據結構,鏈表不要求邏輯上相鄰的元素在物理位置上也相鄰,所以它沒有順序存儲結構所具備的缺點,固然它也失去了數組在一塊連續空間內隨機存取的優勢。數組
class Node { constructor(key) { this.next = null; this.key = key; } }
class List { constructor() { this.head = null; } }
static createNode(key) { return new createNode(key); }
這裏說明一下,這一塊我是向外暴露了一個靜態方法來建立節點,而並不是直接把它封裝進插入操做裏去,由於我感受這樣的邏輯會更加正確一些。 從建立一個鏈表 -> 建立一個節點 -> 將節點插入進鏈表中。可能你會遇到一些文章介紹的方式是直接將一個數據做爲參數去調用insert操做,在insert內部作了一個建立節點。數據結構
插入操做只須要去調整節點的指針便可,兩種狀況:this
head沒有指向任何節點,說明當前插入的節點是第一個spa
head有指向的節點指針
insert(node) { // 若是head有指向的節點 if(this.head){ node.next = this.head; }else { node.next = null; } this.head = node; }
find(key) { let node = this.head; while(node !== null && node.key !== key){ node = node.next; } return node; }
這裏分三種狀況:code
所要刪除的節點恰好是第一個,也就是head指向的節點blog
要刪除的節點爲最後一個節點隊列
在列表中間刪除某個節點
delete(node) { // 第一種狀況 if(node === this.head){ this.head = node.next; return; } // 查找所要刪除節點的上一個節點 let prevNode = this.head; while (prevNode.next !== node) { prevNode = prevNode.next; } // 第二種狀況 if(node.next === null) { prevNode.next = null; } // 第三種狀況 if(node.next) { prevNode.next = node.next; } }
class ListNode { constructor(key) { this.next = null; this.key = key; } } class List { constructor() { this.head = null; this.length = 0; } static createNode(key) { return new ListNode(key); } // 往頭部插入數據 insert(node) { // 若是head後面有指向的節點 if (this.head) { node.next = this.head; } else { node.next = null; } this.head = node; this.length++; } find(key) { let node = this.head; while (node !== null && node.key !== key) { node = node.next; } return node; } delete(node) { if (this.length === 0) { throw 'node is undefined'; } if (node === this.head) { this.head = node.next; this.length--; return; } let prevNode = this.head; while (prevNode.next !== node) { prevNode = prevNode.next; } if (node.next === null) { prevNode.next = null; } if (node.next) { prevNode.next = node.next; } this.length--; } }
若是你把上面介紹的單向列表都看明白了,那麼這裏介紹的雙向列表其實差很少。
從上面的圖能夠很清楚的看到雙向鏈表和單向鏈表的區別。雙向鏈表多了一個指向上一個節點的指針。
class ListNode { this.prev = null; this.next = null; this.key = key; }
class List { constructor(){ this.head = null; } }
static createNode(key){ return new ListNode(key); }
insert(node) { node.prev = null; node.next = this.head; if(this.head){ this.head.prev = node; } this.head = node; }
這裏和單向節點同樣,就直接貼代碼了
search(key) { let node = this.head; while (node !== null && node.key !== key) { node = node.next; } return node; }
和以前單向鏈表同樣,分三種狀況去看:
刪除的是第一個節點
刪除的是中間的某個節點
刪除的是最後一個節點
delete(node) { const {prev,next} = node; delete node.prev; delete node.next; if(node === this.head){ this.head = next; } if(next){ next.prev = prev; } if(prev){ prev.next = next; } }
class ListNode { constructor(key) { // 指向前一個節點 this.prev = null; // 指向後一個節點 this.next = null; // 節點的數據(或者用於查找的鍵) this.key = key; } } /** * 雙向鏈表 */ class List { constructor() { this.head = null; } static createNode(key) { return new ListNode(key); } insert(node) { node.prev = null; node.next = this.head; if (this.head) { this.head.prev = node; } this.head = node; } search(key) { let node = this.head; while (node !== null && node.key !== key) { node = node.next; } return node; } delete(node) { const { prev, next } = node; delete node.prev; delete node.next; if (node === this.head) { this.head = next; } if (prev) { prev.next = next; } if (next) { next.prev = prev; } } }
這裏作一個小總結吧,可能有一部分人讀到這裏還不是特別的明白,個人建議是先好好看懂上面的單向鏈表。 其實只要你明白了鏈表的基礎概念,就是有一個head,而後在有好多的節點(Node),而後用一個指針把他們串起來就行了,至於裏面的插入操做也好,刪除也好,其實都是在調整節點中指針的指向。
後續可能還會是數據結構,多是講二叉堆,也可能回過頭來說一些隊列和棧的思想在程序中的應用。歡迎你們指出文章的錯誤,若是有什麼寫做建議也能夠提出。我會持續的去寫關於前端的一些技術文章,若是你們喜歡的話能夠關注一下哈