# 队列
# 队列的基本概念
队列是一种先进先出(FIFO, First-In-First-Out)的数据结构,循环队列是一种头尾相连的队列,概况自维基百科。
队列在生活中的应用也很广泛,比如说食堂打饭,大家都是排成一列,先到的先打饭先走。再比如写 PPT,总是最前面的 PPT 先呈现在观众的眼前,这样的例子在生活中还有很多。。。。。。
# 队列的基本操作
在 Javascript 和 Typescript 中,"数组"天然自带队列的气质,平时开发直接拿来当队列用即可。既然数组自带队列的气质,用其实现队列自然是不在话下,那么请读者思考下,用对象能不能实现一个队列(为啥会有这个想法,获取元素更加高效了)?嗯,对的,是可以的。那么下面我们分别基于数组和对象去实现队列。
理了下队列大致有,入队、出队、返回队头,判空,获取队列长度,转换成字符串,清空等操作。
export class Queue {
constructor() {}
/**
* @description 入队操作
* @param {number} element
* @return {boolean}
*/
enQueue(element: number): boolean {}
/**
* @description 出队操作
* @return {boolean}
*/
deQueue(): boolean {}
/**
* @description 返回队头元素
* @return {number|undefined}
*/
peek(): number | undefined {}
/**
* @description 判断是否为空
* @return {boolean}
*/
isEmpty(): boolean {}
/**
* @description 获取队列长度
* @return {number}
*/
getLength(): number {}
/**
* @description 转字符串输出
* @returns
*/
toString(): string {}
/**
* @description 清空队列
*/
clear(): void {}
}
这里如果是基于数组的方式去实现的话,那么它需要一个数组去记录数据,并对其进行相关的操作,同时需要记录下队头和队的长度。
class Queue {
data: number[];
head: number;
length: number;
constructor() {
this.data = [];
this.head = 0;
this.length = 0;
}
}
如果是基于对象的方式去实现的话,它需要一个对象去记录数据,然后需要记录队头和队尾后一个元素的位置。
import { objectNumberType } from './type';
export class BetterQueue {
data: objectNumberType;
head: number;
tail: number;
constructor() {
this.data = {};
this.head = 0;
this.tail = 0;
}
}
# 入队
如果是基于数组的实现,那么直接塞到数组后面就好,长度追加 1
/**
* @description 入队操作
* @param {number} element
* @return {boolean}
*/
enQueue(element: number): boolean {
this.data.push(element);
this.length++;
return true;
}
如果是基于对象的实现,那么这里就更简单了,直接放置在 tail 元素的位置,然后 tail 的索引加 1
/**
* @description 入队操作
* @param {number} element
* @return {boolean}
*/
enQueue(element: number): boolean {
this.data[this.tail] = element;
this.tail++;
return true;
}
# 出队
如果是基于数组的实现,判断其是否为空,不是的话就把对头元素往后面移一个,是的,我们这里不直接调用数组的 API 把它删除,而是维护数组中的一串先进先出的数据结构。
/**
* @description 出队操作
* @return {boolean}
*/
deQueue(): boolean {
if (this.isEmpty()) {
return false;
}
this.head++;
this.length--;
return true;
}
如果是对象的话,判断其是否为空,不是的话就对头加 1。
/**
* @description 出队操作
* @return {boolean}
*/
deQueue(): boolean {
if (this.isEmpty()) {
return false;
}
this.head++;
return true;
}
# 返回队头
基于数组的实现
/**
* @description 返回队头元素
* @return {number|undefined}
*/
peek(): number | undefined {
return this.data[this.head];
}
基于对象的实现
/**
* @description 返回队头元素
* @return {number|undefined}
*/
peek(): number | undefined {
return this.data[this.head];
}
# 判空
这个两者倒是一样的,长度为 0 不就是空嘛
/**
* @description 判断是否为空
* @return {boolean}
*/
isEmpty(): boolean {
return this.getLength() === 0;
}
# 获取队列长度
基于数组的实现,因为其开始记录并维护了长度,所以这里直接返回
/**
* @description 获取队列长度
* @return {number}
*/
getLength(): number {
return this.length;
}
基于对象的实现,队尾后一个元素减去对头就是所求的长度, 想一下这里我为什么不直接记录队尾啊,额,如果记录队尾还要加 1 把减掉的队头加上了,权衡了下还是记录队尾的下一个吧
/**
* @description 获取队列长度
* @return {number}
*/
getLength(): number {
return this.tail - this.head;
}
# 转字符串
这里都是大同小异,一把遍历出结果。
基于数组的实现。
/**
* @description 转字符串输出
* @returns
*/
toString(): string {
let str = '';
for (let i = 0; i < this.length; i++) {
str = `${str}, ${this.data[this.head + i]}`;
}
return str.slice(2);
}
基于对象的实现
/**
* @description 转字符串输出
* @returns
*/
toString(): string {
if (this.isEmpty()) {
return '';
}
let str = `${this.data[this.head]}`;
for (let i = this.head + 1; i < this.tail; i++) {
str = `${str}, ${this.data[i]}`;
}
return str;
}
# 清空
基于数组的实现
/**
* @description 清空队列
*/
clear(): void {
this.data = [];
this.head = 0;
this.length = 0;
}
基于对象的实现
/**
* @description 清空队列
*/
clear(): void {
this.data = {};
this.head = 0;
this.tail = 0;
}
完整的基于数组的实现
export class Queue {
data: number[];
head: number;
length: number;
constructor() {
this.data = [];
this.head = 0;
this.length = 0;
}
/**
* @description 入队操作
* @param {number} element
* @return {boolean}
*/
enQueue(element: number): boolean {
this.data.push(element);
this.length++;
return true;
}
/**
* @description 出队操作
* @return {boolean}
*/
deQueue(): boolean {
if (this.isEmpty()) {
return false;
}
this.head++;
this.length--;
return true;
}
/**
* @description 返回队头元素
* @return {number|undefined}
*/
peek(): number | undefined {
return this.data[this.head];
}
/**
* @description 判断是否为空
* @return {boolean}
*/
isEmpty(): boolean {
return this.getLength() === 0;
}
/**
* @description 获取队列长度
* @return {number}
*/
getLength(): number {
return this.length;
}
/**
* @description 转字符串输出
* @returns
*/
toString(): string {
let str = '';
for (let i = 0; i < this.length; i++) {
str = `${str}, ${this.data[this.head + i]}`;
}
return str.slice(2);
}
/**
* @description 清空队列
*/
clear(): void {
this.data = [];
this.head = 0;
this.length = 0;
}
}
完整的基于对象的队列
import { objectNumberType } from './type';
export class BetterQueue {
data: objectNumberType;
head: number;
tail: number;
constructor() {
this.data = {};
this.head = 0;
this.tail = 0;
}
/**
* @description 入队操作
* @param {number} element
* @return {boolean}
*/
enQueue(element: number): boolean {
this.data[this.tail] = element;
this.tail++;
return true;
}
/**
* @description 出队操作
* @return {boolean}
*/
deQueue(): boolean {
if (this.isEmpty()) {
return false;
}
this.head++;
return true;
}
/**
* @description 返回队头元素
* @return {number|undefined}
*/
peek(): number | undefined {
return this.data[this.head];
}
/**
* @description 判断是否为空
* @return {boolean}
*/
isEmpty(): boolean {
return this.getLength() === 0;
}
/**
* @description 获取队列长度
* @return {number}
*/
getLength(): number {
return this.tail - this.head;
}
/**
* @description 转字符串输出
* @returns
*/
toString(): string {
if (this.isEmpty()) {
return '';
}
let str = `${this.data[this.head]}`;
for (let i = this.head + 1; i < this.tail; i++) {
str = `${str}, ${this.data[i]}`;
}
return str;
}
/**
* @description 清空队列
*/
clear(): void {
this.data = {};
this.head = 0;
this.tail = 0;
}
}
篇幅问题,这里基于泛型的改造我就不放了,请移步到: https://github.com/ataola/coding-ts/tree/main/code/base
# 循环队列的基本操作
需要实现如下功能
MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。
本题来自 leetcode, https://leetcode-cn.com/leetbook/read/queue-stack/kzlb5/
export class MyCircularQueue {
constructor(k: number) {}
/**
* @description 向循环队列插入一个元素。如果成功插入则返回真。如果入队前是空的,则将head 和 tail 都向右移一位,也就是下标为0的地方; 否则只需右移tail
* @param {number} value
* @return {boolean}
*/
enQueue(value: number): boolean {}
/**
* @description 从循环队列中删除一个元素。如果成功删除则返回真。如果出队时队列不为空且为最后一个元素(head == tail),则置head = tail = -1, 否则只需右移head
* @return {boolean}
*/
deQueue(): boolean {}
/**
* @description 从队首获取元素。如果队列为空,返回 -1 。
* @return {number}
*/
Front(): number {}
/**
* @description 获取队尾元素。如果队列为空,返回 -1 。
* @return {number}
*/
Rear(): number {}
/**
* @description 检查循环队列是否为空。head = tail = -1;
* @return {boolean}
*/
isEmpty(): boolean {}
/**
* @description 检查循环队列是否已满。
* @return {boolean}
*/
isFull(): boolean {}
}
/**
* Your MyCircularQueue object will be instantiated and called as such:
* var obj = new MyCircularQueue(k)
* var param_1 = obj.enQueue(value)
* var param_2 = obj.deQueue()
* var param_3 = obj.Front()
* var param_4 = obj.Rear()
* var param_5 = obj.isEmpty()
* var param_6 = obj.isFull()
*/
这里需要一个数组 data 去存放数据,然后需要一个队头,队尾,还有一个最大尺寸。这里巧妙在,最开始设置 head 和 tail 为-1,可能有些同学想不到。
export class MyCircularQueue {
data: number[];
head: number;
tail: number;
maxSize: number;
constructor(k: number) {
this.data = new Array(k);
this.head = -1;
this.tail = -1;
this.maxSize = k;
}
}
# 入队
向循环队列插入一个元素。如果成功插入则返回真。如果入队前是空的,则将 head 和 tail 都向右移一位,也就是下标为 0 的地方; 否则只需右移 tail
/**
* @description 向循环队列插入一个元素。如果成功插入则返回真。如果入队前是空的,则将head 和 tail 都向右移一位,也就是下标为0的地方; 否则只需右移tail
* @param {number} value
* @return {boolean}
*/
enQueue(value: number): boolean {
if (this.isFull()) {
return false;
}
if (this.isEmpty()) {
this.head++;
this.tail++;
this.data[this.tail] = value;
} else {
this.tail = (this.tail + 1) % this.maxSize;
this.data[this.tail] = value;
}
return true;
}
# 出队
从循环队列中删除一个元素。如果成功删除则返回真。如果出队时队列不为空且为最后一个元素(head == tail),则置 head = tail = -1, 否则只需右移 head
/**
* @description 从循环队列中删除一个元素。如果成功删除则返回真。如果出队时队列不为空且为最后一个元素(head == tail),则置head = tail = -1, 否则只需右移head
* @return {boolean}
*/
deQueue(): boolean {
if (this.isEmpty()) {
return false;
}
delete this.data[this.head];
if (this.head === this.tail) {
this.head = this.tail = -1;
} else {
this.head = (this.head + 1) % this.maxSize;
}
return true;
}
# 获取队首
从队首获取元素。如果队列为空,返回 -1 。
/**
* @description 从队首获取元素。如果队列为空,返回 -1 。
* @return {number}
*/
Front(): number {
if (this.isEmpty()) {
return -1;
}
return this.data[this.head];
}
# 获取队尾元素
从队尾获取元素。如果队列为空,返回 -1 。
/**
* @description 获取队尾元素。如果队列为空,返回 -1 。
* @return {number}
*/
Rear(): number {
if (this.isEmpty()) {
return -1;
}
return this.data[this.tail];
}
# 判空
队头和队尾的位置等于-1 的话就是空
/**
* @description 检查循环队列是否为空。head = tail = -1;
* @return {boolean}
*/
isEmpty(): boolean {
return this.head === this.tail && this.head === -1;
}
# 判满
队尾元素的位置加 1 对最大尺寸取余等于队头的位置就是满了。
/**
* @description 检查循环队列是否已满。
* @return {boolean}
*/
isFull(): boolean {
return (this.tail + 1) % this.maxSize === this.head;
}
# 参考文献
维基百科 - 队列: https://zh.wikipedia.org/wiki/%E9%98%9F%E5%88%97
维基百科 - 循环队列: https://zh.wikipedia.org/wiki/%E7%92%B0%E5%BD%A2%E7%B7%A9%E8%A1%9D%E5%8D%80