夯下数据结构和算法基础,JS 里没有栈、队列、链表巴拉巴拉明显的结构,只能用类去伪造,不然做算法题真的费劲。
这次造普通队列类吧,这边底层使用数组,当然有空的话,你也可以试试对象。
基本概念
- 队列是一种遵循先进先出(FIFO,First In First Out)原则的有序集合。
- 新添加的或待删除的元素都保存在队列的一端,称为“队尾”,另一端就叫“队头”。
- 队列是一种操作受限制的线性表,只允许在一端插入数据,在另一端删除数据。
- 队列的插入操作叫做入队,删除操作叫做出队。
应用场景
队列的应用场景:银行排队、打印机打印任务、操作系统中的进程调度、浏览器的前进后退、任务队列、事件队列、Promise队列等等。
基本方法
- enqueue(element(s)) 向队列尾部添加一个(或多个)新的项。
- dequeue() 移除队列的第一(即排在队列最前面的)项,并返回被移除的元素。
- front() 返回队列中第一个元素——最先被添加,也将是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息——与 Stack 类的 peek 方法非常类似)。
- size() 返回队列包含的元素个数,与数组的 length 属性类似。
- isEmpty() 如果队列中不包含任何元素,返回 true,否则返回 false。
- clear() 移除队列里的所有元素。
类实现
class Queue {
queue: any[];
constructor() {
this.queue = [];
}
enqueue(value: any) {
this.queue.enqueue(value);
}
dequeue() {
return this.queue.shift();
}
// 这里注意是队前面的第一个
front() {
return this.queue[0];
}
size() {
return this.queue.length;
}
isEmpty() {
return this.size() === 0;
}
clear() {
this.queue = [];
}
}
可以头上顶个注释,就容易调用方法了
/**
* Queue
* 队列是一种遵循先进先出(FIFO,First In First Out)原则的有序集合。
* 新添加的或待删除的元素都保存在队列的一端,称为“队尾”,另一端就叫“队头”。
* 队列是一种操作受限制的线性表,只允许在一端插入数据,在另一端删除数据。
* 队列的插入操作叫做入队,删除操作叫做出队。
* @description 队列
* @class Queue
* @property queue 队列内部存储的数组
* @method enqueue(element(s)) 向队列尾部添加一个(或多个)新的项。
* @method dequeue() 移除队列的第一(即排在队列最前面的)项,并返回被移除的元素。
* @method front() 返回队列中第一个元素——最先被添加,也将是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息——与 Stack 类的 peek 方法非常类似)。
* @method size() 返回队列包含的元素个数,与数组的 length 属性类似。
* @method isEmpty() 如果队列中不包含任何元素,返回 true,否则返回 false。
* @method clear() 移除队列里的所有元素。
* @example
* const q = new Queue()
* q.enqueue(1)
* q.enqueue(2)
* q.enqueue(3)
* q.dequeue() // 1
* q.dequeue() // 2
* q.dequeue() // 3
* q.dequeue() // undefined
* q.enqueue(1)
* q.enqueue(2)
* q.front() // 1
* q.size() // 2
* q.isEmpty() // false
* q.clear()
* q.isEmpty() // true
*/
练习:实现击鼓传花的游戏
击鼓传花的规则是:有 n 个人围成一圈,从第一个人开始报数,数到 m 的人出局,然后从下一个人重新开始报数,数到 m 的人再出局,直到剩下最后一个人为止。
利用循环队列 可以实现击鼓传花的游戏。
循环队列是一种特殊的队列,当队列中的元素个数达到队列的最大容量时,再往队列中添加元素,会覆盖掉队列中最早添加的元素。
游戏实现的主要逻辑:
- 参赛者形成参赛者队列
- 每一次传花的时候,传完花的出队,然后重新从队尾进队(循环队列的威力,也是最核心的地方)
- 每一轮传花完毕之后,拿花的人也就是队头的第一个人,出队,进去淘汰者列表(回不去嘞),参赛者队列减少
- 直到参赛者队列只剩一个人的时候,比赛结束,那人获胜
function hotPotato(nameList: any[], num: number) {
// 传花队列,这个队列始终是参赛者,但是参赛者会越来越少
const queue = new Queue()
// 淘汰列表
const eliminatedList: any[] = []
// 将参赛者一个个放进队列
for (let i = 0; i < nameList.length; i++) {
queue.enqueue(nameList[i])
}
// 队列里只剩一个人的时候,比赛终止,也就是循环终止条件
while (queue.size() !== 1) {
// 开始传花,传num次
for (let i = 0; i < num; i++) {
// 传完的人,出队,然后重新排到队尾(循环)
queue.enqueue(queue.dequeue())
}
// 一轮之后,拿花的话也就是队首人出队,进去淘汰者列表
eliminatedList.enqueue(queue.dequeue())
}
return {
eliminated: eliminatedList,
// 队里唯一的人
winner: queue.front()
}
}
加上注释的话
/**
* 击鼓传花的规则是:有 n 个人围成一圈,从第一个人开始报数,数到 m 的人出局,然后从下一个人重新开始报数,数到 m 的人再出局,直到剩下最后一个人为止。
* 可以利用循环队列实现击鼓传花的游戏。
* 循环队列是一种特殊的队列,当队列中的元素个数达到队列的最大容量时,再往队列中添加元素,
* 会覆盖掉队列中最早添加的元素。
* @description 击鼓传花
* @param {Array} nameList 参与游戏的人
* @param {Number} num 数到几的人出局
* @returns {Object} 最后剩下的人和出局的人
* @example
* const nameList = ['a', 'b', 'c', 'd', 'e', 'f']
* const num = 3
* drumAndPass(nameList, num) // { eliminated: [ 'c', 'f', 'd', 'b', 'e' ], winner: 'a' }
* @example
* const nameList = ['a', 'b', 'c', 'd', 'e', 'f']
* const num = 2
* drumAndPass(nameList, num) // { eliminated: [ 'b', 'e', 'a', 'f', 'd' ], winner: 'c' }
* @example
* const nameList = ['a', 'b', 'c', 'd', 'e', 'f']
* const num = 7
* drumAndPass(nameList, num) // { eliminated: [ 'f', 'e', 'd', 'c', 'b' ], winner: 'a' }
* @example
* const nameList = ['a', 'b', 'c', 'd', 'e', 'f']
* const num = 1
* drumAndPass(nameList, num) // { eliminated: [ 'a', 'b', 'c', 'd', 'e' ], winner: 'f' }
*/
引用
- 《JavaScript 数据结构与算法》(希望我能看完)