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

Java数据结构栈和队列(Stack和Queue详解)

前言:

        栈和队列是数据结构中重要的存储方式,也是建立在Arrarylist和LinkedList之上的数据结构。

        本猿在C语言阶段就已经详细剖析过栈和队列,不熟悉的小伙伴可以翻看之前的博客内容!

      Java阶段一方面剖析Java中自带的Stack和Queue中的各种方法,另一方面也是重点重新再次实现栈和队列,  并发掘和C语言中的区别。

栈(Stack):

        栈是一种"先进后出"的数据结构,如图:

Java中的栈:

        在Java的util包中有Stack类,我们可以看到Stack的源代码:

继承了Vector,那么Vector中又有哪些继承关系呢?

我们发现继承了AbstractList,并且扩展了好多接口,其中包括List接口。

综上我们可以以一张结构图概括:

栈的模拟实现:

        我们利用顺序表模拟实现栈,当然也可以用链表实现栈,Java中用的是顺序表!至于为什么要用顺序表,等自己实现一遍栈大概就清楚了!

    

public class MyStack {private int[] array;private int size;private static final int DEFFAULEVALUE = 10;public MyStack() {array = new int[DEFFAULEVALUE];}public void push(int value) {ensureCapacity();array[this.size++] = value;}public int pop(){if(empty()) {throw new RuntimeException("栈为空,无法获取栈顶元素");}size--;return array[size];}public int peek(){if(empty()){throw new RuntimeException("栈为空,无法获取栈顶元素");}return array[size-1];}public int size(){return size;}private boolean empty() {if(this.size == 0) {return true;}return false;}private void ensureCapacity() {if(this.size == array.length) {array = Arrays.copyOf(array,size*2);}}
}

注意:

        在main方法中测试的时候,需要注意打印栈当中的元素的时候需要pop打印,也就是需要用到for循环打印,但是这样写对不对呢?

        

for (int i = 0; i < myStack.size(); i++) {System.out.println(myStack.pop());
}

这样的for循环是有问题的,问题在于每次pop结束之后,size()的值在发生变化,也就是这个循环不会执行size()次。

        所以以上遍历会出现问题,应该这样遍历:

int num = myStack.size();
for (int i = 0; i < num; i++) {System.out.println(myStack.pop());
}

这样遍历才不会出现问题!! 

关于栈的习题(典型):

1、括号匹配问题:

        20. 有效的括号 - 力扣(LeetCode)

        具体题目请参考力扣,具体实现代码如下:

        

class Solution {public boolean isValid(String s) {if(s == null) {return true;}Stack<Character> stack = new Stack<>();for(int i = 0;i<s.length();i++) {char a = s.charAt(i);if(stack.empty()) {if(a == ')' || a == ']' || a == '}') {return false;}}if(a == '(' || a == '{' ||a == '[') {stack.push(a);}else {char b = stack.pop();if(!(a == ')' && b == '(' || a == ']' && b == '[' || a == '}' && b == '{')) {return false;}}}if(!stack.empty()) {return false;}return true;}
}

2、逆波兰表达式求值:

     150. 逆波兰表达式求值 - 力扣(LeetCode)   

class Solution {public int evalRPN(String[] tokens) {Stack<String> str = new Stack<>();for(int i = 0;i < tokens.length;i++) {if(tokens[i].equals("+")) {int num1 = Integer.valueOf(str.pop());int num2 = Integer.valueOf(str.pop());str.push(num1+num2+"");}else if(tokens[i].equals("-")) {int num1 = Integer.valueOf(str.pop());int num2 = Integer.valueOf(str.pop());str.push(num2-num1+"");}else if(tokens[i].equals("*")) {int num1 = Integer.valueOf(str.pop());int num2 = Integer.valueOf(str.pop());str.push(num1*num2+"");}else if(tokens[i].equals("/")) {int num1 = Integer.valueOf(str.pop());int num2 = Integer.valueOf(str.pop());str.push(num2/num1+"");}else {str.push(tokens[i]);}}return Integer.valueOf(str.pop());}
}

3、出栈进栈次序匹配:

栈的压入、弹出序列_牛客题霸_牛客网 (nowcoder.com)

public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param pushV int整型一维数组 * @param popV int整型一维数组 * @return bool布尔型*/public boolean IsPopOrder (int[] pushV, int[] popV) {// write code hereStack<Integer> st = new Stack<>();int j = 0;for(int i = 0;i<pushV.length;i++) {st.push(pushV[i]);while(!st.empty() && st.peek() == popV[j]) {st.pop();j++;}}if(st.empty()) {return true;}return false;
}
}

4、最小栈:

155. 最小栈 - 力扣(LeetCode)

class MinStack {Stack<Integer> st1;Stack<Integer> st2;int minInteger;public MinStack() {st1 = new Stack<>();st2 = new Stack<>();}public void push(int val) {if(st1.empty() && st2.empty()) {minInteger = val;st1.push(val);st2.push(val);}else {st1.push(val);if(val < minInteger) {st2.push(val);minInteger = val;}else {st2.push(minInteger);}}}public void pop() {st1.pop();st2.pop();if(!st2.empty()) {minInteger = st2.peek();}}public int top() {return st1.peek();}public int getMin() {return st2.peek();}
}

队列(Queue):

