四、队列与栈的封装
队列(Queue)和栈(Stack)是两种经常使用的数据结构,其中:
队列是一种先进先出(First In First Out, FIFO)的数据结构,它允许在尾部添加元素,在头部移除元素;
栈则是一种先进后出(First In Last Out, FILO)的数据结构,它允许在尾部添加元素,在尾部移除元素。
实际上,JS/TS内置的Array数组对象已经添加了对队列和栈的支持,而我们自行实现的LinkList双向循环列表,也能支持队列和栈的相关操作。
更进一步,我们会发现队列和栈的添加元素操作具有一致性行为,仅仅是移除元素时方向的不同而已,队列从头部移除元素,而栈是从尾部移除元素。
因此我们使用统一的接口,在内部对JS/TS Array对象和我们实现的List对象进行封装,实现一个适配器模式的队列与栈数据结构。这个统一的接口队列与栈接口将会非常有利于下一节介绍的树数据结构中各种迭代器的算法实现。
新建 IAdapter.ts,声明 IAdapter<T> 类型的泛型接口,该接口统一了队列和栈的接口方法:
/*
* @Description:
* @Author: tianyw
* @Date: 2023-01-28 21:44:18
* @LastEditTime: 2023-01-28 21:44:41
* @LastEditors: tianyw
*/
export interface IAdapter<T> {
add(t: T): void; // 将t入队列或堆栈
remove(): T | undefined; // 弹出队列或堆栈顶部的元素
clear(): void; // 清空队列或堆栈,用于重用
//属性
length: number; // 当前队列或堆栈的元素个数
isEmpty: boolean; // 判断当前队列或堆栈是否为空
}
新建 AdapterBase.ts,声明 AdapterBase<T> :
/*
* @Description:
* @Author: tianyw
* @Date: 2023-01-28 21:44:54
* @LastEditTime: 2023-01-28 21:54:19
* @LastEditors: tianyw
*/
import { IAdapter } from "./IAdapter";
import { LinkList } from "./LinkList";
// AdapterBase 抽象基类做了 3 件事
// 1、实现了队列和栈共同具有的操作
// 2、将不同的操作以抽象方法的方式留给具体实现者
// 3、在构造函数中根据布尔变量来决定是使用 LinkList 还是用 JS/TS Array
export abstract class AdapterBase<T> implements IAdapter<T> {
// 内部封装了 JS/TS 内置的 Array 对象或我们自行实现的 List 双向循环链表对象
protected _arr: Array<T> | LinkList<T>;
// 构造函数 参数 useList 用来指明我们是用链表还是用 TS/JS Array 来实现底部增删操作
public constructor(useLinkList: boolean = true) {
if(useLinkList === true) {
this._arr = new LinkList<T>(); // 使用链表对象
}else{
this._arr = new Array<T>(); // 使用 JS/TS Array 对象
}
}
// 在容器的尾部添加一个元素
public add(t: T): void {
this._arr.push(t);
}
// 抽象方法 这是因为队列和栈具有不同的操作方法
// 子类需要自行实现正确的方法
public abstract remove(): T | undefined;
// 当前队列或栈的元素个数
public get length(): number {
return this._arr.length;
}
// 判断队列或栈是否为空
public get isEmpty(): boolean {
return this._arr.length <= 0;
}
// 清空队列或栈
public clear(): void {
// 简单起见,直接重新创建一个新的底层容器对象
// 需要使用 instanceof 判断类型
if(this._arr instanceof LinkList) {
this._arr = new LinkList<T>();
}else{
this._arr = new Array<T>();
}
}
// public toString(): string {
// return this._arr.toString();
// }
}
新建 Queue.ts,声明队列子类:
/*
* @Description:
* @Author: tianyw
* @Date: 2023-01-28 21:54:30
* @LastEditTime: 2023-01-28 22:00:11
* @LastEditors: tianyw
*/
import { AdapterBase } from "./AdapterBase";
export class Queue<T> extends AdapterBase<T> {
public remove(): T | undefined {
if (this._arr.length > 0) {
// LinkList 和 Array 对于顶部删除具有相同名称的方法 无需额外判断
return this._arr.shift();
} else {
return undefined;
}
}
}
新建 Stack.ts,声明栈子类:
/*
* @Description:
* @Author: tianyw
* @Date: 2023-01-28 22:00:20
* @LastEditTime: 2023-01-28 22:01:19
* @LastEditors: tianyw
*/
import { AdapterBase } from "./AdapterBase";
export class Stack<T> extends AdapterBase<T> {
public remove(): T | undefined {
if (this._arr.length > 0) {
return this._arr.pop();
} else {
return undefined;
}
}
}
使用 demo,运行效果:
本章参考如下:
《TypeScript 图形渲染实战——基于WebGL的3D架构与实现》