当前位置:   article > 正文

数据结构之栈和队列 - 超详细的教程,手把手教你认识并运用栈和队列_队列和栈

队列和栈

目录

1. 栈(Stack)

1.1 概念

1.2 栈的使用

1.3 栈的模拟实现

1.4 栈的应用场景

1.5 概念区分

2. 队列(Queue)

2.1 概念

2.2 队列的使用

2.3 队列模拟实现

2.4 循环队列

3. 双端队列 (Deque)

4. 练习题


栈:后进先出

队列:先进先出

1. 栈(Stack)

1.1 概念

栈:是一种特殊的线性表只允许在固定的一端插入或者删除元素,一个栈包含了栈顶和栈底。只能在栈顶插入或者删除元素。栈的底层是由数组实现的。

栈遵循先入后出原则,也就是先插入的元素得到后面才能删除,后面插入的元素比先插入的元素要更早的删除。可以理解成:后进先出

入栈:在栈顶插入元素。

出栈:在栈顶删除元素。

1.2 栈的使用

如下图,栈的常用方法有:

检查栈是否为空的意思是,看看栈里面是不是一个元素都没有。

 栈的方法都挺简单,我就不一个个演示了,刷题的时候直接用即可

  1. import java.util.Stack;
  2. public class Main {
  3. public static void main(String[] args) {
  4. //创建一个栈
  5. Stack<Integer> stack = new Stack<>();
  6. //入栈
  7. stack.push(1);
  8. //看看栈顶元素但不删除
  9. int top = stack.peek();
  10. System.out.println(top);
  11. //删除栈顶元素
  12. stack.pop();
  13. //如果栈不为空,就看看栈里有几个元素
  14. if (!stack.empty()) {
  15. int size = stack.size();
  16. System.out.println(size);
  17. }
  18. }
  19. }

1.3 栈的模拟实现

既然要实现一个栈,那我们可以先定义一个 MyStack 类,接下来只需要思考怎么完成 MyStack 类即可。

之前也说过,栈的底层是由一个数组实现的,所以我们可以定义一个数组,对栈进行操作就是对数组进行操作。

为了以后方便操作数组,我们还可以再定义一个 usedSize ,用来表示数组当前存放的元素个数。

因为我们待会还要提供 MyStack 的构造方法,就相当于给数组分配内存,所以我们可以定义一个默认容量,用来表示数组一开始的容量大小。

有了默认容量,我们就可以把 MyStack 的构造方法给写出来了。

栈要实现以下方法:

  1. //入栈
  2. public void push(int val) {
  3. }
  4. //出栈
  5. public int pop() {
  6. return -1;
  7. }
  8. //返回栈顶元素
  9. public int peek() {
  10. return -1;
  11. }
  12. //判空
  13. public boolean empty() {
  14. return false;
  15. }
  16. //返回栈的元素个数
  17. public int size() {
  18. return 0;
  19. }

我们可以先挑最简单的实现,比如 empty 和 size。

我们先实现 empty 方法,很简单,就是看数组有没有元素咯,元素个数不为 0 ,说明栈不空。

再来实现 size 方法,这个也简单,直接返回数组元素个数就好啦。

再来实现 pop 方法,出栈,就是出栈顶元素,也就是把数组的最后一个元素给删掉,那就得先判断数组里有没有元素,根据这个再来进行删除。

usedSize-- ,之后入栈的时候会把原来的元素给覆盖掉,相当于是变相删除了这个元素

再来实现 peek 方法,这个和 pop 方法同理,只是瞄一眼栈顶元素,但是不用删除栈顶元素。

最后再来实现 push 方法,首先,你得看看数组满没满,满了就要扩容,然后再来放元素。

  1. import java.util.Arrays;
  2. public class MyStack {
  3. private int[] elem;
  4. private int usedSize;//表示当前数组存放元素个数
  5. private static final int DEFAULT_CAPACITY = 10;//数组的默认容量
  6. public MyStack() {
  7. this.elem = new int[DEFAULT_CAPACITY];
  8. }
  9. //判空
  10. public boolean empty() {
  11. return this.usedSize == 0;
  12. }
  13. //返回栈的元素个数
  14. public int size() {
  15. return this.usedSize;
  16. }
  17. //出栈
  18. public int pop() {
  19. //栈为空,删不了
  20. if (empty()) {
  21. return -1;
  22. }
  23. //栈不为空,删除
  24. int top = this.elem[this.usedSize - 1];
  25. usedSize--;
  26. return top;
  27. }
  28. //返回栈顶元素
  29. public int peek() {
  30. //栈为空
  31. if (empty()) {
  32. return -1;
  33. }
  34. return elem[usedSize - 1];
  35. }
  36. //入栈
  37. public void push(int val) {
  38. //数组满了,得扩容
  39. if (this.usedSize == this.elem.length) {
  40. //扩容
  41. this.elem = Arrays.copyOf(this.elem, this.elem.length * 2);
  42. }
  43. this.elem[usedSize] = val;
  44. usedSize++;
  45. }
  46. }