队列 :只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out) 入队列:进行插入操作的一端称为 队尾( Tail/Rear 出队列:进行删除操作的一端称为 队头 Head/Front ).

Java中的队列:

Java 中, Queue 是个接口,底层是通过链表实现 的。

也是一系列的继承关系。

队列的模拟实现:

public class MyQueue {// 双向链表节点public static class ListNode{ListNode next;ListNode prev;int value;ListNode(int value){this.value = value;}}ListNode first; // 队头ListNode last; // 队尾int size = 0;// 入队列---向双向链表位置插入新节点public void offer(int e){ListNode newNode = new ListNode(e);if(first == null){first = newNode;
// last = newNode;}else{last.next = newNode;newNode.prev = last;
// last = newNode;}last = newNode;size++;}// 出队列---将双向链表第一个节点删除掉public int poll(){
// 1. 队列为空
// 2. 队列中只有一个元素----链表中只有一个节点---直接删除
// 3. 队列中有多个元素---链表中有多个节点----将第一个节点删除int value = 0;if(first == null){return -1;}else if(first == last){last = null;first = null;}else{value = first.value;first = first.next;first.prev.next = null;first.prev = null;}--size;return value;}// 获取队头元素---获取链表中第一个节点的值域public int peek(){if(first == null){return 0;}return first.value;}public int size() {return size;}public boolean isEmpty(){return first == null;}
}

循环队列:

        实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。 环形队列通常使用数组实现。
数组下标循环的小技巧
1. 下标最后再往后 (offset 小于 array.length): index = (index + offset) % array.length
2. 下标最前再往前 (offset 小于 array.length): index = (index + array.length - offset) % array.length

如何区分空与满:

1. 通过添加 size 属性记录
2. 保留一个位置
3. 使用标记
接下来我们就来实战一下:
622. 设计循环队列 - 力扣(LeetCode)
要求:
分别用三种不同的区分空和满的方式:
方式一:
class MyCircularQueue {private int[] arr;private int size;private int rear;private int front;public MyCircularQueue(int k) {arr = new int[k];size = 0;}public boolean enQueue(int value) {if(isFull()) {return false;}arr[rear] = value;size++;rear = (rear+1)%arr.length;return true;}public boolean deQueue() {if(isEmpty()) {return false;}front = (front+1)%arr.length;size--;return true;}public int Front() {if(isEmpty()) {return -1;}return arr[front];}public int Rear() {if(isEmpty()) {return -1;}return arr[(rear+arr.length-1)%arr.length];}public boolean isEmpty() {if(size == 0) {return true;}return false;}public boolean isFull() {if(size == arr.length) {return true;}return false;}
}

方式二:

class MyCircularQueue {private int[] arr;private int rear;private int front;public MyCircularQueue(int k) {arr = new int[k+1];rear = 0;front = 0;}public boolean enQueue(int value) {if(isFull()) {return false;}arr[rear] = value;rear = (rear+1)%arr.length;return true;}public boolean deQueue() {if(isEmpty()) {return false;}front = (front+1)%arr.length;return true;}public int Front() {if(isEmpty()) {return -1;}return arr[front];}public int Rear() {if(isEmpty()) {return -1;}return arr[(rear+arr.length-1)%arr.length];}public boolean isEmpty() {if(rear == front) {return true;}return false;}public boolean isFull() {if((rear+1)%arr.length == front) {return true;}return false;}
}

方式三:大家可以自己试试!

双端队列:

        双端队列(deque )是指允许两端都可以进行入队和出队操作的队列, deque “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。
        此时该场景应该有几种组合:
        对头出,对头进。
        对头出,对尾进。
        对尾出,对头进。
        队尾出,对头出。
        也就是该双端队列既可以当作栈也可以当作队列!!
        Deque 是一个接口,使用时必须创建 LinkedList 的对象。
Deque<Integer> stack = new ArrayDeque<>();// 双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();// 双端队列的链式实现

 


http://www.mrgr.cn/news/45119.html

相关文章:

  • 系统架构设计师教程 第14章 14.3 云原生架构相关技术 笔记
  • 网页前端开发之Javascript入门篇(8/9):数组
  • LabVIEW提高开发效率技巧----阻塞时钟
  • SQL专项练习第五天
  • Python OpenCV精讲系列 - 动态场景分析深入理解(十六)
  • python3 venv的使用详解
  • 冥想第一千三百零三天(1303)
  • TCN-Transformer时间序列预测(多输入单预测)——基于Pytorch框架
  • 基于时频分析与自适应滤波技术的多分量雷达信号提取与重建研究
  • Stable Diffusion最新版nowebui的api使用详解
  • java二维数组
  • python 实现最小生成树 boruvka算法
  • 【含文档】基于Springboot+Android的公交系统查询与设计(含源码+数据库+lw)
  • 各省份自然灾害损失情况数据(2004-2022年)
  • Redis快速入门
  • LoRA:大模型的低阶自适用(论文复现)
  • 解决Docker环境下Next.js和FastAPI的跨容器通信问题
  • #String StringBuilder StringBuffer
  • vulnhub-THE PLANETS-EARTH靶机
  • 【JVM系列】深入理解Java虚拟机(JVM)的核心技术:从加载到初始化的全过程解析(一、Java类加载器)