当前位置:   article > 正文

【数据结构-JAVA】栈(Stack)和队列(Queue)_java 栈和队列

java 栈和队列

1.1 栈的概念

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

举几个栈在现实生活中的例子,如:羽毛球放进羽毛球筒里,第一个放的最后一个拿,而最后放的最先拿到。

再比如,子弹装入弹匣,最先装的最后打出,而最后装的,最先打出。

1.2 栈的方法

方法

功能

Stack()

构造一个空的栈

E push(E e)

将e入栈,并返回e

E pop()

将栈顶元素出栈并返回

E peek()

获取栈顶元素,并且该栈顶元素不动

int size()

获取栈中有效元素个数

boolean empty()

检测栈是否为空

对比其他的数据结构,可以发现栈是一种较为简单的数据结构,主要特征便是先进后出,里面的方法也较其他数据结构数量更少。

  1. import java.util.Stack;
  2. public class Text {
  3. public static void main(String[] args) {
  4. Stack<Integer> stack = new Stack<>();
  5. stack.push(0);
  6. stack.push(1);
  7. stack.push(2);
  8. stack.push(3);
  9. stack.push(4);
  10. stack.push(5);
  11. System.out.println(stack.size());
  12. System.out.print(stack.pop()+" ");
  13. System.out.print(stack.pop()+" ");
  14. System.out.print(stack.pop()+" ");
  15. System.out.print(stack.peek()+" ");//返回栈顶元素,但该元素依旧留在栈顶
  16. System.out.print(stack.peek()+" ");
  17. System.out.print(stack.pop()+" ");//返回栈顶元素,并将该元素出栈
  18. System.out.print(stack.pop()+" ");
  19. System.out.println(stack.pop()+" ");
  20. System.out.println(stack.empty());
  21. }
  22. }

输出:

6

5 4 3 2 2 2 1 0

true

1.3 栈的模拟实现

栈的实现,可以使用数组,那就是个顺序栈,而如果使用链表,那就是链式栈,而若是使用单链表的话,拿就只能头插头删,只有这样的时间复杂度才都是 O(1);而如果使用的是双向链表的话,也是头插头删:

  1. public static void main(String[] args) {
  2. LinkedList<Integer> stack = new LinkedList<>();
  3. stack.push(1);
  4. stack.push(2);
  5. stack.push(3);
  6. stack.push(4);
  7. System.out.println(stack.peek());
  8. System.out.println(stack.pop());
  9. System.out.println(stack.pop());
  10. System.out.println(stack.pop());
  11. System.out.println(stack.pop());
  12. }

输出:

4

4

3

2

1

在 LinkedList 类的源代码中,也有跟栈一样的方法,发现 push 是头插,且 pop 也是头删:

下面的一个模拟实现,是用数组来实现的。

  1. import java.util.Arrays;
  2. public class MyStack {
  3. public int[] arr;
  4. public int usedSize;
  5. public MyStack() {
  6. arr = new int[10];
  7. }
  8. public int push(int data){
  9. if(isFull()){
  10. arr = Arrays.copyOf(arr,arr.length + 10);
  11. }
  12. arr[usedSize] = data;
  13. usedSize++;
  14. return data;
  15. }
  16. private boolean isFull(){
  17. return usedSize == arr.length;
  18. }
  19. public boolean isEmpty(){
  20. return usedSize == 0;
  21. }
  22. public int pop(){
  23. if(isEmpty()){
  24. throw new EmptyStackException("The Stack is empty!");
  25. }
  26. // int val = usedSize-1;
  27. // usedSize--;
  28. // return arr[val];
  29. //又或者写成如下更加简便的形式:
  30. return arr[--usedSize];
  31. }
  32. public int peek(){
  33. if(isEmpty()){
  34. throw new EmptyStackException("The Stack is empty!");
  35. }
  36. return arr[usedSize-1];
  37. }
  38. public int size(){
  39. return usedSize;
  40. }
  41. }

题目 最小栈中:

在 push 函数中,else 的代码块中,if(val <= minStack.peek()) ,也就是说,stack 里如果出现相同的最小元素,也要存放到 minStack 中,