1.4 栈的应用场景

1. 改变元素的序列

1. 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()

             A: 1,4,3,2           B: 2,3,4,1           C: 3,1,4,2             D: 3,4,2,1

选C

 

 

2.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )。

A: 12345ABCDE         B: EDCBA54321          C: ABCDE12345          D: 54321EDCBA

选B。

20. 有效的括号

思路:直接遍历 s ,因为是看看括号是否匹配,可以利用栈来完成,所以直接让右括号来跟左括号匹配即可

分两种情况,如果是左括号,那就直接入栈

如果是右括号,那就匹配,匹配之前得看看栈是不是为空,如果为空,说明就不匹配

如果匹配了,那就出栈。

最后遍历完 s ,如果栈为空,说明全部匹配成功,返回 true。

  1. class Solution {
  2. public boolean isValid(String s) {
  3. Stack<Character> stack = new Stack<>();
  4. for (int i = 0; i < s.length(); i++) {
  5. char ch = s.charAt(i);
  6. //左括号
  7. if (ch == '(' || ch == '[' || ch == '{') {
  8. stack.push(ch);
  9. } else {
  10. //右括号
  11. if (!stack.empty()) {
  12. char top = stack.peek();
  13. if (top == '(' && ch == ')'
  14. || top == '{' && ch == '}'
  15. || top == '[' && ch == ']') {
  16. stack.pop();
  17. } else {
  18. return false;
  19. }
  20. } else {
  21. //栈为空,没有左括号
  22. return false;
  23. }
  24. }
  25. }
  26. if (!stack.empty()) {
  27. return false;
  28. }
  29. return true;
  30. }
  31. }

150. 逆波兰表达式求值

思路:利用栈来完成,遍历数组,如果遇到数字,那就放进栈里,如果遇到运算符,就弹出栈的两个元素,

第一个弹出的元素作为运算符的右操作数,第二个作为左操作数,将计算完的值放入栈中

  1. class Solution {
  2. public int evalRPN(String[] tokens) {
  3. Stack<Integer> stack = new Stack<>();
  4. for (int i = 0; i < tokens.length; i++) {
  5. //运算符
  6. if (tokens[i].equals("+")
  7. || tokens[i].equals("-")
  8. || tokens[i].equals("*")
  9. || tokens[i].equals("/")) {
  10. int b = stack.pop();
  11. int a = stack.pop();
  12. switch (tokens[i]) {
  13. case "+":
  14. stack.push(a + b);
  15. break;
  16. case "-":
  17. stack.push(a - b);
  18. break;
  19. case "*":
  20. stack.push(a * b);
  21. break;
  22. case "/":
  23. stack.push(a / b);
  24. break;
  25. }
  26. } else {
  27. //数字
  28. int tmp = Integer.parseInt(tokens[i]);
  29. stack.push(tmp);
  30. }
  31. }
  32. return stack.pop();
  33. }
  34. }

JZ31 栈的压入、弹出序列

你平常怎么判断出栈顺序的,现在就怎么写这个方法,思路是差不多的。

思路:这题利用栈来完成,遍历 push 数组,用 j 遍历 pop 数组,先把元素放入栈里,然后如果栈不为空并且栈顶元素和 pop[ j ] 相同的话,那就出栈,然后 j 往后走。

最后看看栈是否为空,如果为空,说明全都匹配成功,就返回 true ,否则返回 false

  1. import java.util.*;
  2. public class Solution {
  3. /**
  4. * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
  5. *
  6. *
  7. * @param pushV int整型一维数组
  8. * @param popV int整型一维数组
  9. * @return bool布尔型
  10. */
  11. public boolean IsPopOrder(int[] push, int[] pop) {
  12. int j = 0;
  13. Stack<Integer> stack = new Stack<>();
  14. //用i遍历push数组,j遍历pop数组,
  15. //将push数组的所有数字都放入栈中,然后再peek栈
  16. //如果peek栈的元素和pop数组的j下标元素相同
  17. //就将该元素出栈
  18. //遍历完i后,如果栈不是空的就返回false
  19. for (int i = 0; i < push.length; i++) {
  20. //入栈
  21. stack.push(push[i]);
  22. //匹配
  23. while (!stack.empty() && j < pop.length && stack.peek() == pop[j]) {
  24. stack.pop();
  25. j++;
  26. }
  27. }
  28. if (stack.empty()) {
  29. return true;
  30. }
  31. return false;
  32. }
  33. }

