Stack和Queue—模拟实现,实战应用全解析!

news/2025/2/21 10:55:39/

各位看官早安午安晚安呀

如果您觉得这篇文章对您有帮助的话

欢迎您一键三连,小编尽全力做到更好
欢迎您分享给更多人哦

大家好,我们今天来学习java数据结构的Stack和Queue(栈和队列)

一:栈

1.1:栈的概念

栈:入口和出口在一个地方(最先进去的只能最后出来)

想要另一端出去的则是队列(后面我会讲解)

:一种特殊的线性表,其 只允许在固定的一端进行插入和删除元素操作 。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO Last In First Out )的原则

1.2:模拟实现栈

java">package StackSimulate;import java.util.Arrays;public class MyStack {private int usedSize;//栈里的有效元素//如果我们在栈外就不需要这个变量来访问元素的个数了,应该使用size();private int [] elem;//栈底层是由数组实现的,它继承的Vector底层就是数组实现的//当然单双链表也可以,但是单链表尾巴进就麻烦了,没有lastprivate static final int DEFAULT_CAPACITY = 10;public MyStack() {this.elem = new int [DEFAULT_CAPACITY];}//有效元素个数public int size(){return usedSize;}//压栈public void push(int val) {if (isFull()) {elem = Arrays.copyOf(elem, 2 * elem.length);}elem[usedSize] = val;usedSize++;}//判断栈是否满了private boolean isFull() {return usedSize == elem.length;}//判断栈是否为空public boolean empty() {return usedSize == 0;}//出栈public int pop() {if (empty()) {throw new EmptyException();}
//        usedSize--;  //这一步那个栈顶元素就已经没有了,就像5变成了4,
//        return elem[usedSize]; //这样写其实不好,因为这个元素我们认为没有了,但是你还是要去访问int oldVal = elem[usedSize -1];usedSize--;return oldVal;   //这样好一点,我们把这个元素保存起来了,然后返回}//返回栈顶元素public int peek() {if (empty()) {throw new EmptyException();}return elem[usedSize - 1];}}

1.3:模拟栈测试

java">import java.util.LinkedList;public class Test {public static void main(String[] args) {MyStack myStack = new MyStack();myStack.push(10);myStack.push(20);myStack.push(66);myStack.push(66);myStack.push(88);myStack.push(99);System.out.println(myStack.size());System.out.println("=======================");System.out.println(myStack.pop());//99System.out.println(myStack.pop());//88System.out.println(myStack.empty());//falseSystem.out.println(myStack.size());//4System.out.println(myStack.peek());//66System.out.println(myStack.size());//4myStack.pop();myStack.pop();myStack.pop();myStack.pop();//myStack.pop();//抛出异常System.out.println("===================");LinkedList<Integer> stack = new LinkedList<>();stack.push(100);stack.push(100);stack.push(222);stack.push(444);stack.push(666);System.out.println(stack.peek());//666System.out.println(stack);}
}

1.4:关于Stack目前的理解

Stack 继承了 Vector Vector ArrayList 类似,都是动态的顺序表,不同的是 Vector 是线程安
全的

几张图概括:

队列和栈链式实现最好用双向链表

续:

Stack用顺序表这种结构(底层数组),由于自身的特性完美的避开了数组删除,增加元素的高时间复杂度

1.5:例题讲解

1.5.1将递归转化为循环

比如:逆序打印链表
java">    // 递归方式public static void printList(Node head){if(null != head){printList(head.next);System.out.print(head.val + " ");}}// 循环方式public static void printList(Node head){if(null == head){return;}Stack<Node> s = new Stack<>();
// 将链表中的结点保存在栈中Node cur = head;while(null != cur){s.push(cur);cur = cur.next;}// 将栈中的元素出栈while(!s.empty()){System.out.print(s.pop().val + " ");}}

1.5.2:括号匹配

括号匹配
java">class Solution {public boolean isValid(String s) {Stack<Character>stack = new Stack<>();for(int i = 0; i < s.length(); i++){char ch = s.charAt(i);if(ch =='(' || ch =='[' || ch =='{'){//左括号stack.push(ch);}else {if (stack.empty()) { //判断是否为空,防止栈为空,出现空指针异常return false;}char top = stack.peek();if ((ch == ')' && top == '(') || (ch == ']' && top == '[') || (ch == '}' && top == '{')) {//右括号 stack.pop();continue;//跳过后面的return} return false;}}if(stack.empty()){return true;}return false;}
}

1.5.3:逆波兰表达式求值

逆波兰表达式求值