而在 pop 函数中, int val = stack.pop (拆箱了),后面if 判断 就可以写成 if(val == minStack.peek())

1.4 栈的使用场景

1.4.1 逆序打印链表

这个题目,采用以下两种方法来求解:

方法一:递归打印
  1. //递归打印链表
  2. public static void playList(Node pHead){
  3. if(pHead == null){
  4. return ;
  5. }
  6. if(pHead.next == null){
  7. System.out.println(pHead.val);
  8. return ;
  9. }
  10. playList(pHead.next);
  11. System.out.println(pHead.val);
  12. }
方法二:使用栈来打印
  1. //使用栈来逆着打印单向链表
  2. public static void playList1(){
  3. Stack<Node> stack = new Stack<>();
  4. Node cur = head;
  5. while(cur != null){
  6. stack.push(cur);
  7. cur = cur.next;
  8. }
  9. while(!stack.isEmpty()){
  10. Node temp = stack.pop();
  11. System.out.print(temp.val+" ");
  12. }
  13. System.out.println();
  14. }

1.4.2 括号匹配

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。

左括号必须以正确的顺序闭合。

每个右括号都有一个对应的相同类型的左括号

  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. if(ch == '(' || ch == '{'||ch == '['){
  7. stack.push(ch);
  8. }else{
  9. if(stack.empty()){
  10. return false;
  11. }
  12. char ch2 = stack.peek();
  13. if(ch == ')' && ch2 == '(' || ch == '}' && ch2 == '{' || ch == ']' && ch2 == '['){
  14. stack.pop();
  15. }else{
  16. return false;
  17. }
  18. }
  19. }
  20. if(!stack.empty()){
  21. return false;
  22. }
  23. return true;
  24. }
  25. }

1.4.3 逆波兰表达式求值

给你一个字符串数组 tokens ,表示一个根据 逆波兰表达式 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

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

1.4.4 栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。

  1. import java.util.ArrayList;
  2. import java.util.Stack;
  3. public class Solution {
  4. public boolean IsPopOrder(int [] pushA,int [] popA) {
  5. int j = 0;
  6. Stack<Integer> stack = new Stack<>();
  7. for(int i = 0; i < pushA.length;i++){
  8. stack.push(pushA[i]);
  9. while(j < popA.length && !stack.empty() && stack.peek().equals(popA[j])){
  10. stack.pop();
  11. j++;
  12. }
  13. }
  14. if(!stack.empty()){
  15. return false;
  16. }
  17. return true;
  18. }
  19. }

注意:stack.peek() == popA[j] 最好修改成 stack.peek().equals(popA[j])

1.4.5 最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

MinStack() 初始化堆栈对象。

void push(int val) 将元素val推入堆栈。

void pop() 删除堆栈顶部的元素。

int top() 获取堆栈顶部的元素。

int 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. minStack.push(val);
  12. }else{
  13. if(val <= minStack.peek()){
  14. minStack.push(val);
  15. }
  16. }
  17. }
  18. public void pop() {
  19. if(!stack.empty()){
  20. Integer val = stack.pop();
  21. if(val.equals(minStack.peek())){
  22. minStack.pop();
  23. }
  24. }
  25. }
  26. public int top() {
  27. if(!stack.empty()){
  28. return stack.peek();
  29. }
  30. return -1;
  31. }
  32. public int getMin() {
  33. return minStack.peek();
  34. }
  35. }

1.5 栈、虚拟机栈和栈帧的区别

栈是一种先进后出的数据结构,虚拟机栈是运行程序时的内存,而方法在虚拟机栈开辟的内存就叫栈帧。

2. 队列(Queue)

2.1 队列的概念

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的原则。(FIFO——First In First Out)

入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行删除操作的一端称为队头 (Head/Front)

2.2 队列的方法

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

方法

功能

boolean offer(E e)

入队列

E poll()

出队列

E peek()

获取队头元素

int size()

获取队列有效元素个数

boolean isEmpty()

检测队列是否为空

