当前位置: 首页 > news >正文

【左程云算法07】队列和栈-链表数组实现

目录

​编辑1)队列的介绍

核心操作

3)队列的链表实现和数组实现

使用数组实现队列

2)栈的介绍

核心操作

4)栈的数组实现

使用语言内置的实现

使用数组手动实现栈

5)环形队列的实现 leecode622

代码解析


视频链接
【算法讲解013【入门】队列和栈-链表、数组实现】

1)队列的介绍

先进先出。进了从尾进,从头出。

队列我们认为范围是左闭右开的。范围是[L,R),因此如果L<R,就说明有元素,如果L==R,说明队列里没有元素。

如果我们想加b到R位置,那么我们R++;(原来R在1位置)

如果我们想让数弹出,那么我们拿L位置的数,让L++

队列是一种遵循 先进先出 (First-In, First-Out, FIFO) 原则的线性数据结构。

可以把它想象成现实生活中的排队:最早来排队的人,最先获得服务并离开。在数据结构中,最早被放入(入队)的元素,也最先被取出(出队)。

核心操作

一个基本的队列通常支持以下几种操作:

  • offer(value) (或 enqueue): 将一个元素添加到队尾。

  • poll() (或 dequeue): 从队头取出一个元素,并将其从队列中移除。

  • peek() (或 head): 查看队头的元素,但不移除它。

  • isEmpty(): 判断队列是否为空。

  • size():返回队列中元素的个数。

3)队列的链表实现和数组实现

在很多语言中,都有现成的、基于链表实现的队列结构。例如在 Java 中,LinkedList 类就实现了 Queue 接口。

// 直接用Java内部的实现
// 其实内部就是双向链表,常数操作
public static class Queue1 {// java中的双向链表LinkedList就足够了public Queue<Integer> queue = new LinkedList<>();// 调用任何方法之前,先调用这个方法来判断队内是否有东西public boolean isEmpty() {return queue.isEmpty();}// 向队内加入num, 加到队尾public void offer(int num) {queue.offer(num);}// 从队头拿,从头拿public int poll() {return queue.poll();}
}

使用现成的 LinkedList 来实现队列非常简单,因为其双向链表的结构天然支持在头部和尾部进行 O(1) 复杂度的增删操作,完美契合队列的需求。

使用数组实现队列

在笔试和面试中,更常见的要求是让我们手动用数组来实现一个队列。这更能考察我们对数据结构底层实现的理解。

这是一个基础版的数组队列实现:

// 实际刷题时更常见的写法,常数时间好
// 如果可以确定加入操作的总次数不超过n,那么可以用
// 一般笔试、面试都会有一个明确数据量,所以这是最常用的方式
public static class Queue2 {public int[] queue;public int l; // 头指针public int r; // 尾指针// 加入操作的总次数上限是多少,一定要明确public Queue2(int n) {queue = new int[n];l = 0;r = 0;}// 调用任何方法之前,先调用这个方法来判断队内是否有东西public boolean isEmpty() {return l == r;}// 入队操作public void offer(int num) {queue[r++] = num;}// 出队操作public int poll() {return queue[l++];}// 查看队头public int head() {return queue[l];}// 查看队尾public int tail() {return queue[r - 1];}// 查看大小public int size() {return r - l;}
}

代码解析

  • 结构:我们用一个固定大小的数组 queue 作为容器,并设置两个指针:

    • l (left): 指向队头。下一个要被 poll 的元素就是 queue[l]。

    • r (right): 指向下一个可以插入元素的位置。下一个 offer 的元素将被放入 queue[r]。

  • isEmpty(): 当 l 和 r 指针相遇时 (l == r),说明队列中没有任何元素,队列为空。

  • offer(num): 将元素 num 放入 r 指向的位置,然后将 r 指针后移 (r++)。

  • poll(): 返回 l 指向的元素,然后将 l 指针后移 (l++)。

这种实现的局限性
这个基础版的数组队列有一个明显的问题:指针 l 和 r 只能单向地向右移动。这意味着,即使我们 poll 了很多元素,数组前面空出来的空间也无法被重新利用。当 r 到达数组末尾时,即使队列实际大小很小,我们也无法再 offer 新的元素了。

2)栈的介绍

像弹匣一样,装的时候放在上一个的上面弹出的时候也是上面的先弹出。先d再c等等。

和上面的队类似。

与队列的“先进先出”相反,栈是一种遵循 后进先出 (Last-In, First-Out, LIFO) 原则的线性数据结构。

它最经典的类比就是一摞盘子:我们总是把新盘子放在最上面,而取盘子时,也总是从最上面拿。最后放上去的盘子,最先被取走。

核心操作

一个基本的栈通常支持以下几种操作:

  • push(value): 将一个元素压入栈顶。

  • pop(): 从栈顶弹出一个元素,并将其从栈中移除。

  • peek(): 查看栈顶的元素,但不移除它。

  • isEmpty(): 判断栈是否为空。

  • size():返回栈中元素的个数。