java">class Solution {public int evalRPN(String[] tokens) {Stack <Integer> stack = new Stack<>();for(String s:tokens){if(isOpreations(s)){int ret1 = stack.pop();int ret2 = stack.pop();switch(s){case "+":{stack.push(ret1 + ret2);break;}case "-":{stack.push(ret2 - ret1);break;}case "*":{stack.push(ret1 * ret2);break;}case "/":{stack.push(ret2 / ret1);break;}}}else{stack.push(Integer.parseInt(s));}}return stack.pop();}public boolean isOpreations(String s){if(s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/")){return true;}return false;}
}

1.5.4:栈的压入、弹出序列

栈的压入、弹出序列
java">public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param pushV int整型一维数组 * @param popV int整型一维数组 * @return bool布尔型*/public boolean IsPopOrder (int[] pushA, int[] popB) {// write code hereStack<Integer> stack = new Stack<>();int j = 0;  //记得放外边for(int x : pushA){stack.push(x);while(!stack.empty() && j<popB.length && stack.peek() == popB[j] ){//这里一定要判断stack是否为空,你想想,我一直搁着pop,一会就pop完了// 最后i到5,j到1,你就算是判断你也stack.peek();就越界了// 其实 J < pop.length就够了,(!stack.empty())相加就加stack.pop();j++;}}/*if(stack.empty()){return true;}return false;*/return stack.empty();}
}

1.5.5:最小栈

 最小栈

java">import java.util.*;
class MinStack {// 最小栈里面的元素至少能保证它是这个栈下面的元素以及遇到下一个元素之前,它都是最小的private Stack<Integer> stack;private Stack<Integer> minStack;//你得是成员变量才能用的啦,不然其他方法咋用嘛public MinStack() {stack = new Stack<>();minStack = new Stack<>();}public void push(int val) {stack.push(val);//不管怎么样你一定要push进去if(minStack.empty()){       // 最小栈里面的是空的,这个时候你放进去一个任意数,这个任意数就是最小值minStack.push(val);}if(val <= minStack.peek()){//等于也要加上minStack.push(val);}}public void pop() {if(! stack.empty()){ //根本不用担心minStack会空指针异常,最后Stack和MinStack都会剩下一个元素int ret = stack.pop();//不管怎么样你一定要pop出去,除非空指针了if( ret == minStack.peek()){//等于也要加上minStack.pop();}}}public int top() {if(stack.empty()){return -1;}return stack.peek();}public int getMin() {if(minStack.empty()){return -1;}return minStack.peek();}
}

1.6:栈、虚拟机栈、栈帧有什么区别呢?

栈(Stack)

  1. 定义:栈是一种基本的线性数据结构,它按照后进先出(LIFO, Last In First Out)的原则存储和操作数据。

  2. 特点

    • 栈中的数据项只能在一端(栈顶)进行插入和删除操作。
    • 栈底固定,栈顶可以动态变化。
    • 栈具有有限的容量,当容量达到上限时,称为栈溢出。
    • 栈的操作简单高效,时间复杂度为O(1)。
  3. 应用场景:栈在多种场景下都有应用,如撤销操作浏览器的前进后退功能逆序输出中缀表达式转换为后缀表达式等。

虚拟机栈(Java Virtual Machine Stack)包含着栈帧

  1. 定义:虚拟机栈是Java虚拟机中用于方法调用和执行的一块特殊作用的内存空间。

  2. 特点

    • 每个线程在创建时都会创建一个虚拟机栈,该栈是线程私有的
    • 虚拟机栈中保存着一个个的栈帧(Stack Frame),每个栈帧对应着一次Java方法调用。
    • 虚拟机栈的访问速度仅次于程序计数器,是快速有效的分配存储方式。

栈帧(Stack Frame)

  1. 定义:栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构

  2. 特点

    • 每个方法在运行时,JVM都会为其创建一个栈帧,并将该栈帧压入到对应的虚拟机栈中。
    • 当方法调用结束时,该方法对应的栈帧会从虚拟机栈中出栈。
    • 栈帧中包含局部变量表、操作数栈、动态链接(指向运行时常量池的方法引用)、方法返回地址以及一些附加信息。

总之:

二:队列

2.1:队列的概念


Java中,Queue是个接口,底层是通过链表实现的。(头进尾出或者尾进头出)

2.2.队列的模拟实现

java">public class MyQueue {//其实这就是双链表的不同实现方法的名字而已,没啥区别(就是里面的方法不一样罢了)//队列和栈里的方法极其相似class ListNode{int value;//队列里面的有效值ListNode pre;//前一个节点的地址ListNode next;public ListNode(int value) {this.value = value;}}ListNode front;  //头结点的地址ListNode rear;  //尾巴节点int usedSize;//尾巴进去public void offer(int data){ListNode node = new ListNode(data);if(rear == null){rear = node;front = node;}else{rear.next = node;node.pre = rear;rear = node;}usedSize++;}//头出public int poll(){if(front == null){throw new NullPointerException();}int ret = front.value;if(front == rear){front = null;rear = null;}else{front = front.next;front.pre = null;}usedSize--;return ret; //这里一定不能写front.value!!!}public int size(){return usedSize;}public int peek(){if(front == null){throw new NullPointerException();}int ret = front.value;return ret;}public boolean isEmpty(){return usedSize == 0;}
}

