https://www.jianshu.com/p/ca1bb95ada76
队列
队列(Queue),它是一种运算受限的线性表,先进先出(FIFO First In First Out)
队列是一种受限的线性结构, 受限之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作
应用举例:线程队列
在开发中为了让任务可以并行处理通常会开启多个线程.
但不能让大量线程同时运行处理任务,会占用过多资源,因此使用线程队列.
线程队列会按照次序来启动线程并且处理对应的任务.
基于数组实现队列
// 自定义队列
function Queue() {
var items = []
// 队列操作的方法
// enter queue方法
this.enqueue = function (element) {
items.push(element)
}
// delete queue方法
this.dequeue = function () {
return items.shift()
}
// 查看前端的元素
this.front = function () {
return items[0]
}
// 查看队列是否为空
this.isEmpty = function () {
return items.length == 0
}
// 查看队列中元素的个数
this.size = function () {
return items.length
}
}
基于对象实现队列
创建类来表示一个队列.
使用对象存储元素, 使用count属性来控制队列大小, 使用lowestCount来追踪第一个元素.
然后声明一些队列可用的方法.
// @ts-check
export default class Queue {
constructor() {
this.count = 0; // 控制队列大小,队列大小计数
this.lowestCount = 0; // 记录队列首项的下标值,用于追踪队列首项
this.items = {}; // 使用对象存储元素
}
// 向队列尾部添加新的项
enqueue(element) {
// items属性是对象,是键值对的集合
// 因此向队列尾部添加新的项可以把count变量作为items对象中的键key,对应的元素element作为它的值value.
this.items[this.count] = element;
this.count++;
}
// 移除队列首项并返回被移除的元素
dequeue() {
// 若队列为空,返回undefined
if (this.isEmpty()) {
return undefined;
}
// 使用result暂存队列首项,以便delete移除队列首项后将其返回
const result = this.items[this.lowestCount];
delete this.items[this.lowestCount];
// lowestCount 计数加 1
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() {
// 计算队列中有多少元素,只需计算count和lowestCount之间的差值即可
// 比如count为2,lowestCount为0, 则说明队列中存在下标为0/1的两个元素
// 然后从队列中移除一个元素,lowestCount值变为1, count仍为2,则队列中只有下标为1的一个元素
return this.count - this.lowestCount;
}
// 从索引值为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;
}
}
双端队列
允许从后端和前端 添加和移除元素的特殊队列
同时遵循先进先出和后进先出的原则, 可以说是队列和栈相结合的一种数据结构.
// @ts-check
// 双端队列结构
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 { // this.lowestCount为0
// 在第一位添加元素,需要将所有元素后移一位
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;
}
}
优先队列
优先级队列的特点:
普通的队列插入一个元素, 数据会被放在后端. 并且需要前面所有的元素都处理完成后才会处理前面的数据.
但是优先级队列, 在插入一个元素的时候会考虑该数据的优先级.(和其他数据优先级进行比较)
比较完成后, 可以得出这个元素正确的队列中的位置. 其他处理方式, 和队列的处理方式一样.
也就是说, 如果我们要实现优先级队列, 最主要是要修改添加方法. (当然, 还需要以某种方式来保存元素的优先级)
优先级队列的实现
实现优先级队列相对队列主要有两方面需要考虑:
封装元素和优先级放在一起(可以封装一个新的构造函数)
添加元素时, 将当前的优先级和队列中已经存在的元素优先级进行比较, 以获得自己正确的位置.
// 封装优先级队列
function PriorityQueue() {
var items = []
// 封装一个新的构造函数, 用于保存元素和元素的优先级
function QueueElement(element, priority) {
this.element = element
this.priority = priority
}
// 添加元素的方法
this.enqueue = function (element, priority) {
// 1.根据传入的元素, 创建新的QueueElement
var queueElement = new QueueElement(element, priority)
// 2.获取传入元素应该在正确的位置
if (this.isEmpty()) {
items.push(queueElement)
} else {
var added = false
for (var i = 0; i < items.length; i++) {
// 注意: 我们这里是数字越小, 优先级越高
if (queueElement.priority < items[i].priority) {
items.splice(i, 0, queueElement)
added = true
break
}
}
// 遍历完所有的元素, 优先级都大于新插入的元素时, 就插入到最后
if (!added) {
items.push(queueElement)
}
}
}
// 删除元素的方法
this.dequeue = function () {
return items.shift()
}
// 获取前端的元素
this.front = function () {
return items[0]
}
// 查看元素是否为空
this.isEmpty = function () {
return items.length == 0
}
// 获取元素的个数
this.size = function () {
return items.length
}
}
代码解析:
封装了一个QueueElement, 将element和priority封装在一起.
在插入新的元素时, 有如下情况下考虑:
- 根据新的元素先创建一个新的QueueElement对象.
- 如果元素是第一个被加进来的, 那么不需要考虑太多, 直接加入数组中即可.
- 如果是后面加进来的元素, 需要和前面加进来的元素依次对比优先级.
一旦优先级, 大于某个元素, 就将该元素插入到元素这个元素的位置. 其他元素会依次向后移动. - 如果遍历了所有的元素, 没有找到某个元素被这个新元素的优先级低, 直接放在最后即可.
作者:coderwhy
链接:https://www.jianshu.com/p/ca1bb95ada76
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
队列解决问题- 击鼓传花, 转圈报数
// 实现击鼓传花的函数
function passGame(nameList, num) {
// 1.创建一个队列, 并且将所有的人放在队列中
// 1.1.创建队列
var queue = new Queue()
// 1.2.通过for循环, 将nameList中的人放在队列中
for (var i = 0; i < nameList.length; i++) {
queue.enqueue(nameList[i])
}
// 2.寻找最后剩下的人
while (queue.size() > 1) {
// 将前num-1中的人, 都从队列的前端取出放在队列的后端
for (var i = 0; i < num; i++) {
queue.enqueue(queue.dequeue())
}
// 将第num个人, 从队列中移除
queue.dequeue()
}
// 3.获取剩下的一个人
alert(queue.size())
var endName = queue.dequeue()
alert("最终留下来的人:" + endName)
// 4.获取该人在队列中的位置
return nameList.indexOf(endName)
}