JavaScript 數據結構與演算法2(隊列和雙端隊列)
- 2022 年 5 月 6 日
- 筆記
- 數據結構, JS數據結構與演算法, JavaScript數據結構與演算法, JS數據結構, JavaScript數據結構
學習數據結構的 git 程式碼地址: //gitee.com/zhangning187/js-data-structure-study
1、隊列和雙端隊列
隊列和棧非常類似,但是使用了與 後進先出 不同的原則。雙端隊列是一種將棧的原則和隊列的原則混合在一起的數據結構。
1.1 隊列數據結構
隊列是遵循先進先出(FIFO)原則的一組有序的項。隊列在尾部添加新元素,並從頂部移除元素。最新添加的元素必須排在隊列的末尾。
最常見的例子就是排隊。排隊打飯,排隊買票等,電腦裡面有排隊列印等。
1.1.1 創建隊列類
class Queue { constructor() { // 記錄隊列的長度,大小 this.count = 0; // 記錄隊列的第一個元素 this.lowestCount = 0; // 這裡也可以使用數組,但是為了獲取元素時更高效,使用對象存儲元素, // 和 Stack 非常類似,只是添加和移除元素的原則不同 this.items = {}; } }
1.1.2 向隊列添加元素
// 向隊列尾部添加項 // 這裡的實現方法和 Stack 棧中的方式相同。將 count 作為items中的鍵對應元素作為它的值 enqueue(element) { this.items[this.count] = element; this.count++; }
1.1.3 從隊列移除元素
// 移除第一項,並返回被移除的元素 dequeue() { if (this.isEmpty()) { return undefined; } const result = this.items[this.lowestCount]; // 刪除最先進去的元素 delete this.items[this.lowestCount]; // 最先添加的元素位置,也要向後移動 this.lowestCount++; return result; }
只有 enqueue 方法和 dequeue 方法可以添加和移除元素,這樣就確保了 Queue 類遵循先進先出原則。
1.1.4 查看隊列頭元素
// 返回隊列中第一個元素,最先被添加,也將是最先被移除的元素,隊列不做任何變動,只是取值 peek() { if (this.isEmpty()) { return undefined; } return this.items[this.lowestCount]; }
1.1.5 檢查隊列是否為空和獲取隊列長度
// 是否為空隊列 isEmpty() { return this.count === 0; } // 隊列元素個數 size() { return this.count - this.lowestCount; }
1.1.6 清空隊列和toString方法
// 清空隊列 clear() { this.items = {}; this.count = 0; this.lowestCount = 0; } toString() { if (this.isEmpty()) { return ''; } let queueString = this.items[this.lowestCount]; for (let i = this.lowestCount + 1; i < this.count; i++) { queueString += `,${this.items[i]}`; } return queueString; }
1.2 雙端隊列數據結構
雙端隊列(deque,或 double-ended queue)是一種允許我們同時從前端或後端添加或移除元素的特殊隊列。
雙端隊列生活中的例子,電影院、餐廳排隊等。一個人剛買了票還想問一些問題,可以直接在隊伍的最前面詢問資訊,另外,在隊伍尾部的人不想排隊了可以直接離開隊伍。
雙端隊列的一個常見應用是存儲一系列的撤銷操作。每當用戶在軟體中進行一個操作,該操作會被存在一個雙端隊列中(就像一個棧裡面)。雙端隊列同時遵守了先進先出和後進先出的原則,可以說它是把隊列和棧相結合的一種數據結構。
1.2.1 創建 Deque 類
class Deque { constructor() { this.count = 0; this.lowestCount = 0; this.items = {}; } }
雙端隊列是一個特殊的隊列,構造函數和部分方法和隊列相同,isEmpty、clear、size、toString
1.2.2 實現雙端隊列的方法
// 前端添加元素 addFront(element) { if (this.isEmpty()) {// 空隊列 this.addBack(element); } else { this.lowestCount--; this.items[this.lowestCount] = element; } } // 後端添加元素 addBack(element) { this.items[this.count] = element; this.count++; } // 從前端移除 removeFront() { if (this.isEmpty()) { return undefined; } const result = this.items[this.lowestCount]; // 刪除最先進去的元素 delete this.items[this.lowestCount]; // 最先添加的元素位置,也要向後移動 this.lowestCount++; return result; } // 從後端移除 removeBack() { if (this.isEmpty()) { return undefined; } this.count--; const result = this.items[this.count]; delete this.items[this.count]; return result; } // 返回最前端的元素 peekFront() { if (this.isEmpty()) { return undefined; } return this.items[this.lowestCount]; } // 返回最後端的元素 peekBack() { if (this.isEmpty()) { return undefined; } return this.items[this.count - 1]; }
1.3 使用隊列和雙端隊列解決問題
使用隊列模擬擊鼓傳花遊戲,使用雙端隊列檢查一個短語是否為迴文。
1.3.1 循環隊列--擊鼓傳花遊戲
隊列可以修改為很多的不同隊列,其中可以修改為一種循環隊列。循環隊列的一個例子就是擊鼓傳花遊戲--多人圍成一個圈,把花儘快地傳遞給旁邊的人。某一刻傳花停止,花在誰手裡誰就退出,直到最後一個人為勝者。
// 擊鼓傳花 import {Queue} from './Queue.js'; // list: 人數 num: 傳遞次數 function hotPotato(list, num) { const queue = new Queue(); // 淘汰的列表 const elimitatedList = []; for (let i = 0; i < list.length; i++) { // 依次添加到隊列中 queue.enqueue(list[i]); } while (queue.size() > 1) { for (let i = 0; i < num; i++) { // 隊列開頭移除一項添加到隊尾, // 模擬擊鼓傳花(如果你把花傳給旁邊的人,你的淘汰威脅就立即解除了) queue.enqueue(queue.dequeue()); } // 淘汰的放在數組中 // 一旦達到傳遞次數,拿著花的那個人就被淘汰了 elimitatedList.push(queue.dequeue()); } return { eliminated: elimitatedList, winner: queue.dequeue() }; } const names = ['zn1', 'zn2', 'zn3', 'zn4', 'zn5']; const result = hotPotato(names, 7); console.log(result);// zn1 為 winner
1.3.2 迴文檢查器
迴文是正反都能讀通的單詞、片語、數或一系列字元的序列。abba,abcd
有不同的演算法可以檢查字元串是否為迴文。最簡單的方式是將字元串反向排列並檢查它和原字元串是否相同。如果兩者相同那麼它就是一個迴文。也可以使用棧來完成,但是利用數據結構來解決這個問題最簡單的方法是使用雙端隊列。
// 迴文檢查 function palindromeChecker(aString) { // 檢查字元串是否合法 if (aString === undefined || aString === null || (aString !== null && aString.length === 0)) { return false; } const deque = new Deque(); // 大小寫字母都轉化為小寫,同時移除所有空格。 const lowerString = aString.toLocaleLowerCase().split(' ').join(''); let isEqual = true; // 字元插入到雙端隊列中 for (let i = 0; i < lowerString.length; i++) { deque.addBack(lowerString.charAt(i)); } while (deque.size() > 1 && isEqual) { // 最前面一個是否等於最後面一個 isEqual = deque.removeFront() === deque.removeBack(); } return isEqual; } console.log(1111, palindromeChecker('123321'));
1.3.3 任務隊列
當我們在瀏覽器中打開新標籤時,就會創建一個任務隊列。因為每個標籤都是單執行緒處理所有的任務,稱為事件循環。瀏覽器要負責多個任務,渲染HTML、執行 JavaScript 程式碼、處理用戶交互(用戶輸入、滑鼠點擊等)、執行和處理非同步請求。
-----------磨刀不誤砍柴工、加油!!!

