数据结构之栈与队列

栈遵循先入后出的原则,因此我们只能在栈顶添加或删除元素。然而,数组和链表都可以在任意位置添加和删除元素,因此栈可以视为一种受限制的数组或链表。换句话说,我们可以"屏蔽"数组或链表的部分无关操作,使其对外表现的逻辑符合栈的特性。

所以栈只能改变栈顶的数据

具体实现一个栈(链表实现):

kotlin 复制代码
/* 基于链表实现的栈 */
class LinkedListStack {
    #stackPeek; // 将头节点作为栈顶
    #stkSize = 0; // 栈的长度
​
    constructor() {
        this.#stackPeek = null;
    }
​
    /* 获取栈的长度 */
    get size() {
        return this.#stkSize;
    }
​
    /* 判断栈是否为空 */
    isEmpty() {
        return this.size === 0;
    }
​
    /* 入栈 */
    push(num) {
        const node = new ListNode(num);
        node.next = this.#stackPeek;
        this.#stackPeek = node;
        this.#stkSize++;
    }
​
    /* 出栈 */
    pop() {
        const num = this.peek();
        this.#stackPeek = this.#stackPeek.next;
        this.#stkSize--;
        return num;
    }
​
    /* 访问栈顶元素 */
    peek() {
        if (!this.#stackPeek) throw new Error('栈为空');
        return this.#stackPeek.val;
    }
​
    /* 将链表转化为 Array 并返回 */
    toArray() {
        let node = this.#stackPeek;
        const res = new Array(this.size);
        for (let i = res.length - 1; i >= 0; i--) {
            res[i] = node.val;
            node = node.next;
        }
        return res;
    }
}

当然栈也可以使用数组实现,具体的优劣如下:

1.数据的平均效率是更高的因为一般数组是一段连续的内存

2.但是数组是需要动态扩容的,所以在面临动态扩容的时候下一次操作的时间复杂度会变成O(n)

浏览器撤销与栈

浏览器撤销功能的数据结构就是栈,一般而言有两个栈一个为现有使用的一个为被撤回的,当一个撤回操作进行的时候,其栈顶元素被弹出到另外一个栈顶,此时如果用户实行反撤回,则需要从另外一个栈顶把那个元素弹出来然后回到现有使用的栈

队列

队列有区别与栈,栈是先进后出而队列是先进先出,所以实际上队列我们需要关注的不止是头而且也要关注尾。

这里附一个使用数组的队列(因为有一些地方可以优化)

kotlin 复制代码
/* 基于环形数组实现的队列 */
class ArrayQueue {
    #nums; // 用于存储队列元素的数组
    #front = 0; // 队首指针,指向队首元素
    #queSize = 0; // 队列长度
​
    constructor(capacity) {
        this.#nums = new Array(capacity);
    }
​
    /* 获取队列的容量 */
    get capacity() {
        return this.#nums.length;
    }
​
    /* 获取队列的长度 */
    get size() {
        return this.#queSize;
    }
​
    /* 判断队列是否为空 */
    isEmpty() {
        return this.#queSize === 0;
    }
​
    /* 入队 */
    push(num) {
        if (this.size === this.capacity) {
            console.log('队列已满');
            return;
        }
        // 计算队尾指针,指向队尾索引 + 1
        // 通过取余操作实现 rear 越过数组尾部后回到头部
        const rear = (this.#front + this.size) % this.capacity;
        // 将 num 添加至队尾
        this.#nums[rear] = num;
        this.#queSize++;
    }
​
    /* 出队 */
    pop() {
        const num = this.peek();
        // 队首指针向后移动一位,若越过尾部,则返回到数组头部
        this.#front = (this.#front + 1) % this.capacity;
        this.#queSize--;
        return num;
    }
​
    /* 访问队首元素 */
    peek() {
        if (this.isEmpty()) throw new Error('队列为空');
        return this.#nums[this.#front];
    }
​
    /* 返回 Array */
    toArray() {
        // 仅转换有效长度范围内的列表元素
        const arr = new Array(this.size);
        for (let i = 0, j = this.#front; i < this.size; i++, j++) {
            arr[i] = this.#nums[j % this.capacity];
        }
        return arr;
    }
}

讲讲具体优化在哪,优化在于一般而言删除数组的头元素的时间复杂度为o(n)会比较麻烦,所以这里使用了两个变量来控制位置把删除带来的影响给修正了,也就是说在这种情况下不存在真正的删除,只是通过环形数组,在即将发生越界的情况的时候,把尾指针/头指针循环回去了,但是依旧遵从先进先出这一核心思想

双向队列

双向队列是队列的加强版在原有的基础上增加了队首加入和队尾弹出的功能

相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁2 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄2 天前
JavaScript 隐式全局变量解析
javascript
汉堡大王95272 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大2 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师2 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端