155. 最小栈

思路:定义两个栈,一个就是普通的栈,一个用来存放有可能是最小值的元素的栈(简称最小栈)

构造方法就是初始化这两个栈,

push 方法,那就得判断是不是第一次入栈,如果是第一次入栈,那就两个栈都要入,如果不是第一次,那就普通栈正常入栈,而最小栈就需要判断一下,如果要入的 val 小于等于 最小栈的栈顶元素,那就入栈。

pop 方法,得看看是不是空栈,空栈出不了。如果栈不为空,那普通栈就正常出栈,但是得看看普通栈出的元素在最小栈中是否存在,如果存在,那最小栈也得出。

top 方法,看看是不是空栈,不是就正常返回栈顶元素

getMin 方法也同理。

  1. class MinStack {
  2. private Stack<Integer> stack;//一个是普通栈
  3. private Stack<Integer> minStack;//一个存有可能是最小值的栈
  4. public MinStack() {
  5. stack = new Stack<>();
  6. minStack = new Stack<>();
  7. }
  8. public void push(int val) {
  9. stack.push(val);
  10. if (minStack.empty()) {
  11. //如果是第一次入栈,那么两个栈都得入
  12. minStack.push(val);
  13. } else {
  14. int tmp = minStack.peek();
  15. //如果val小于等于minStack栈顶元素,那就入栈
  16. if (val <= tmp) {
  17. minStack.push(val);
  18. }
  19. }
  20. }
  21. public void pop() {
  22. if (stack.empty()) {
  23. return;
  24. }
  25. int top = stack.pop();
  26. int tmp = minStack.peek();
  27. if (tmp == top) {
  28. //如果普通栈和最小栈元素相等,那就都得出栈
  29. //不是的话就普通栈出栈
  30. minStack.pop();
  31. }
  32. }
  33. //获取普通栈的栈顶元素
  34. public int top() {
  35. if (stack.empty()) {
  36. return -1;
  37. }
  38. return stack.peek();
  39. }
  40. //获取普通栈的栈顶元素
  41. public int getMin() {
  42. if (minStack.empty()) {
  43. return -1;
  44. }
  45. return minStack.peek();
  46. }
  47. }

1.5 概念区分

栈、虚拟机栈、栈帧有什么区别呢?
栈:一种数据结构。
虚拟机栈:内存当中的一块区域下,创建局部变量等会在虚拟机栈上分配内存
栈帧:调用方法时在内存中开辟的一块空间。

2. 队列(Queue)

先进先出的一种结构

2.1 概念

队列:如上图,只能在队尾插入元素,队头删除元素,是一种特殊的线性表,遵循先进先出原则。

2.2 队列的使用

在Java中,Queue是个接口,底层是通过链表实现的,所以在创建对象的时候,必须实例化 LinkedList 对象。

Queue 的常见方法有: 

跟栈的使用方式差不多,我就不演示啦

2.3 队列模拟实现

队列可以使用顺序结构或者链式结构实现,更推荐使用链式结构实现,因为简单

和栈同理,既然要实现队列,那就先定义一个 MyQueue 类。

同时,既然是使用链表实现,那肯定得定义一个静态内部类 ListNode。

而一个队列还有队头和队尾,所以也要定义队头和队尾

因为要实现的方法里包含 isEmpty 方法,所以可以额外定义一个 usedSize 来记录队列的元素个数

队列要实现的方法有这些:

  1. public void offer(int val) {
  2. }
  3. //出队列 相当于删除队尾结点
  4. public int poll() {
  5. return -1;
  6. }
  7. //获取队头
  8. public int peek() {
  9. return -1;
  10. }
  11. public int getUsedSize() {
  12. return 0;
  13. }
  14. public boolean isEmpty() {
  15. return false;
  16. }

同样的,我们先选择简单的方法来实现,比如 getUsedSize 方法和 isEmpty 方法。

我们先实现 getUsedSize 方法,这个简单,直接返回 usedSize 就行。

再来实现 isEmpty 方法,判断队列是否为空,就是判断队列里usedSize是否为零。

再来实现 offer 方法,分两种情况,

一种是队列为空,说明链表里面还没有元素,也就是说新增的元素是第一个元素,所以就让 front 和 rear 都指向新增的元素即可。

第二种是队列不为空,相当于链表的头插法。

再来实现 poll 方法

分两种情况,第一种,如果队列为空,删不了