注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。

  1. public static void main(String[] args) {
  2. Queue<Integer> queue = new LinkedList<>();
  3. queue.offer(6);
  4. queue.offer(7);
  5. queue.offer(8);
  6. queue.offer(9);
  7. queue.offer(10);
  8. System.out.println(queue.toString());
  9. System.out.println(queue.size());
  10. System.out.println(queue.peek());
  11. System.out.println(queue.poll());
  12. System.out.println(queue.poll());
  13. System.out.println(queue.poll());
  14. System.out.println(queue.poll());
  15. System.out.println(queue.poll());
  16. }

输出:

[6, 7, 8, 9, 10]

5

6

6

7

8

9

10

LinkedList 重写 Queue 里面的方法,发现 offer 是尾插:

而 poll 是头删:

2.3 队列的模拟实现

单链表实现队列,首先得记录尾部,并且只能尾部插入,头部删除,而不能头部插入,尾部删除,因为此时,尾部的前一个节点不可知。

  1. public class MyQueue {
  2. static class Node{
  3. public int val;
  4. public Node next;
  5. public Node(int val) {
  6. this.val = val;
  7. }
  8. }
  9. public Node head;
  10. public Node last;
  11. public int usedSize;
  12. //入队列
  13. public void offer(int data){
  14. Node node = new Node(data);
  15. //队列为空时:
  16. if(head == null){
  17. head = node;
  18. last = node;
  19. }else{
  20. //队列不为空时,单向链表的尾部入队列:
  21. last.next = node;
  22. last = node;
  23. }
  24. usedSize++;
  25. }
  26. //出队列
  27. public int poll(){
  28. //队列为空时:
  29. if(empty()){
  30. throw new EmptyQueueException("队列为空,出队列失败!");
  31. }
  32. int val = head.val;
  33. //队列只有一个元素时:
  34. if(head.next == null){
  35. last = last.next;
  36. }
  37. head = head.next;
  38. usedSize--;
  39. return val;
  40. }
  41. public int peek(){
  42. if(empty()){
  43. throw new EmptyQueueException("队列为空,队头的首个元素获取失败!");
  44. }
  45. return head.val;
  46. }
  47. public boolean empty(){
  48. return usedSize == 0;
  49. }
  50. public int getUsedSize(){
  51. return usedSize;
  52. }
  53. }
  1. public class Text {
  2. public static void main(String[] args) {
  3. MyQueue myQueue = new MyQueue();
  4. myQueue.offer(1);
  5. myQueue.offer(5);
  6. myQueue.offer(8);
  7. myQueue.offer(13);
  8. myQueue.offer(19);
  9. System.out.println(myQueue.getUsedSize());
  10. System.out.println(myQueue.peek());
  11. System.out.println(myQueue.poll());
  12. System.out.println(myQueue.poll());
  13. System.out.println(myQueue.poll());
  14. System.out.println(myQueue.poll());
  15. System.out.println(myQueue.poll());
  16. }
  17. }

输出:

5

1

1

5

8

13

19

而双向链表实现队列,头删尾插 ,或头插尾删的时间复杂度都是 O(1) ,下面的模拟实现,是头插尾删:

  1. public class MyQueue2 {
  2. static class Node{
  3. public int val;
  4. public Node prev;
  5. public Node next;
  6. public Node(int val) {
  7. this.val = val;
  8. }
  9. }
  10. public Node head;
  11. public Node last;
  12. public int usedSize;
  13. public void offer(int data){
  14. Node node = new Node(data);
  15. if(head == null){
  16. last = node;
  17. }else{
  18. node.next = head;
  19. head.prev = node;
  20. }
  21. head = node;
  22. usedSize++;
  23. }
  24. public int poll(){
  25. if(empty()){
  26. throw new EmptyQueueException("队列为空,出队列失败!");
  27. }
  28. int ret = last.val;
  29. if(last == head){
  30. last = null;
  31. head = null;
  32. }else{
  33. last = last.prev;
  34. last.next.prev = null;
  35. last.next = null;
  36. }
  37. usedSize--;
  38. return ret;
  39. }
  40. public int getUsedSize(){
  41. return usedSize;
  42. }
  43. public boolean empty(){
  44. return usedSize == 0;
  45. }
  46. public int peek(){
  47. return last.val;
  48. }
  49. }
  1. public static void main(String[] args) {
  2. MyQueue2 myQueue2 = new MyQueue2();
  3. myQueue2.offer(9);
  4. myQueue2.offer(6);
  5. myQueue2.offer(4);
  6. myQueue2.offer(3);
  7. myQueue2.offer(2);
  8. System.out.println(myQueue2.getUsedSize());
  9. System.out.println(myQueue2.peek());
  10. System.out.println(myQueue2.poll());
  11. System.out.println(myQueue2.poll());
  12. System.out.println(myQueue2.poll());
  13. System.out.println(myQueue2.poll());
  14. System.out.println(myQueue2.poll());
  15. }

