上一篇系列文章《【數據結構基礎】棧簡介(使用ES6)》筆者介紹了什麼是數據結構和什麼是棧及相關代碼實現,本篇文章筆者給你們介紹下什麼是隊列以及相關的代碼實現。前端
本篇文章將從如下幾個方面進行介紹:算法
本篇文章閱讀時間預計10分鐘。數組
隊列是一個有序集合,遵循先進先出的原則(FIFO),與堆棧的原則偏偏相反。容許插入的一端稱爲隊尾,容許刪除的一端稱爲對頭。假設隊列是q=(a1,a2,......,an),那麼a1就是隊頭,an就是隊尾。咱們刪除時,從a1開始刪除,而插入時,只能在an後插入。瀏覽器
隊列就比如咱們生活中的排隊,好比咱們去醫院掛號須要排隊,進電影院須要排隊進場,去超市買東西須要排隊結帳,打電話諮詢客服須要排隊接聽等等。bash
在計算機中最多見的例子就是打印機的打印隊列任務,假設咱們要打印五分不一樣的文檔,咱們須要依次打開每一個文檔,依次的單擊「打印按鈕」,每一個打印指令都會送往打印隊列任務,最早按打印按鈕的文檔最早被打印,直到全部文檔被打印完成。微信
首先咱們先聲明建立一個初始化的queue類,實現代碼以下:數據結構
class Queue {
constructor() {
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
} 複製代碼
首先咱們建立了一個存儲隊列元素的數據結構,咱們聲明瞭count變量,方便咱們統計隊列大小,聲明lowestCount變量標記隊列的對頭,方便咱們刪除元素。接下來咱們要聲明以下方法,來實現一個完整的隊列:ide
此方法主要實現了向隊列的隊尾添加新的元素,實現的關鍵就是「隊尾」添加元素,實現代碼以下:函數
enqueue(element) {
this.items[this.count] = element;
this.count++;
} 複製代碼
因爲隊列的items屬性是對象,咱們使用count做爲對象的屬性,元素添加至隊列後,count的值遞增長1。oop
此方法主要用於刪除隊列元素,因爲隊列遵循先進先出原則,咱們須要將隊列的「隊頭」元素進行移除,代碼實現以下:
dequeue() {
if (this.isEmpty()) {
return undefined;
}
const result = this.items[this.lowestCount];
delete this.items[this.lowestCount];
this.lowestCount++;
return result;
}複製代碼
首先咱們須要驗證隊列是否爲空,若是未空返回未定義。若是隊列不爲空,咱們首先獲取「隊頭」元素,而後使用delete方法進行刪除,同時標記對頭元素的變量lowestCount遞增長一,而後返回刪除的隊頭元素。
如今咱們來實現一些輔助方法,好比咱們想查看「隊頭」元素,咱們用peek()方法進行實現,使用lowestCount變量進行獲取,實現代碼以下:
peek() {
if (this.isEmpty()) {
return undefined;
}
return this.items[this.lowestCount];
} 複製代碼
獲取隊列的長度,咱們可使用count變量與lowestCount相減便可,假如咱們的count屬性爲2,lowestCount爲0,這意味着隊列有兩個元素。接下來咱們從隊列裏中刪除一個元素,lowestCount的值更新爲1,count的值不變,所以隊列的長度爲1,依次類推。所以size()方法的實現代碼以下:
size() {
return this.count - this.lowestCount;
} 複製代碼
isEmpty()的實現方式更爲簡單了,只須要判斷size()是否返回爲0便可,實現代碼以下:
isEmpty() {
return this.size() === 0;
}複製代碼
要清空隊列元素,咱們能夠一直調用dequeue()方法,直至返回undefined便可或者將各變量重置爲初始值便可,咱們使用重置初始化的思路,代碼以下:
clear() {
this.items = {};
this.count = 0;
this.lowestCount = 0;
}複製代碼
接下來咱們實現最後一個方法,打印輸出隊列全部的元素,示例代碼以下:
toString() {
if (this.isEmpty()) {
return '';
}
let objString = `${this.items[this.lowestCount]}`;
for (let i = this.lowestCount + 1; i < this.count; i++) {
objString = `${objString},${this.items[i]}`;
}
return objString;
} 複製代碼
export default class Queue {
constructor() {
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
enqueue(element) {
this.items[this.count] = element;
this.count++;
}
dequeue() {
if (this.isEmpty()) {
return undefined;
}
const result = this.items[this.lowestCount];
delete this.items[this.lowestCount];
this.lowestCount++;
return result;
}
peek() {
if (this.isEmpty()) {
return undefined;
}
return this.items[this.lowestCount];
}
isEmpty() {
return this.size() === 0;
}
clear() {
this.items = {};
this.count = 0;
this.lowestCount = 0;
}
size() {
return this.count - this.lowestCount;
}
toString() {
if (this.isEmpty()) {
return '';
}
let objString = `${this.items[this.lowestCount]}`;
for (let i = this.lowestCount + 1; i < this.count; i++) {
objString = `${objString},${this.items[i]}`;
}
return objString;
}
}複製代碼
首先引入咱們的Queue類,而後初始化建立咱們的Queue類,驗證是否爲空,而後進行添加刪除元素,示例代碼以下:
const queue = new Queue();
console.log(queue.isEmpty()); // outputs true
queue.enqueue('John');
queue.enqueue('Jack');
console.log(queue.toString()); // John,Jack
queue.enqueue('Camila');
console.log(queue.toString()); // John,Jack,Camila
console.log(queue.size()); // outputs 3
console.log(queue.isEmpty()); // outputs false
queue.dequeue(); // remove John
queue.dequeue(); // remove Jack
console.log(queue.toString()); // Camila複製代碼
以下圖所示演示了上述代碼的執行效果:
雙端隊列是一個特殊的更靈活的隊列,咱們能夠在隊列的「隊頭」或「隊尾」添加和刪除元素。因爲雙端隊列是實現了FIFO和LIFO這兩個原則,也能夠說是隊列和堆棧結構的合體結構。
在咱們生活中,好比排隊買票,有的人着急或特殊狀況,直接來到隊伍的最前面,有的人由於其餘的事情,等不了太長時間,從隊尾離開了。
首先咱們聲明初始化一個雙端隊列,代碼和隊列的結構相似,以下段代碼所示:
class Deque {
constructor() {
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
}複製代碼
因爲雙端隊列的結構和隊列的結構相似,只是插入和刪除更靈活而已,isEmpty(), clear(), size()和toString()相關方法保持一致,還須要增長如下相關的方法:
因爲從雙端隊列的的「隊頭」添加元素,稍微複雜些,實現代碼以下:
addFront(element) {
if (this.isEmpty()) {
this.addBack(element);
} else if (this.lowestCount > 0) {
this.lowestCount--;
this.items[this.lowestCount] = element;
} else {
for (let i = this.count; i > 0; i--) {
this.items[i] = this.items[i - 1];
}
this.count++;
this.lowestCount = 0;
this.items[0] = element;
}
}複製代碼
從上述代碼咱們能夠看出,若是雙端隊列爲空,咱們複用了addBack()方法,避免書寫重複的代碼;若是隊頭元素lowestCount的變量大於0,咱們將變量遞減,將新添加的元素賦值給隊頭元素;若是lowestCount的變量爲0,爲了不負值的出現,咱們將隊列元素總體日後移動1位,進行從新賦值,將隊頭索引爲0的位置留給新添加的元素。
因爲文章篇幅有限,其餘的方法又很相似,再也不一一介紹,完整的代碼以下:
export default class Deque {
constructor() {
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
addFront(element) {
if (this.isEmpty()) {
this.addBack(element);
} else if (this.lowestCount > 0) {
this.lowestCount--;
this.items[this.lowestCount] = element;
} else {
for (let i = this.count; i > 0; i--) {
this.items[i] = this.items[i - 1];
}
this.count++;
this.items[0] = 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];
}
isEmpty() {
return this.size() === 0;
}
clear() {
this.items = {};
this.count = 0;
this.lowestCount = 0;
}
size() {
return this.count - this.lowestCount;
}
toString() {
if (this.isEmpty()) {
return '';
}
let objString = `${this.items[this.lowestCount]}`;
for (let i = this.lowestCount + 1; i < this.count; i++) {
objString = `${objString},${this.items[i]}`;
}
return objString;
}
}複製代碼
接下來咱們來驗證下咱們的Deque類,首先引入Deque類的文件,代碼以下:
const deque = new Deque();
console.log(deque.isEmpty()); // outputs true
deque.addBack('John');
deque.addBack('Jack');
console.log(deque.toString()); // John,Jack
deque.addBack('Camila');
console.log(deque.toString()); // John,Jack,Camila
console.log(deque.size()); // outputs 3
console.log(deque.isEmpty()); // outputs false
deque.removeFront(); // remove John
console.log(deque.toString()); // Jack,Camila
deque.removeBack(); // Camila decides to leave
console.log(deque.toString()); // Jack
deque.addFront('John'); // John comes back for information
console.log(deque.toString()); // John,Jack」複製代碼
不知道你們玩過擊鼓傳花嗎,筆者最怕玩這個,不知道是點背還在咋地,這個花球總和我有緣,自己就五音不全還要表演,人可丟大了。什麼是擊鼓傳花,在這裏給沒玩過的朋友解釋下:數人或幾十人圍成圓圈坐下,其中一人拿花(或一小物件);另有一人揹着你們或矇眼擊鼓(桌子、黑板或其餘能發出聲音的物體),鼓響時衆人開始依次傳花,至鼓中止爲止。此時花在誰手中(或其座位前),誰就上臺表演節目(可能是唱歌、跳舞、說笑話;或回答問題、猜謎、按紙條規定行事等);偶然若是花在兩人手中,則兩人可經過猜拳或其它方式決定負者。
今天咱們要用隊列實現這個遊戲,稍微不一樣的是,拿到花球的人須要出列,直到最後一個拿到花球的人獲勝。假設告訴敲鼓的人一個數字(從0開始),按照數字循環在場的人,到達這個數字中止敲鼓,直到最後一我的爲止。
你們是否是火燒眉毛的想知道代碼如何實現?代碼以下所示:
function hotFlower(elementsList, num) {
const queue = new Queue();
const elimitatedList = [];
for (let i = 0; i < elementsList.length; i++) {
queue.enqueue(elementsList[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 = ['John', 'Jack', 'Camila', 'Ingrid', 'Carl'];
const result = hotFlower(names, 7);
result.eliminated.forEach(name => {
console.log(`${name} was eliminated from the Hot Flower game.`);
});
console.log(`The winner is: ${result.winner}`);複製代碼
上述代碼將會輸出:
Camila was eliminated from the Hot Flower game.
Jack was eliminated from the Hot Flower game.
Carl was eliminated from the Hot Flower game.
Ingrid was eliminated from the Hot Flower game.
The winner is: John複製代碼
代碼運行時,隊列的變化示意圖以下:
許多英語單詞不管是順讀仍是倒讀,其詞形和詞義徹底同樣,如dad(爸爸)、noon(中午)、level(水平)等。最簡單的方法就是反轉字符串與原始字符串進行比較是否相等。從數據結構的角度咱們能夠運用堆棧的結構進行實現,然而用雙端隊列的結構實現起來也很是簡單,示例代碼以下:
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;
let firstChar, lastChar;
for (let i = 0; i < lowerString.length; i++) {
deque.addBack(lowerString.charAt(i));
}
while (deque.size() > 1 && isEqual) {
firstChar = deque.removeFront();
lastChar = deque.removeBack();
if (firstChar !== lastChar) {
isEqual = false;
}
}
return isEqual;
}複製代碼
從上述代碼咱們能夠看出:
接下來咱們來驗證下咱們的算法是否正確:
console.log('a', palindromeChecker('a'));
console.log('aa', palindromeChecker('aa'));
console.log('kayak', palindromeChecker('kayak'));
console.log('level', palindromeChecker('level'));
console.log('Was it a car or a cat I saw', palindromeChecker('Was it a car or a cat I saw'));
console.log('Step on no pets', palindromeChecker('Step on no pets'));複製代碼
上述代碼的運行結果都返回爲true。
今天關於隊列的介紹就到這裏,咱們一塊兒學習了什麼是隊列和雙端隊列,以及如何進行代碼實現。而且運用循環隊列的機制實現了擊鼓傳花的遊戲,同時又運用雙端隊列的結構實現了迴文的驗證。其實隊列在咱們的實際業務場景中運用仍是蠻多的,好比咱們要實現一個隊列的消息推送機制,咱們JS的event loop的時間循環機制,瀏覽器的頁面渲染機制等等。但願本篇的內容對你們有所幫助,在實踐中運用多了才能運用隊列的機制解決更多的實際問題。