第二种,队列不为空,就得判断,队列里面是否只有一个节点,如果只有一个节点,那删了队列就空了,如果不是只有一个节点,那就相当于删除尾巴节点。

最后实现 peek 方法,与 poll 方法类似,先看看空不空,不为空,就直接返回 top,为空,就返回 -1。

  1. public class MyQueue {
  2. static class ListNode {
  3. private int val;
  4. private ListNode prev;
  5. private ListNode next;
  6. public ListNode(int val) {
  7. this.val = val;
  8. }
  9. }
  10. private ListNode front;//队头
  11. private ListNode rear;//队尾
  12. private int usedSize;
  13. public void offer(int val) {
  14. ListNode node = new ListNode(val);
  15. if (front == null) {
  16. front = node;
  17. rear = node;
  18. } else {
  19. //采用头插法
  20. front.prev = node;
  21. node.next = front;
  22. front = node;
  23. }
  24. usedSize++;
  25. }
  26. //出队列 相当于删除队尾结点
  27. public int poll() {
  28. //头插尾删
  29. if (rear == null) {
  30. return -1;
  31. } else if (front == rear) {
  32. //只有一个结点
  33. int ret = rear.val;
  34. front = null;
  35. rear = null;
  36. usedSize--;
  37. return ret;
  38. } else {
  39. int ret = rear.val;
  40. rear = rear.prev;
  41. rear.next = null;
  42. usedSize--;
  43. return ret;
  44. }
  45. }
  46. //获取队头
  47. public int peek() {
  48. if (rear == null) {
  49. return -1;
  50. }
  51. //因为是头插尾删,所以返回的是队尾元素
  52. return rear.val;
  53. }
  54. public int getUsedSize() {
  55. return usedSize;
  56. }
  57. public boolean isEmpty() {
  58. if (usedSize == 0) {
  59. return true;
  60. }
  61. return false;
  62. }
  63. }

2.4 循环队列

如下图,就是一个循环队列。环形队列通常使用数组实现。

该怎么区分循环队列到底满没满?

1. 可以定义一个 usedSize,如果 usedSize == elem.length,说明满了

2. 可以定义一个 flag ,如果满了,就标记为 true

3. 可以浪费一个空间,来区分

一般用第三种方法,因为比较简单。

那么该怎么让数组变成一个循环数组呢?

622. 设计循环队列

首先,循环队列的底层是一个数组,所以我们要定义一个数组,并且把 front,rear 也一起定义上。

然后实现构造方法,因为我们选择浪费一个空间来区分循环队列满没满,所以我们要多初始化一个空间给数组,不然数组就放不下那么多元素了。

再来实现 isEmpty 方法 ,如下图,很明显,当 front == rear 的时候,队列为空。

再来实现 isFull 方法 ,因为浪费了一个空间来区分满没满,根据前面讲过的,如下图:

再来实现 front 和 rear 方法

先判断空不空,不为空就返回,为空就返回 -1,返回队尾元素的时候得注意 rear 下标,假如 rear 刚好为 0 ,index = rear - 1;这显然是不对的,他的上一个应该是 len - 1下标才对,所以得做判断。

再来实现 enQueue 方法 ,首先判断满没满,没满就插入,满了就返回 false

再来实现 deQueue 方法,先判断 空不空,不空就出队头,空就返回.

  1. class MyCircularQueue {
  2. public int elem[];
  3. public int front;
  4. public int rear;//队尾进元素,所以增加元素的时候,就放到 rear 下标即可
  5. public MyCircularQueue(int k) {
  6. //因为我们浪费了一个空间来区别队列满没满
  7. //所以要多初始化一个空间
  8. this.elem = new int[k + 1];
  9. }
  10. public boolean enQueue(int value) {
  11. if (isFull()) {
  12. return false;
  13. }
  14. elem[rear] = value;
  15. rear = (rear + 1) % elem.length;
  16. return true;
  17. }
  18. public boolean deQueue() {
  19. //为空
  20. if (isEmpty()) {
  21. return false;
  22. }
  23. //出队头
  24. front = (front + 1) % elem.length;
  25. return true;
  26. }
  27. public int Front() {
  28. if (isEmpty()) {
  29. return -1;
  30. }
  31. return elem[front];
  32. }
  33. public int Rear() {
  34. if (isEmpty()) {
  35. return -1;
  36. }
  37. int index = (rear == 0) ? (elem.length - 1) : (rear - 1);
  38. return elem[index];
  39. }
  40. public boolean isEmpty() {
  41. if (front == rear) {
  42. return true;
  43. }
  44. return false;
  45. }
  46. public boolean isFull() {
  47. if (((rear + 1) % elem.length) == front) {
  48. return true;
  49. }
  50. return false;
  51. }
  52. }