输出:

5

9

9

6

4

3

2

2.4 循环队列

实际中我们有时还会使用一种队列叫循环队列。如操作系统中,讲解生产者消费者模型时可以就会使用循环队列。 循环队列通常使用数组实现。

如果我们使用数组去实现队列,就会遇到这样一个问题:一旦这个数组满了,即使释放了队列前面的元素,我们也无法插入新的元素。但是使用循环队列,我们能使用这些空间去存储新的值。

循环队列的实现思路:

  1. 用 front 和 rear 引用来分别存储队列的头元素和尾部的下一个元素,当队列为空时,front 和 rear 指向数组下标为 0 的位置:

  1. 从 rear 位置处插入第一个元素 666 , front 不变,而 rear 往后走一个:

  1. 以此类推,将整个队列装满元素:

不知同学们发现没有,数组下标为 7 的位置是空的,但我在上面却说“满”,这是为何?

如果连下标为 7 的地方也放了元素,那么 rear 和 front 又重新指向同一个地方了,那如何与空区别?

上面两个问题,让我来一一解答:

其实判断循环队列是否满了,有很多方法,这里讲解两种。

第一种,牺牲一个空间,为后续判断是否满了做准备。

在这里我们先思考一个问题,循环队列,如何循环呢? 0 -> 1 -> 2 -> 3 - >4 ->5 ->6 -> 7 ->0 ,所以不管对于 front 还是 rear ,下标7 的下一个是 0 ,而不是8 ,也就是说,我们不能再使用 front++ ,或 rear++,而是

front = (front + 1) % arr.length
rear = (rear + 1) % arr.length

而最后一个空间不去用,就是为了区分空和满的状态,空是 front == rear, 而满就是 (rear + 1) % arr.length == front ,即 rear 的下一个位置,与 front 相同。此时队列为满,不再添加。除非在头部进行删除,这样 front = (front + 1) % arr.length , front 来到下一个:

这时,又可以在尾部进行添加了。

而第二种判断循环队列是否满的方法,是利用一个 size 变量,来记录队列已存储的总个数,这时候整个数组都可以放满。

下面的代码实现,是通过牺牲一个空间来判断满否:

  1. class MyCircularQueue {
  2. private int[] elem;
  3. private int front;//表示队列的头
  4. private int rear;//表示队列的尾
  5. public MyCircularQueue(int k) {
  6. //如果是以牺牲一个空间来作为判断是否满了的标准,这里可以多申请一个空间
  7. elem = new int[k+1];
  8. }
  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. //出队列
  19. public boolean deQueue() {
  20. if(isEmpty()){
  21. return false;
  22. }
  23. front = (front + 1) % elem.length;
  24. return true;
  25. }
  26. public int Front() {
  27. if(isEmpty()){
  28. return -1;
  29. }
  30. return elem[front];
  31. }
  32. public int Rear() {
  33. if(isEmpty()){
  34. return -1;
  35. }
  36. return rear == 0 ? elem[elem.length - 1] :elem[rear - 1];
  37. }
  38. public boolean isEmpty() {
  39. return rear == front;
  40. }
  41. public boolean isFull() {
  42. return (rear + 1) % elem.length == front;
  43. }
  44. }

2.5 双端队列(Deque)

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。 那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。

3. 栈和队列的相互实现

3.1 利用栈去实现队列