2.3.模拟队列测试:

java">public class Test {public static void main(String[] args) {MyQueue myQueue = new MyQueue();myQueue.offer(10);myQueue.offer(20);myQueue.offer(66);myQueue.offer(77);myQueue.offer(99);System.out.println(myQueue.peek());//10System.out.println(myQueue.poll());//10System.out.println(myQueue.peek());//20System.out.println("=================");System.out.println(myQueue.size());//4myQueue.poll();myQueue.poll();System.out.println(myQueue.isEmpty());//是否为空,falsemyQueue.poll();myQueue.poll();System.out.println(myQueue.isEmpty());//是否为空,true//myQueue.poll();//空指针异常
}
}

2.4.队列的方法的一些区别

队列的offer和add的区别在哪里?

1.返回值:
offer(E e): 返回boolean类型。如果元素成功被添加到队列中,则返回true;如果队列已满(对于某些有界队列),则返回false。
add(E e): 也返回boolean类型。如果元素成功被添加到队列中,则返回true。但如果队列已满(对于某些有界队列),则会抛出IllegalStateException异常。()
异常处理:
offer(E e): 不会抛出异常,总是返回true或false来表示操作是否成功。
add(E e): 在队列已满的情况下会抛出IllegalStateException,不返回false。
使用场景:
offer(E e): 更常用于需要检查队列是否已满而不想处理异常的情况。
add(E e): 更适合在需要确保元素一定被添加且队列容量足够时使用,因为它在无法添加元素时会通过异常来通知调用者。

IllegalStateException:

 IllegalStateException:是 Java 中的一个运行时异常(RuntimeException 的子类),它表明某个应用程序对象当前处于不适当的状态,因此无法执行请求的操作。这个异常通常不是由系统抛出,而是由程序员在代码中显式地抛出。(譬如如果尝试在一个已经关闭的文件流上写入数据,可能会抛出 IllegalStateException

poll和remove方法的区别?

  • poll()方法:队列为空时不会抛出异常,总是返回null或移除的元素。
  • remove()方法:在队列为空时会抛出NoSuchElementException异常。

peek()和element()方法的区别?

  • peek()方法:队列为空时不会抛出异常,总是返回null或队顶的元素。
  • element()方法:在队列为空时会抛出NoSuchElementException异常。

NoSuchElementException异常:

这个异常通常在尝试访问某个不存在的元素时抛出,尤其是在使用迭代器(Iterator)、列表迭代器(ListIterator)、队列(Queue)等集合框架中的组件时。

Stack:可以本身就用new一个Stack这个结构,也可以用LinkedList(链式队列),ArrayDueue(双端队列)(底层是数组)。

Queue和Dueue:底层就是LinkedList,所以用LinkedList实现这两种结构都没问题,当然new一个ArrayDueue也完全没问题。

底层是数组的:可以是顺序表,栈,队列,双端队列

底层是链表的:单双链表,栈,队列,双端队列

2.4 循环队列(上面说的用数组实现)

设计循环队列

实际中我们有时还会使用一种队列叫循环队列,环形队列通常使用数组实现。 (队列的数组实现还有ArrayDueue哦(双端队列))
代码实现(以及注解和图形理解)
java">class MyCircularQueue {int front;int rear;int[] elem;public MyCircularQueue(int k) {elem = new int[k + 1];}//向循环队列插入一个元素。如果成功插入则返回真。public boolean enQueue(int value) {if(isFull()){  //插入元素,应该是rear出发,return false;}elem[rear] = value;rear = (rear +1) % elem.length;return true;}//从循环队列中删除一个元素。如果成功删除则返回真。public boolean deQueue() { if(isEmpty()){ return false;}front =  (front + 1) % elem.length;//我这个是为了防止本来在 7位置,你突然++ 就变成8了,但是我这个数组根本没有这个下标return true;}//返回队首元素public int Front() {if(isEmpty()){return -1;}return elem[front];  //这个front下标一直都有元素}//返回队尾元素public int Rear() {  //rear下标一直没有元素,返回上一个下标的元素。if(isEmpty()){return -1;}if(rear == 0){int cur = 0;cur = elem.length -1;return elem[cur];}return elem[rear -1];}//判断是否为空public boolean isEmpty() {return front == rear;}//判断是否满了public boolean isFull() {return (rear + 1) % elem.length == front;   // 这里可不是rear + 1 == front,一定要%elem.length}
}