3. 双端队列 (Deque)

双端队列,就是两边都能进行出队入队操作的队列。元素可以从队头出队和入队,也可以从队尾出队和入队。

Deque 也是一个接口,实例化的时候也要创建LinkedList的对象。

4. 练习

225. 用队列实现栈

思路:可以使用两个队列实现,只有当两个队列都为空的时候,栈才为空,因为队列是先进先出的结构,而栈是先进后出的结构,所以出栈的时候,就得出不为空的队列,将不为空的队列的所有元素出队放到另一个队列里,最后再出一次队即可,而入栈的时候也同理,就把元素放到不为空的队列里。

  1. class MyStack {
  2. private Queue<Integer> qu1;
  3. private Queue<Integer> qu2;
  4. public MyStack() {
  5. qu1 = new LinkedList<>();
  6. qu2 = new LinkedList<>();
  7. }
  8. public void push(int x) {
  9. //入栈的时候入到不为空的队列里
  10. if (empty()) {
  11. qu1.offer(x);
  12. } else {
  13. if (!qu1.isEmpty()) {
  14. qu1.offer(x);
  15. }
  16. if (!qu2.isEmpty()) {
  17. qu2.offer(x);
  18. }
  19. }
  20. }
  21. public int pop() {
  22. if (empty()) {
  23. return -1;//栈为空
  24. }
  25. if (!qu1.isEmpty()) {
  26. int cnt = qu1.size() - 1;
  27. while (cnt != 0) {
  28. int tmp = qu1.poll();
  29. qu2.offer(tmp);
  30. cnt--;
  31. }
  32. return qu1.poll();
  33. }
  34. int cnt = qu2.size() - 1;
  35. while (cnt != 0) {
  36. int tmp = qu2.poll();
  37. qu1.offer(tmp);
  38. cnt--;
  39. }
  40. return qu2.poll();
  41. }
  42. public int top() {
  43. if (empty()) {
  44. return -1;
  45. }
  46. int tmp = 0;
  47. if (!qu1.isEmpty()) {
  48. int cnt = qu1.size();
  49. while (cnt != 0) {
  50. tmp = qu1.poll();
  51. qu2.offer(tmp);
  52. cnt--;
  53. }
  54. return tmp;
  55. }
  56. int cnt = qu2.size();
  57. while (cnt != 0) {
  58. tmp = qu2.poll();
  59. qu1.offer(tmp);
  60. cnt--;
  61. }
  62. return tmp;
  63. }
  64. public boolean empty() {
  65. //两个队列都为空表示栈为空
  66. if (qu1.isEmpty() && qu2.isEmpty()) {
  67. return true;
  68. }
  69. return false;
  70. }
  71. }

​​​​​​232. 用栈实现队列

思路:利用两个栈来实现,假设一个叫 stack1,一个叫 stack2,简称 s1 和 s2。

固定 入队就入 s1 ,出队首先判断队列是不是空,如果不是空,就出 s2,如果 s2 没元素,那就将 s1 的元素全部出到 s2 里,然后 s2 再来出栈。peek 也同理,只有当两个栈为空的时候,队列才为空。

  1. class MyQueue {
  2. private Stack<Integer> stack1;
  3. private Stack<Integer> stack2;
  4. public MyQueue() {
  5. stack1 = new Stack<>();//s1用来做输入栈
  6. stack2 = new Stack<>();//s2用来做输出栈
  7. }
  8. public void push(int x) {
  9. stack1.push(x);
  10. }
  11. public int pop() {
  12. if (empty()) {
  13. return -1;
  14. }
  15. if (stack2.empty()) {
  16. int size = stack1.size();
  17. while (size != 0) {
  18. int tmp = stack1.pop();
  19. stack2.push(tmp);
  20. size--;
  21. }
  22. return stack2.pop();//是top元素
  23. }
  24. return stack2.pop();//如果s2不为空,说明之前已经按顺序弹出元素了
  25. }
  26. public int peek() {
  27. if (empty()) {
  28. return -1;
  29. }
  30. if (stack2.empty()) {
  31. int size = stack1.size();
  32. while (size != 0) {
  33. int tmp = stack1.pop();
  34. stack2.push(tmp);
  35. size--;
  36. }
  37. return stack2.peek();//是top元素
  38. }
  39. return stack2.peek();
  40. }
  41. public boolean empty() {
  42. if (stack1.empty() && stack2.empty()) {
  43. return true;
  44. }
  45. return false;
  46. }
  47. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/868991
推荐阅读
相关标签
  

闽ICP备14008679号