- 基礎知識就像是一座大樓的地基,它決定了咱們的技術高度。
- 咱們應該多掌握一些可移值的技術或者再過十幾年應該都不會過期的技術,數據結構與算法就是其中之一。
棧、隊列、鏈表、堆 是數據結構與算法中的基礎知識,是程序員的地基。前端
筆者寫的 JavaScript 數據結構與算法之美 系列用的語言是 JavaScript ,旨在入門數據結構與算法和方便之後複習。node
線性表(Linear List):就是數據排成像一條線同樣的結構。每一個線性表上的數據最多隻有前和後兩個方向。數組、鏈表、隊列、棧 等就是線性表結構。git
非線性表:數據之間並非簡單的先後關係。二叉樹、堆、圖 就是非線性表。程序員
本文主要講線性表,非線性表會在後面章節講。github
因此數組支持 隨機訪問,根據下標隨機訪問的時間複雜度爲 O(1)。面試
數組爲了保持內存數據的連續性,會致使插入、刪除這兩個操做比較低效,由於底層一般是要進行大量的數據搬移來保持數據的連續性。
插入與刪除的時間複雜度以下:
插入:從最好 O(1) ,最壞 O(n) ,平均 O(n)
刪除:從最好 O(1) ,最壞 O(n) ,平均 O(n)算法
可是由於 JavaScript 是弱類型的語言,弱類型則容許隱式類型轉換。編程
隱式:是指源碼中沒有明顯的類型轉換代碼。也就是說,一個變量,能夠賦值字符串,也能夠賦值數值。segmentfault
let str = "string" str = 123 console.log(str) // 123
你還能夠直接讓字符串類型的變量和數值類型的變量相加,雖然得出的最終結果未必是你想象的那樣,但必定不會報錯。數組
let a = 123 let b = "456" let c = a + b // 數值加字符串,結果是字符串 console.log(c) // "123456"
數組的每一項能夠是不一樣的類型,好比:
// 數組的類型有 數值、字符串,還能夠隨意變動類型 const arr = [ 12, 34, "abc" ] arr[2] = { "key": "value" } // 把數組的第二項變成對象 console.log(arr) // [ 12, 34, { "key": "value"} ]
定義的數組的大小是可變的,不像強類型語言,定義某個數組變量的時候就要定義該變量的大小。
const arr = [ 12, 34, "abc"] arr.push({ "key": "value" }) // 添加一項 對象 consolelog(arr) // [ 12, 34, "abc", { "key": "value" } ]
JavaScript 原生支持數組,並且提供了不少操做方法,這裏不展開講。
棧
結構。棧頂
,另外一端就叫棧底
。操做受限
的線性表,只容許在一端插入和刪除數據。空棧
。棧也被用在編程語言的編譯器和內存中保存變量、方法調用等,好比函數的調用棧。
棧的方法:
// Stack類 function Stack() { this.items = []; // 添加新元素到棧頂 this.push = function(element) { this.items.push(element); }; // 移除棧頂元素,同時返回被移除的元素 this.pop = function() { return this.items.pop(); }; // 查看棧頂元素 this.peek = function() { return this.items[this.items.length - 1]; }; // 判斷是否爲空棧 this.isEmpty = function() { return this.items.length === 0; }; // 清空棧 this.clear = function() { this.items = []; }; // 查詢棧的長度 this.size = function() { return this.items.length; }; // 打印棧裏的元素 this.print = function() { console.log(this.items.toString()); }; }
測試:
// 建立Stack實例 var stack = new Stack(); console.log(stack.isEmpty()); // true stack.push(5); // undefined stack.push(8); // undefined console.log(stack.peek()); // 8 stack.push(11); // undefined console.log(stack.size()); // 3 console.log(stack.isEmpty()); // false stack.push(15); // undefined stack.pop(); // 15 console.log(stack.size()); // 3 stack.print(); // 5,8,11 stack.clear(); // undefined console.log(stack.size()); // 0
棧的應用實例:JavaScript 數據結構與算法之美 - 實現一個前端路由,如何實現瀏覽器的前進與後退 ?
隊列裏面有一些聲明的輔助方法:
代碼:
// Queue類 function Queue() { this.items = []; // 向隊列尾部添加元素 this.enqueue = function(element) { this.items.push(element); }; // 移除隊列的第一個元素,並返回被移除的元素 this.dequeue = function() { return this.items.shift(); }; // 返回隊列的第一個元素 this.front = function() { return this.items[0]; }; // 判斷是否爲空隊列 this.isEmpty = function() { return this.items.length === 0; }; // 獲取隊列的長度 this.size = function() { return this.items.length; }; // 清空隊列 this.clear = function() { this.items = []; }; // 打印隊列裏的元素 this.print = function() { console.log(this.items.toString()); }; }
測試:
// 建立Queue實例 var queue = new Queue(); console.log(queue.isEmpty()); // true queue.enqueue('John'); // undefined queue.enqueue('Jack'); // undefined queue.enqueue('Camila'); // undefined queue.print(); // "John,Jack,Camila" console.log(queue.size()); // 3 console.log(queue.isEmpty()); // false queue.dequeue(); // "John" queue.dequeue(); // "Jack" queue.print(); // "Camila" queue.clear(); // undefined console.log(queue.size()); // 0
優先隊列中元素的添加和移除是依賴優先級
的。
應用
優先隊列分爲兩類
最小優先隊列是把優先級的值最小的元素被放置到隊列的最前面(表明最高的優先級)。
好比:有四個元素:"John", "Jack", "Camila", "Tom",他們的優先級值分別爲 4,3,2,1。
那麼最小優先隊列排序應該爲:"Tom","Camila","Jack","John"。
最大優先隊列正好相反,把優先級值最大的元素放置在隊列的最前面。
以上面的爲例,最大優先隊列排序應該爲:"John", "Jack", "Camila", "Tom"。
實現
實現一個優先隊列,有兩種選項:
這裏最小優先隊列和最大優先隊列我都採用第一種方式實現,你們能夠嘗試一下第二種。
下面只重寫 enqueue() 方法和 print() 方法,其餘方法和上面的普通隊列徹底相同。
實現最小優先隊列
// 定義最小優先隊列 function MinPriorityQueue () { this.items = []; this.enqueue = enqueue; this.dequeue = dequeue; this.front = front; this.isEmpty = isEmpty; this.size = size; this.clear = clear; this.print = print; }
實現最小優先隊列 enqueue() 方法和 print() 方法:
// 優先隊列添加元素,要根據優先級判斷在隊列中的插入順序 function enqueue (element, priority) { var queueElement = { element: element, priority: priority }; if (this.isEmpty()) { this.items.push(queueElement); } else { var added = false; for (var i = 0; i < this.size(); i++) { if (queueElement.priority < this.items[i].priority) { this.items.splice(i, 0, queueElement); added = true; break ; } } if (!added) { this.items.push(queueElement); } } } // 打印隊列裏的元素 function print () { var strArr = []; strArr = this.items.map(function (item) { return `${item.element}->${item.priority}`; }); console.log(strArr.toString()); }
最小優先隊列測試:
// 建立最小優先隊列minPriorityQueue實例 var minPriorityQueue = new MinPriorityQueue(); console.log(minPriorityQueue.isEmpty()); // true minPriorityQueue.enqueue("John", 1); // undefined minPriorityQueue.enqueue("Jack", 3); // undefined minPriorityQueue.enqueue("Camila", 2); // undefined minPriorityQueue.enqueue("Tom", 3); // undefined minPriorityQueue.print(); // "John->1,Camila->2,Jack->3,Tom->3" console.log(minPriorityQueue.size()); // 4 console.log(minPriorityQueue.isEmpty()); // false minPriorityQueue.dequeue(); // {element: "John", priority: 1} minPriorityQueue.dequeue(); // {element: "Camila", priority: 2} minPriorityQueue.print(); // "Jack->3,Tom->3" minPriorityQueue.clear(); // undefined console.log(minPriorityQueue.size()); // 0
實現最大優先隊列
// 最大優先隊列 MaxPriorityQueue 類 function MaxPriorityQueue () { this.items = []; this.enqueue = enqueue; this.dequeue = dequeue; this.front = front; this.isEmpty = isEmpty; this.size = size; this.clear = clear; this.print = print; } // 優先隊列添加元素,要根據優先級判斷在隊列中的插入順序 function enqueue (element, priority) { var queueElement = { element: element, priority: priority }; if (this.isEmpty()) { this.items.push(queueElement); } else { var added = false; for (var i = 0; i < this.items.length; i++) { // 注意,只須要將這裏改成大於號就能夠了 if (queueElement.priority > this.items[i].priority) { this.items.splice(i, 0, queueElement); added = true; break ; } } if (!added) { this.items.push(queueElement); } } }
最大優先隊列測試:
// 建立最大優先隊列maxPriorityQueue實例 var maxPriorityQueue = new MaxPriorityQueue(); console.log(maxPriorityQueue.isEmpty()); // true maxPriorityQueue.enqueue("John", 1); // undefined maxPriorityQueue.enqueue("Jack", 3); // undefined maxPriorityQueue.enqueue("Camila", 2); // undefined maxPriorityQueue.enqueue("Tom", 3); // undefined maxPriorityQueue.print(); // "Jack->3,Tom->3,Camila->2,John->1" console.log(maxPriorityQueue.size()); // 4 console.log(maxPriorityQueue.isEmpty()); // false maxPriorityQueue.dequeue(); // {element: "Jack", priority: 3} maxPriorityQueue.dequeue(); // {element: "Tom", priority: 3} maxPriorityQueue.print(); // "Camila->2,John->1" maxPriorityQueue.clear(); // undefined console.log(maxPriorityQueue.size()); // 0
循環隊列,顧名思義,它長得像一個環。把它想像成一個圓的鐘就對了。
關鍵是:肯定好隊空和隊滿的斷定條件。
循環隊列的一個例子就是擊鼓傳花遊戲(Hot Potato)。在這個遊戲中,孩子們圍城一個圓圈,擊鼓的時候把花盡快的傳遞給旁邊的人。某一時刻擊鼓中止,這時花在誰的手裏,誰就退出圓圈直到遊戲結束。重複這個過程,直到只剩一個孩子(勝者)。
下面咱們在普通隊列的基礎上,實現一個模擬的擊鼓傳花遊戲,下面只寫擊鼓傳花的代碼片斷:
// 實現擊鼓傳花 function hotPotato (nameList, num) { var queue = new Queue(); for (var i = 0; i < nameList.length; i++) { queue.enqueue(nameList[i]); } var eliminated = ''; while (queue.size() > 1) { // 循環 num 次,隊首出來去到隊尾 for (var i = 0; i < num; i++) { queue.enqueue(queue.dequeue()); } // 循環 num 次事後,移除當前隊首的元素 eliminated = queue.dequeue(); console.log(`${eliminated} 在擊鼓傳花中被淘汰!`); } // 最後只剩一個元素 return queue.dequeue(); } // 測試 var nameList = ["John", "Jack", "Camila", "Ingrid", "Carl"]; var winner = hotPotato(nameList, 10); console.log(`最後的勝利者是:${winner}`);
執行結果爲:
// John 在擊鼓傳花中被淘汰! // Ingrid 在擊鼓傳花中被淘汰! // Jack 在擊鼓傳花中被淘汰! // Camila 在擊鼓傳花中被淘汰! // 最後的勝利者是:Carl
隊列小結
一些具備某些額外特性的隊列,好比:循環隊列、阻塞隊列、併發隊列。它們在不少偏底層系統、框架、中間件的開發中,起着關鍵性的做用。
以上隊列的代碼要感謝 leocoder351。
簡單的連接結構圖:
其中,data 中保存着數據,next 保存着下一個鏈表的引用。
上圖中,咱們說 data2 跟在 data1 後面,而不是說 data2 是鏈表中的第二個元素。值得注意的是,咱們將鏈表的尾元素指向了 null 節點,表示連接結束的位置。
因此鏈表不支持 隨機訪問,若是要找特定的項,只能從頭開始遍歷,直到找到某個項。
因此訪問的時間複雜度爲 O(n)。
鏈表中插入或者刪除一個數據,咱們並不須要爲了保持內存的連續性而搬移結點,由於鏈表的存儲空間自己就不是連續的,只須要考慮相鄰結點的指針改變。
因此,在鏈表中插入和刪除一個數據是很是快速的,時間複雜度爲 O(1)。
三種最多見的鏈表結構,它們分別是:
因爲鏈表的起始點的肯定比較麻煩,所以不少鏈表的實現都會在鏈表的最前面添加一個特殊的節點,稱爲 頭節點,表示鏈表的頭部。
通過改造,鏈表就成了以下的樣子:
針對鏈表的插入和刪除操做,咱們只須要考慮相鄰結點的指針改變,因此插入與刪除的時間複雜度爲 O(1)。
在 d2 節點後面插入 d4 節點:
刪除 d4 節點:
單向鏈表的八種經常使用操做:
具體代碼:
// 單鏈表 function SinglyLinkedList() { // 節點 function Node(element) { this.element = element; // 當前節點的元素 this.next = null; // 下一個節點指針 } var length = 0; // 鏈表的長度 var head = null; // 鏈表的頭部節點 // 向鏈表尾部添加一個新的節點 this.append = function(element) { var node = new Node(element); var currentNode = head; // 判斷是否爲空鏈表 if (head === null) { // 是空鏈表,就把當前節點做爲頭部節點 head = node; } else { // 從 head 開始一直找到最後一個 node while (currentNode.next) { // 後面還有 node currentNode = currentNode.next; } // 把當前節點的 next 指針 指向 新的節點 currentNode.next = node; } // 鏈表的長度加 1 length++; }; // 向鏈表特定位置插入一個新節點 this.insert = function(position, element) { if (position < 0 && position > length) { // 越界 return false; } else { var node = new Node(element); var index = 0; var currentNode = head; var previousNode; // 在最前插入節點 if (position === 0) { node.next = currentNode; head = node; } else { // 循環找到位置 while (index < position) { index++; previousNode = currentNode; currentNode = currentNode.next; } // 把前一個節點的指針指向新節點,新節點的指針指向當前節點,保持鏈接性 previousNode.next = node; node.next = currentNode; } length++; return true; } }; // 從鏈表的特定位置移除一項 this.removeAt = function(position) { if ((position < 0 && position >= length) || length === 0) { // 越界 return false; } else { var currentNode = head; var index = 0; var previousNode; if (position === 0) { head = currentNode.next; } else { // 循環找到位置 while (index < position) { index++; previousNode = currentNode; currentNode = currentNode.next; } // 把當前節點的 next 指針 指向 當前節點的 next 指針,便是 刪除了當前節點 previousNode.next = currentNode.next; } length--; return true; } }; // 從鏈表中移除指定項 this.remove = function(element) { var index = this.indexOf(element); return this.removeAt(index); }; // 返回元素在鏈表的索引,若是鏈表中沒有該元素則返回 -1 this.indexOf = function(element) { var currentNode = head; var index = 0; while (currentNode) { if (currentNode.element === element) { return index; } index++; currentNode = currentNode.next; } return -1; }; // 若是鏈表中不包含任何元素,返回 true,若是鏈表長度大於 0,返回 false this.isEmpty = function() { return length === 0; }; // 返回鏈表包含的元素個數,與數組的 length 屬性相似 this.size = function() { return length; }; // 獲取鏈表頭部元素 this.getHead = function() { return head.element; }; // 因爲鏈表使用了 Node 類,就須要重寫繼承自 JavaScript 對象默認的 toString() 方法,讓其只輸出元素的值 this.toString = function() { var currentNode = head; var string = ''; while (currentNode) { string += ',' + currentNode.element; currentNode = currentNode.next; } return string.slice(1); }; // 打印鏈表數據 this.print = function() { console.log(this.toString()); }; // 獲取整個鏈表 this.list = function() { console.log('head: ', head); return head; }; }
測試:
// 建立單向鏈表實例 var singlyLinked = new SinglyLinkedList(); console.log(singlyLinked.removeAt(0)); // false console.log(singlyLinked.isEmpty()); // true singlyLinked.append('Tom'); singlyLinked.append('Peter'); singlyLinked.append('Paul'); singlyLinked.print(); // "Tom,Peter,Paul" singlyLinked.insert(0, 'Susan'); singlyLinked.print(); // "Susan,Tom,Peter,Paul" singlyLinked.insert(1, 'Jack'); singlyLinked.print(); // "Susan,Jack,Tom,Peter,Paul" console.log(singlyLinked.getHead()); // "Susan" console.log(singlyLinked.isEmpty()); // false console.log(singlyLinked.indexOf('Peter')); // 3 console.log(singlyLinked.indexOf('Cris')); // -1 singlyLinked.remove('Tom'); singlyLinked.removeAt(2); singlyLinked.print(); // "Susan,Jack,Paul" singlyLinked.list(); // 具體控制檯
整個鏈表數據在 JavaScript 裏是怎樣的呢 ?
爲了看這個數據,特地寫了個 list 函數:
// 獲取整個鏈表 this.list = function() { console.log('head: ', head); return head; };
重點上上面的最後一行代碼: singlyLinked.list() ,打印的數據以下:
因此,在 JavaScript 中,單鏈表的真實數據有點相似於對象,其實是 Node 類生成的實例。
單向鏈表只有一個方向,結點只有一個後繼指針 next 指向後面的結點。
而雙向鏈表,它支持兩個方向,每一個結點不止有一個後繼指針 next 指向後面的結點,還有一個前驅指針 prev 指向前面的結點。
單向鏈表與又向鏈表比較
因此,若是存儲一樣多的數據,雙向鏈表要比單鏈表佔用更多的內存空間。
雖然兩個指針比較浪費存儲空間,但能夠支持雙向遍歷,這樣也帶來了雙向鏈表操做的靈活性。
咱們能夠訪問一個特定節點的下一個或前一個元素。
具體代碼:
// 建立雙向鏈表 DoublyLinkedList 類 function DoublyLinkedList() { function Node(element) { this.element = element; //當前節點的元素 this.next = null; //下一個節點指針 this.previous = null; //上一個節點指針 } var length = 0; // 鏈表長度 var head = null; // 鏈表頭部 var tail = null; // 鏈表尾部 // 向鏈表尾部添加一個新的項 this.append = function(element) { var node = new Node(element); var currentNode = tail; // 判斷是否爲空鏈表 if (currentNode === null) { // 空鏈表 head = node; tail = node; } else { currentNode.next = node; node.prev = currentNode; tail = node; } length++; }; // 向鏈表特定位置插入一個新的項 this.insert = function(position, element) { if (position < 0 && position > length) { // 越界 return false; } else { var node = new Node(element); var index = 0; var currentNode = head; var previousNode; if (position === 0) { if (!head) { head = node; tail = node; } else { node.next = currentNode; currentNode.prev = node; head = node; } } else if (position === length) { this.append(element); } else { while (index < position) { index++; previousNode = currentNode; currentNode = currentNode.next; } previousNode.next = node; node.next = currentNode; node.prev = previousNode; currentNode.prev = node; } length++; return true; } }; // 從鏈表的特定位置移除一項 this.removeAt = function(position) { if ((position < 0 && position >= length) || length === 0) { // 越界 return false; } else { var currentNode = head; var index = 0; var previousNode; if (position === 0) { // 移除第一項 if (length === 1) { head = null; tail = null; } else { head = currentNode.next; head.prev = null; } } else if (position === length - 1) { // 移除最後一項 if (length === 1) { head = null; tail = null; } else { currentNode = tail; tail = currentNode.prev; tail.next = null; } } else { while (index < position) { index++; previousNode = currentNode; currentNode = currentNode.next; } previousNode.next = currentNode.next; previousNode = currentNode.next.prev; } length--; return true; } }; // 從鏈表中移除指定項 this.remove = function(element) { var index = this.indexOf(element); return this.removeAt(index); }; // 返回元素在鏈表的索引,若是鏈表中沒有該元素則返回 -1 this.indexOf = function(element) { var currentNode = head; var index = 0; while (currentNode) { if (currentNode.element === element) { return index; } index++; currentNode = currentNode.next; } return -1; }; // 若是鏈表中不包含任何元素,返回 true ,若是鏈表長度大於 0 ,返回 false this.isEmpty = function() { return length == 0; }; // 返回鏈表包含的元素個數,與數組的 length 屬性相似 this.size = function() { return length; }; // 獲取鏈表頭部元素 this.getHead = function() { return head.element; }; // 因爲鏈表使用了 Node 類,就須要重寫繼承自 JavaScript 對象默認的 toString() 方法,讓其只輸出元素的值 this.toString = function() { var currentNode = head; var string = ''; while (currentNode) { string += ',' + currentNode.element; currentNode = currentNode.next; } return string.slice(1); }; this.print = function() { console.log(this.toString()); }; // 獲取整個鏈表 this.list = function() { console.log('head: ', head); return head; }; }
測試:
// 建立雙向鏈表 var doublyLinked = new DoublyLinkedList(); console.log(doublyLinked.isEmpty()); // true doublyLinked.append('Tom'); doublyLinked.append('Peter'); doublyLinked.append('Paul'); doublyLinked.print(); // "Tom,Peter,Paul" doublyLinked.insert(0, 'Susan'); doublyLinked.print(); // "Susan,Tom,Peter,Paul" doublyLinked.insert(1, 'Jack'); doublyLinked.print(); // "Susan,Jack,Tom,Peter,Paul" console.log(doublyLinked.getHead()); // "Susan" console.log(doublyLinked.isEmpty()); // false console.log(doublyLinked.indexOf('Peter')); // 3 console.log(doublyLinked.indexOf('Cris')); // -1 doublyLinked.remove('Tom'); doublyLinked.removeAt(2); doublyLinked.print(); // "Susan,Jack,Paul" doublyLinked.list(); // 請看控制檯輸出
整個鏈表數據在 JavaScript 裏是怎樣的呢 ?
// 獲取整個鏈表 this.list = function() { console.log('head: ', head); return head; };
調用 doublyLinked.list(); .
控制檯輸出以下:
鏈表代碼實現的關鍵是弄清楚:前節點與後節點與邊界。
循環鏈表是一種特殊的單鏈表。
循環鏈表和單鏈表類似,節點類型都是同樣。
惟一的區別是,在建立循環鏈表的時候,讓其頭節點的 next 屬性指向它自己
。
即:
head.next = head;
這種行爲會致使鏈表中每一個節點的 next 屬性都指向鏈表的頭節點,換句話說,也就是鏈表的尾節點指向了頭節點,造成了一個循環鏈表。以下圖所示:
循環鏈表:在單鏈表的基礎上,將尾節點的指針指向頭結點,就構成了一個循環鏈表。環形鏈表從任意一個節點開始,均可以遍歷整個鏈表。
代碼:
// 循環鏈表 function CircularLinkedList() { // 節點 function Node(element) { this.element = element; // 當前節點的元素 this.next = null; // 下一個節點指針 } var length = 0, head = null; this.append = function(element) { var node = new Node(element), current; if (!head) { head = node; // 頭的指針指向本身 node.next = head; } else { current = head; while (current.next !== head) { current = current.next; } current.next = node; // 最後一個節點指向頭節點 node.next = head; } length++; return true; }; this.insert = function(position, element) { if (position > -1 && position < length) { var node = new Node(element), index = 0, current = head, previous; if (position === 0) { // 頭節點指向本身 node.next = head; head = node; } else { while (index++ < position) { previous = current; current = current.next; } previous.next = node; node.next = current; } length++; return true; } else { return false; } }; this.removeAt = function(position) { if (position > -1 && position < length) { var current = head, previous, index = 0; if (position === 0) { head = current.next; } else { while (index++ < position) { previous = current; current = current.next; } previous.next = current.next; } length--; return current.element; } else { return false; } }; this.remove = function(element) { var current = head, previous, indexCheck = 0; while (current && indexCheck < length) { if (current.element === element) { if (indexCheck == 0) { head = current.next; length--; return true; } else { previous.next = current.next; length--; return true; } } else { previous = current; current = current.next; indexCheck++; } } return false; }; this.remove = function() { if (length === 0) { return false; } var current = head, previous, indexCheck = 0; if (length === 1) { head = null; length--; return current.element; } while (indexCheck++ < length) { previous = current; current = current.next; } previous.next = head; length--; return current.element; }; this.indexOf = function(element) { var current = head, index = 0; while (current && index < length) { if (current.element === element) { return index; } else { index++; current = current.next; } } return -1; }; this.isEmpty = function() { return length === 0; }; this.size = function() { return length; }; // 因爲鏈表使用了 Node 類,就須要重寫繼承自 JavaScript 對象默認的 toString() 方法,讓其只輸出元素的值 this.toString = function() { var current = head, string = '', indexCheck = 0; while (current && indexCheck < length) { string += ',' + current.element; current = current.next; indexCheck++; } return string.slice(1); }; // 獲取鏈表頭部元素 this.getHead = function() { return head.element; }; // 打印鏈表數據 this.print = function() { console.log(this.toString()); }; // 獲取整個鏈表 this.list = function() { console.log('head: ', head); return head; }; }
測試:
// 建立單向鏈表實例 var circularLinked = new CircularLinkedList(); console.log(circularLinked.removeAt(0)); // false console.log(circularLinked.isEmpty()); // true circularLinked.append('Tom'); circularLinked.append('Peter'); circularLinked.append('Paul'); circularLinked.print(); // "Tom,Peter,Paul" circularLinked.insert(0, 'Susan'); circularLinked.print(); // "Susan,Tom,Peter,Paul" circularLinked.insert(1, 'Jack'); circularLinked.print(); // "Susan,Jack,Tom,Peter,Paul" console.log(circularLinked.getHead()); // "Susan" console.log(circularLinked.isEmpty()); // false console.log(circularLinked.indexOf('Peter')); // 3 console.log(circularLinked.indexOf('Cris')); // -1 circularLinked.remove('Tom'); circularLinked.removeAt(2); circularLinked.print(); // "Susan,Jack,Paul" circularLinked.list(); // 具體控制檯
整個鏈表數據在 JavaScript 裏是怎樣的呢 ?
// 獲取整個鏈表 this.list = function() { console.log('head: ', head); return head; };
調用 circularLinked.list() 。
控制檯輸出以下:
你知道你們發現沒有,爲何從 1 - 4 - 1 了,還有 next 節點,並且是還能夠一直點 next ,重複的展開下去,這正是 循環 的緣由。
鏈表總結
JavaScript 數據結構與算法之美 的系列文章,堅持 3 - 7 天左右更新一篇,暫定計劃以下表。
| 標題 | 連接 |
| :------ | :------ |
| 時間和空間複雜度 | https://github.com/biaochenxu... |
| 線性表(數組、鏈表、棧、隊列) | https://github.com/biaochenxu... |
| 實現一個前端路由,如何實現瀏覽器的前進與後退 ?| https://github.com/biaochenxu... |
| 棧內存與堆內存 、淺拷貝與深拷貝 | 精彩待續 |
| 非線性表(樹、堆) | 精彩待續 |
| 遞歸 | 精彩待續 |
| 冒泡排序 | 精彩待續 |
| 插入排序 | 精彩待續 |
| 選擇排序 | 精彩待續 |
| 歸併排序 | 精彩待續 |
| 快速排序 | 精彩待續 |
| 計數排序 | 精彩待續 |
| 基數排序 | 精彩待續 |
| 桶排序 | 精彩待續 |
| 希爾排序 | 精彩待續 |
| 堆排序 | 精彩待續 |
| 十大經典排序彙總 | 精彩待續 |
若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。
文章中的代碼已經所有放在了個人 github 上,若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵。
關注個人公衆號,第一時間接收最新的精彩博文。
文章能夠轉載,但須註明做者及出處,須要轉載到公衆號的,喊我加下白名單就好了。
參考文章:
數組:爲何不少編程語言中數組都從 0 開始編號?
JS中的算法與數據結構——鏈表(Linked-list)
JavaScript數據結構 03 - 隊列
鏈表(上):如何實現 LRU 緩存淘汰算法?
JavaScript數據結構——隊列