图解:

. 双端队列 (Deque)

双端队列( deque )是指允许两端都可以进行入队和出队操作的队列, deque “double ended queue” 的简称。
那就说明元素可以从队头出队和入队也可以从队尾出队和入队
在实际工程中,使用 Deque 接口是比较多的,栈和队列均可以使用该接口
java">        Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现(直接当成栈就完全没问题//毕竟你两边都能进出嘛stack.push(1);Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现Deque<Integer> stack1 = new LinkedList<>();//栈来玩玩啦,队列也可以//这里一定要注意,这里并不是栈本身的结构,只是这个名字(这里你可别new 一个Stack了,没实现这个接口)

四. 总结

1.Stack:可以本身就用new一个Stack这个结构,也可以用LinkedList(链式队列),ArrayDueue(双端队列)(底层是数组)。

2.Queue和Dueue:底层就是LinkedList,所以用LinkedList实现这两种结构都没问题,当然new一个ArrayDueue(双端队列,底层是数组)也完全没问题。

1.底层是数组的:可以是顺序表,栈,队列,双端队列

2.底层是链表的:单双链表,栈,队列,双端队列

上述就是Stack,Queue—模拟实现,实战应用全解析!的全部内容了,能看到这里相信您一定对小编的文章有了一定的认可,数据结构的出现让我们对于数据的组织的利用有了更加方便的使用~~

有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正

您的支持就是我最大的动力​​​!!!!


http://www.ppmy.cn/news/1573862.html

相关文章

迪威模型网:免费畅享 3D 打印盛宴,科技魅力与趣味创意并存

还在为寻找优质3D打印模型而发愁&#xff1f;快来迪威模型网&#xff08;https://www.3dwhere.com/&#xff09;&#xff0c;一个集前沿科技与无限趣味于一体的免费3D打印宝藏平台&#xff01; 踏入迪威模型网&#xff0c;仿佛开启一场未来科技之旅。其“3D打印”专区&#xff…

【OpenCV】OpenCV 中各模块及其算子的详细分类

OpenCV 的最新版本包含了 500 多个算子&#xff0c;这些算子覆盖了图像处理、计算机视觉、机器学习、深度学习、视频分析等多个领域。为了方便使用&#xff0c;OpenCV 将这些算子分为多个模块&#xff0c;每个模块承担特定的功能。 以下是 OpenCV 中各模块及其算子的详细分类&…

“深入浅出”系列之QT:(10)Qt接入Deepseek

项目配置&#xff1a; 在.pro文件中添加网络模块&#xff1a; QT core network API配置&#xff1a; 将apiUrl替换为实际的DeepSeek API端点 将apiKey替换为你的有效API密钥 根据API文档调整请求参数&#xff08;模型名称、温度值等&#xff09; 功能说明&#xff1a; 使…

后台管理系统-月卡管理

功能说明并准备静态结构 <template><div class"card-container"><!-- 搜索区域 --><div class"search-container"><span class"search-label">车牌号码&#xff1a;</span><el-input clearable placeho…

GCC头文件搜索顺序详解

在C/C编程中&#xff0c;合理管理头文件的引入路径对于项目的组织至关重要。GCC编译器提供了灵活的机制来指定头文件的搜索路径&#xff0c;这主要通过#include "…"和#include <…>两种形式实现。本文将详细介绍这两种形式的区别以及如何使用-I参数优化头文件…

代理和NAT多路转接

1.NAT技术背景 在IPv4协议中存在IP地址数量不充足的问题&#xff0c; NAT技术当前解决IP地址不够用的主要手段, 是路由器的一个重要功能。 NAT能够将私有IP对外通信时转为全局IP. 也就是就是一种将私有IP和全局IP相互转化的技术方法: 很多学校, 家庭, 公司内部采用每个终端设…

嵌入式0xDEADBEEF

在嵌入式系统中&#xff0c;0xDEADBEEF 是一个常见的“魔数”&#xff08;magic number&#xff09;&#xff0c;通常用于调试和内存管理。它的含义和用途如下&#xff1a; 1. 调试用途 未初始化内存的标记&#xff1a;在调试时&#xff0c;0xDEADBEEF 常用于标记未初始化或已…

解决 WSL Ubuntu 中 /etc/resolv.conf 自动重置问题

解决 WSL Ubuntu 中 /etc/resolv.conf 自动重置问题 前言问题描述问题原因尝试过的命令及分析解决方案&#xff1a;修改 wsl.conf 禁用自动生成总结 前言 在使用 Windows Subsystem for Linux (WSL) 的 Ubuntu 子系统时&#xff0c;你可能会遇到 /etc/resolv.conf 文件被自动重…