4)栈的数组实现

使用语言内置的实现

Java 提供了 java.util.Stack 类,可以直接使用。它的底层是动态数组 (Vector)。

// 直接用Java内部的实现
// 其实就是动态数组,不过常数时间并不好
public static class Stack1 {public Stack<Integer> stack = new Stack<>();// 调用任何方法之前,先调用这个方法来判断栈内是否有东西public boolean isEmpty() {return stack.isEmpty();}public void push(int num) {stack.push(num);}public int pop() {return stack.pop();}public int peek() {return stack.peek();}public int size() {return stack.size();}
}

使用数组手动实现栈

这是在笔试、面试中考察的重点。我们通过一个数组和一个指针(或索引)来模拟栈的行为。

// 实际刷题时更常见的写法,常数时间好
// 如果可以保证同时在栈里的元素个数不超过n,那么可以用
// 也就是发生弹出操作之后,空间可以复用
// 一般笔试、面试都会有一个明确数据量,所以这是最常用的方式
public static class Stack2 {public int[] stack;public int size; // 指针,指向下一个可插入的位置// 同时在栈里的元素个数不超过npublic Stack2(int n) {stack = new int[n];size = 0;}// 调用任何方法之前,先调用这个方法来判断栈内是否有东西public boolean isEmpty() {return size == 0;}// 入栈public void push(int num) {stack[size++] = num;}// 出栈public int pop() {return stack[--size];}// 查看栈顶元素public int peek() {return stack[size - 1];}// 返回栈中元素数量public int size() {return size;}
}

代码解析

  • 结构:我们使用一个固定大小的数组 stack 和一个整型变量 size。这里的 size 非常巧妙,它既表示了栈中当前的元素数量,也同时扮演了栈顶指针的角色,指向下一个新元素应该被插入的位置。

  • isEmpty(): 当 size 为 0 时,栈为空。

  • push(num): 将新元素 num 放入 stack[size] 的位置,然后将 size 加一 (size++)。

  • pop(): 先将 size 减一 (--size),使其指向当前的栈顶元素,然后返回 stack[size]。注意,数据并没有从数组中被“清除”,但它已经变得不可访问,后续的 push 操作会覆盖它。这就是“空间复用”的体现。

  • peek(): 直接返回 stack[size - 1] 的值,因为 size - 1 正是当前栈顶元素的索引。

这种数组实现方式,所有操作的平均时间复杂度都是 O(1),性能非常好。

5)环形队列的实现 leecode622

举个例子,一共有五个位置,abcd依次放进去,a位置是头,d位置是尾,此时我想把a弹出,就像上文中的队列弹出,空间释放。头往后去。我再弹出个b接着我再加个e呢?

我要是再加个f呢?

但注意,c位置是头。

再加个g呢?

所以这就是个环形结构

所以只要你不同时多于5个在这个队列里,就能一直保持着环形队列继续下去。

那怎么写代码呢?

前提:size允许才能做操作一和操作二

这道题limit就是5

我现在要加入a

我再加个b,再加个c

弹出a

再加个d呢

再弹出b

再加个e

再加个f 放到尾巴的位置,这不就复用了吗?

https://leetcode.cn/problems/design-circular-queue/

// 设计循环队列
// 测试链接 : https://leetcode.cn/problems/design-circular-queue/
class MyCircularQueue {public int[] queue;public int l; // 头指针public int r; // 尾指针public int size; // 当前队列大小public int limit; // 队列容量// 构造器,设置队列长度为 kpublic MyCircularQueue(int k) {queue = new int[k];l = r = size = 0;limit = k;}// 向循环队列插入一个元素。如果成功插入则返回真public boolean enQueue(int value) {if (isFull()) {return false;} else {queue[r] = value;// r++, 结束了,跳回0r = r == limit - 1 ? 0 : (r + 1);size++;return true;}}// 从循环队列中删除一个元素。如果成功删除则返回真public boolean deQueue() {if (isEmpty()) {return false;} else {// l++, 结束了,跳回0l = l == limit - 1 ? 0 : (l + 1);size--;return true;}}// 从队首获取元素。如果队列为空,返回 -1public int Front() {if (isEmpty()) {return -1;} else {return queue[l];}}// 获取队尾元素。如果队列为空,返回 -1public int Rear() {if (isEmpty()) {return -1;} else {// r 指向的是下一个要插入的位置,所以队尾元素在 r 的前一个位置// 需要计算 r 的前一个位置,同样要考虑循环int last = r == 0 ? (limit - 1) : (r - 1);return queue[last];}}// 检查循环队列是否为空public boolean isEmpty() {return size == 0;}// 检查循环队列是否已满public boolean isFull() {return size == limit;}
}

