队列
- 队列
- 双端队列数据结构
-
应用
- 用击鼓传花游戏模拟循环队列
- 用双端对列检查一个词是否构成回文
- 生成 1 到 n 的二进制数
队列和双端队列
队列遵循先进后出(FIFO, 也称为先来先服务) 原则的. 日常有很多这样场景: 排队购票、银行排队等.
由对列的特性,银行排队为例, 队列应该包含如下基本操作:
- 加入队列(取号) enqueue
- 从队列中移除(办理业务离开) dequeue
- 当前排队号码(呼叫下一个人) peek
- 当前队列长度(当前排队人数) size
- 判断队列是不是空 isEmpty
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
|
class Queue {
constructor() {
// 队列长度, 类数组 length
this .count = 0
// 队列中所有项
this .items = {}
// 记录对列头, 类数组 index
this .lowestCount = 0
}
enqueue(ele) {
this .items[ this .count++] = ele
}
dequeue() {
if ( this .isEnpty()) {
return undefined
}
const ele = this .items[ this .lowestCount]
delete this .items[ this .lowestCount]
this .lowestCount++
return ele
}
peek() {
if ( this .isEnpty()) {
return
}
return this .items[ this .lowestCount]
}
size() {
/**
* 当队列为非空时:
* 1. count 是长度
* 2. lowestCount 是下标
* 两者关系应该 lowestCount = count - 1
*/
return this .count - this .lowestCount
}
isEnpty() {
return this .size() == 0
}
clear() {
this .items = {}
this .lowestCount = 0
this .count = 0
}
toString() {
if ( this .isEnpty()) {
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 或 double-ended queue)
什么是双端队列?
允许从前端(front)和后端(rear)添加元素, 遵循的原则先进先出或后进先出.
双端队列可以理解为就是栈(后进先出)和队列(先进先出)的一种结合体. 既然是结合那么相应的操作也支持队列,栈的操作. 下面我们定义一个Deque
- addFront
- removeFront
- addBack
- removeBack
- clear
- isEmpty
- peekFront
- prekBack
- size
- toString
- class Deque {
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
|
constructor() {
this .items = {}
this .count = 0
this .lowestCount = 0
}
addFront(ele) {
if ( this .isEmpty()) {
this .items[ this .count] = ele
} else if ( this .lowestCount > 0) {
this .lowestCount -= 1
this .items[ this .lowestCount] = ele
} else {
for (let i = this .count; i > 0; i--) {
this .items[i] = this .items[i - 1]
}
this .items[0] = ele
}
this .count++
return ele
}
removeFront() {
if ( this .isEmpty()) {
return
}
const delEle = this .items[ this .lowestCount]
delete this .items[ this .lowestCount]
this .lowestCount++
return delEle
}
addBack(ele) {
this .items[ this .count] = ele
this .count++
}
removeBack() {
if ( this .isEmpty()) {
return
}
const delEle = this .items[ this .count - 1]
delete this .items[ this .count - 1]
this .count--
return delEle
}
peekFront() {
if ( this .isEmpty()) {
return
}
return this .items[ this .lowestCount]
}
peekBack() {
if ( this .isEmpty()) {
return
}
return this .items[ this .count - 1]
}
size() {
return this .count - this .lowestCount
}
isEmpty() {
return this .size() === 0
}
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
}
}
|
队列的应用
击鼓传花游戏
击鼓传花游戏: 简单描述就是一群人围成一个圈传递花,喊停的时花在谁手上就将被淘汰(每个人都可能在前端,每个参与者在队列位置会不断变化),最后只剩下一个时就是赢者. 更加详细可以自行查阅.
下面通过代码实现:
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
|
function hotPotato(elementsList, num) {
// 创建一个容器
const queue = new Queue()
const elimitatedList = []
// 把元素(参赛者)加入队列中
for (let i = 0, len = elementsList.length; i < len; i++) {
queue.enqueue(elementsList[i])
}
/**
* 击鼓传花
* 首先队列规则: 先进先出
* 那么在传花过程中,任何一个元素都可能是前端, 在传花的过程中应该就是前端位置不断变化.
* 当喊停的时(num 循环完), 也就是花落在谁手(谁在前端)则会被淘汰*(移除队列)
*/
while (queue.size() > 1) {
for (let j = 0; j < num; j++) {
queue.enqueue(queue.dequeue())
}
elimitatedList.push(queue.dequeue())
}
return {
winer: queue.dequeue(),
elimitatedList
}
}
|
代码运行如下:
1
2
3
4
5
|
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(hotPotato(arr, Math.ceil(Math.random() * 10))) // { winer: 5, elimitatedList: [4, 8, 2, 7, 3,10, 9, 1, 6]}
console.log(hotPotato(arr, Math.ceil(Math.random() * 10))) // { winer: 5, elimitatedList: [4, 8, 2, 7, 3,10, 9, 1, 6]}
console.log(hotPotato(arr, Math.ceil(Math.random() * 10))) // { winer: 8, elimitatedList: [10, 1, 3, 6, 2,9, 5, 7, 4]}
|
判断回文
上一篇栈中也有涉及回文的实现, 下面我们通过双端队列来实现同样的功能.
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
|
function palindromeChecker(aString) {
if (!aString || typeof aString !== 'string' || !aString.trim().length) {
return false
}
const deque = new Deque()
const lowerString = aString.toLowerCase().split( ' ' ).join( '' )
// 加入队列
for (let i = 0; i < lowerString.length; i++) {
deque.addBack(lowerString[i])
}
let isEqual = true
let firstChar = ''
let lastChar = ''
while (deque.size() > 1 && isEqual) {
firstChar = deque.removeFront()
lastChar = deque.removeBack()
if (firstChar != lastChar) {
isEqual = false
}
}
return isEqual
}
|
下面通过代码演示下:
1
|
console.log(palindromeChecker( 'abcba' )) // true 当前为回文
|
生成 1 到 n 的二进制数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function generatePrintBinary(n) {
var q = new Queue()
q.enqueue( '1' )
while (n-- > 0) {
var s1 = q.peek()
q.dequeue()
console.log(s1)
var s2 = s1
q.enqueue(s1 + '0' )
q.enqueue(s2 + '1' )
}
}
generatePrintBinary(5) // => 1 10 11 100 101
|
到此这篇关于JS中队列和双端队列实现及应用详解的文章就介绍到这了,更多相关JS 双端队列 内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!
原文链接:https://segmentfault.com/a/1190000025160934