3.1.1 实现思路

  1. 申请两个栈:

  1. offer 时,如果是首次 offer,那么指定一个栈用来存放元素入队列,这里是 stack1 入队列,往后入队列就都往 stack1 去放。

  1. poll 时,找到非空的那个栈,这里是 stack2 ,往 stack2 依次放入 stack1 里的所有元素,最后 stack2 栈顶的元素就是队列 poll 的元素。

  1. 继续往队列中放元素,放入 stack1 中:

  1. 队列中继续删掉 22 和 41,那么 stack2 中依次出栈,为空:

  1. 队列继续出队列,由于 stack2 为空,而 stack1 不为空,那么将 stack1 中的元素依次放于 stack2 中,再依次出栈,一直到两个栈都没有元素为止。

3.1.2 具体代码实现

  1. import java.util.Stack;
  2. public class MyQueue {
  3. private Stack<Integer> stack1 ;
  4. private Stack<Integer> stack2 ;
  5. public MyQueue() {
  6. stack1 = new Stack<>();
  7. stack2 = new Stack<>();
  8. }
  9. //选择 stack1 存放入队列时的数据
  10. public void push(int x) {
  11. stack1.push(x);
  12. }
  13. //选择 stack2 存放出队列时的数据
  14. public int pop() {
  15. if(empty()){
  16. return -1;
  17. }
  18. if(stack2.empty()){
  19. while(!stack1.empty()){
  20. stack2.push(stack1.pop());
  21. }
  22. }
  23. return stack2.pop();
  24. }
  25. public int peek() {
  26. if(empty()){
  27. return -1;
  28. }
  29. if(stack2.empty()){
  30. while(!stack1.empty()){
  31. stack2.push(stack1.pop());
  32. }
  33. }
  34. return stack2.peek();
  35. }
  36. public boolean empty() {
  37. return stack1.empty() && stack2.empty();
  38. }
  39. }

3.2 利用队列去实现栈

3.2.1 实现思路

  1. 申请两个队列,queue1 和 queue2 :

  1. 入栈时,若是首次入栈,则选一个空的队列存放入栈数据即可,这里选择了 queue1 :

  1. 出栈时,找到那个非空的队列,如 queue1,队头元素 queue1.size() - 1 次出队列到一个空的队列中,如 queue2。然后让 queue1 中最后一个元素出队列,这就是出栈的元素。

  1. 此时若是再入栈,那么将元素入队列到非空的队列中,如 queue2

  1. 若是再出栈,便如步骤3 所示。

  1. 3、4 反复操作,一直到两个队列为空为止。

3.2.2 代码

  1. public class MyStack {
  2. private Queue<Integer> queue1 ;
  3. private Queue<Integer> queue2 ;
  4. public MyStack() {
  5. queue1 = new LinkedList<>();
  6. queue2 = new LinkedList<>();
  7. }
  8. public void push(int x) {
  9. if(!queue1.isEmpty()){
  10. queue1.offer(x);
  11. }else if(!queue2.isEmpty()){
  12. queue2.offer(x);
  13. }else{
  14. queue1.offer(x);
  15. }
  16. }
  17. public int pop() {
  18. if(empty()){
  19. return -1;
  20. }
  21. if(!queue1.isEmpty()){
  22. int size = queue1.size();
  23. for (int i = 0; i < size - 1 ; i++) {
  24. queue2.offer(queue1.poll());
  25. }
  26. return queue1.poll();
  27. }else{
  28. int size = queue2.size();
  29. for (int i = 0; i < size - 1 ; i++) {
  30. queue1.offer(queue2.poll());
  31. }
  32. return queue2.poll();
  33. }
  34. }
  35. public int top() {
  36. if(empty()){
  37. return -1;
  38. }
  39. int val = -1;
  40. if(!queue1.isEmpty()){
  41. int size = queue1.size();
  42. for (int i = 0; i < size ; i++) {
  43. val = queue1.poll();
  44. queue2.offer( val);
  45. }
  46. return val;
  47. }else{
  48. int size = queue2.size();
  49. for (int i = 0; i < size ; i++) {
  50. val = queue2.poll();
  51. queue1.offer( val);
  52. }
  53. return val;
  54. }
  55. }
  56. public boolean empty() {
  57. return queue1.isEmpty() && queue2.isEmpty();
  58. }
  59. }

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

闽ICP备14008679号