队列是遵循先进先出(FIFO,也称先来先服务)原则的一组有序的项。队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。
在现实中,最常见的队列的例子就是排队。在电影院,学校饭堂,我们都会排队。排在第一位的人会先接受服务。
4.1 创建队列
创建队列类Queue
我们规划一下队列可用的方法:
**enqueue(element)**:向队列尾部添加一个(或)多个新的项。
**dequeue()**:移除队列的第一项(即排在队列最前面的项)并返回被移除的元素
**peek()**:返回队列中第一个元素,即最先添加的元素
**isEmpty()**:如果队列中不包含任何元素,返回true,否则返回false
**size()**:返回队列包含的元素个数。
**clear()**:清空队列
**toString()**:转成String
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| class Queue { _count = 0; _lowestCount = 0; _items = {}; constructor() { }
enqueue(element) { this._items[this._count] = element; this._count++; }
dequeue() { if (this.isEmpty()) { return undefined; } let 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; }
size() { return this._count - this._lowestCount; }
clear() { this._items = {}; this._count = 0; this._lowestCount = 0; }
toString() { if (this.isEmpty()) { return ''; } let str = this._items[this._lowestCount]; for (let i = this._lowestCount + 1; i < this._count; i++) { str += ',' + this._items[i]; } return str; } }
|
使用队列:
1 2 3 4 5 6 7 8 9
| let queue = new Queue(); console.log(queue.isEmpty()); queue.enqueue('张三'); queue.enqueue('李四'); console.log(queue.toString()) console.log(queue.size()); console.log(queue.peek()); queue.dequeue(); console.log(queue.toString())
|
4.2 双端队列
双端队列(deque,或称double-ended queue) 是一种允许我们同时从前端和后端添加和移除元素的特殊队列。
双端队列在现实生活中的例子有电影院、餐厅中排队的队伍等。举个栗子,一个刚买了票的人如果只是还需要再问一些简单的信息,就可以直接回到队伍的头部。另外,在队伍末尾的人如果赶时间,他可以直接离开队伍。
在计算机科学中,双端队列的一个常见应用是存储一系列的撤销操作。每当用户在软件中进行了一个操作,该操作会被存在一个双端队列中。当用户点击撤销按钮时该操作会被从双端队列中弹出,表示它被从后面移除了。在进行了预先定义的一定数量的操作后,最先进行的操作会被从双端队列的前端移除。由于双端队列同时遵守了先进先出****和后进先出**原则,可以说它是把对了和栈相结合的一种数据结构。
既然双端队列是一种特殊的队列,我们可以看到其有多个方法的代码与队列相同
**isEmpty()**:如果队列中不包含任何元素,返回true,否则返回false
**size()**:返回队列包含的元素个数。
**clear()**:清空队列
**toString()**:转成String
我们再规划一下其他可用的方法:
**addFront(element)**:在双端队列前端添加新元素
**addBack(element)**:在双端队列后端新增新的元素
**removeFront()**:从双端队列前端移除第一个元素,并返回该元素
**removeBack()**:从双端队列后端移除第一个元素,并返回该元素
**peekFront()**:返回双端队列前端第一个元素
**peekBack()**:返回双端队列后端第一个元素
实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
| class Deque { _count = 0; _lowestCount = 0; _items = {}; constructor() { }
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; } let result = this._items[this._lowestCount]; delete this._items[this._lowestCount]; this._lowestCount++; return result; }
removeBack() { if (this.isEmpty()) { return undefined; } this._count-- let result = this._items[this._count]; delete this._items[this._count]; return result; }
peekFront() { if (this.isEmpty()) { return undefined; } return this._items[this._lowestCount]; }
peekBack() { return this._items[this._count - 1]; }
isEmpty() { return this.size() === 0; }
size() { return this._count - this._lowestCount; }
clear() { this._items = {}; this._count = 0; this._lowestCount = 0; }
toString() { if (this.isEmpty()) { return ''; } let str = this._items[this._lowestCount]; for (let i = this._lowestCount + 1; i < this._count; i++) { str += ',' + this._items[i]; } return str; } }
|
使用双端队列Deque类
1 2 3 4 5 6 7 8 9 10
| let deque = new Deque(); console.log(deque.isEmpty()); deque.addBack('张三'); deque.addBack('李四'); deque.addFront('王五'); console.log(deque.toString()) deque.removeFront(); console.log(deque.toString()); deque.removeBack(); console.log(deque.toString());
|
4.3 使用队列和双端队列来解决问题
4.3.1 击鼓传花游戏
击鼓传花游戏:孩子们围成一个圆圈,把花尽快地传给旁边的人。某一时刻传花停止。这个时候花在谁手里,谁就退出圆圈,结束游戏。重复这个过程,直到只剩一个孩子(胜者)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
function hotPotato(list, num) { let queue = new Queue(); let eliminatedList = []; 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()) } eliminatedList.push(queue.dequeue()); } return { eliminate: eliminatedList, winner: queue.dequeue() } }
|
应用hotPotato()算法:
1 2 3 4 5 6
| const names = ['张三','李四','王五','John','Jack']; const result = hotPotato(names,8); result.eliminate.forEach(name => { console.log(name + '在击鼓传花游戏中被淘汰。') }) console.log('胜利者:' + result.winner)
|
以上输出如下:

4.3.2 回文检查器
回文是正反都能读通的单词,词组、数或一系列字符的序列,例如:madam或racecar。
有不同的算法可以检查一个词组或字符串是否为回文。最简单的方式是将字符串反向排列并检查它和原字符串是否相同。如果两者相同,那么它就是一个回文。我们也可以用栈来完成,但是利用数据结构来解决这个问题的最简单方法就是使用双端队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
function palindromeDetector(content) { if (content === undefined || content === null || content.length === 0) { return false; } let deque = new Deque(); let lowerString = content.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;
}
|
我们来应用一下**palindromeDetector()**算法:
1 2 3 4
| console.log('a',palindromeDetector('a')); console.log('aa',palindromeDetector('aa')); console.log('kayak',palindromeDetector('kayak')); console.log('hello',palindromeDetector('hello'));
|