代码解析

  • 成员变量

    • l 和 r:与之前一样,分别是头指针和尾指针。

    • limit:数组的总容量,即队列的容量上限。

    • size:核心变量。我们引入一个 size 变量来实时记录队列中元素的个数。这使得判断队列是“空”还是“满”变得极其简单,避免了复杂的指针位置判断。

  • enQueue(value) 入队

    1. 首先通过 isFull() 判断队列是否已满。

    2. queue[r] = value;:在尾指针 r 的位置放入新元素。

    3. r = r == limit - 1 ? 0 : (r + 1);:环形逻辑的关键。更新尾指针 r。如果 r 已经到达数组的最后一个位置 (limit - 1),则下一步就让它跳回到 0;否则,就正常 +1。

    4. size++:队列大小加一。

  • deQueue() 出队

    1. 首先通过 isEmpty() 判断队列是否为空。

    2. l = l == limit - 1 ? 0 : (l + 1);:环形逻辑的关键。更新头指针 l。与 r 的逻辑完全相同,如果 l 到达末尾,就跳回 0。

    3. size--:队列大小减一。

  • Front() 查看队头

    • 如果队列不为空,队头元素就是 l 指针指向的位置 queue[l]。

  • Rear() 查看队尾

    • 这是最需要注意的地方。因为 r 指向的是下一个将要插入的位置,所以真正的队尾元素在 r 的前一个位置。

    • int last = r == 0 ? (limit - 1) : (r - 1);:计算 r 的前一个位置,同样需要考虑环形。如果 r 当前在 0,那么它的前一个位置就是数组的末尾 limit - 1;否则,就是 r - 1。

    • 返回 queue[last] 即可。

  • isEmpty() 和 isFull()

    • 有了 size 变量,这两个判断变得无比清晰:size == 0 即为空,size == limit 即为满。


文章转载自:

http://IOPq2HKb.psLzp.cn
http://56aThq13.psLzp.cn
http://dJ1zR7zx.psLzp.cn
http://5ONQyfkb.psLzp.cn
http://MqtNf7r8.psLzp.cn
http://BDQRrBQ8.psLzp.cn
http://bLCzVqx7.psLzp.cn
http://7R6sro5L.psLzp.cn
http://kqrrjiTV.psLzp.cn
http://1p8s4UHx.psLzp.cn
http://R4lTs1MY.psLzp.cn
http://wfNzwPUQ.psLzp.cn
http://19K7dpaA.psLzp.cn
http://iFxfdl8C.psLzp.cn
http://S3g4SV1H.psLzp.cn
http://BnJC5xEP.psLzp.cn
http://VhA8M8eM.psLzp.cn
http://76wwVMRe.psLzp.cn
http://oY07AZmC.psLzp.cn
http://kyK7zwmJ.psLzp.cn
http://d7l06kih.psLzp.cn
http://QpjJjPn1.psLzp.cn
http://tTwZNKR9.psLzp.cn
http://XIoYvD4N.psLzp.cn
http://v0oEQsxG.psLzp.cn
http://6fGIDjCq.psLzp.cn
http://VAUSkq6p.psLzp.cn
http://AgSX7IjL.psLzp.cn
http://crmmNa6P.psLzp.cn
http://8VcitsOM.psLzp.cn
http://www.dtcms.com/a/379527.html

相关文章:

  • 关于亚马逊账号关联的思考——关于侵权
  • 【硬件-笔试面试题-84】硬件/电子工程师,笔试面试题(知识点:MOS管是损耗有哪些)
  • mybatis vs mybatis-plus
  • 网络诊断和通信中非常重要的工具或协议
  • Mysql主键选取
  • 蓝桥杯嵌入式
  • Python学习——字典和文件
  • urllib的使用
  • AFSim2.9.0学习笔记 —— 4.1、创建项目,以此项目介绍工作中Wizard使用(红方/蓝方武器平台、阵营、更换图标等,多图详细介绍)
  • 机器人驭风而行:低空经济如何开启智能新纪元【科普类】
  • 【论文速读】LLM Compiler:并行函数调用的新范式
  • 【复习】计网每日一题---海明校验码
  • CVPR 2025最佳论文解读|VGGT:Visual Geometry Grounded Transformer
  • 深度学习里的树模型TabNet
  • 洛谷P5250 【深基17.例5】木材仓库 (集合法)详解
  • zsn的作品集
  • 磁共振成像原理(理论)6:自由感应衰减 (Free Induction Decays)
  • 第3节-使用表格数据-CHECK约束
  • 彻底解决Qt中文乱码以及汉字编码的问题(UTF-8/GBK)
  • 【观察】傅建平:迈向“数据强国”,打通数据要素化“任督二脉”的三把钥匙
  • 一些常用的CAPL小功能
  • 当Claude Code失灵,Qwen Code能否成为你的救星?
  • Spring 项目骨架
  • C++轻量级配置管理器升级版
  • WiFi CSI标准
  • 9、从水果店账本到AI大脑:矩阵运算如何驱动现代人工智能?零基础完全指南(Transformer数学原理)
  • 外部碎片和内部碎片
  • Product Hunt 每日热榜 | 2025-09-11
  • 【前沿技术拓展Trip Two】具身智能
  • LeetCode 1658. 将x减到0的最小操作数