[数据结构] 栈和队列
目录
1.栈
1.1 栈的概念
1.2 栈的模拟实现
1.3 栈的应用
1.3.1 将递归转为循环
1.3.2 括号匹配
1.3.3 逆波兰表达式求值
1.3.4 出栈入栈次序匹配
1.3.5 最小栈
2. 队列
2.1 队列的概念
2.2 队列中的方法
2.3 队列中方法的模拟实现
2.4 循环队列
2.4.1 循环队列的概念
2.4.2 循环队列的实现
2.5 双端队列
2.6 相关题目
2.6.1 用队列实现栈
2.6.2 用栈实现队列
1.栈
1.1 栈的概念
栈是一种特殊的线性表,只能从一端进行插入和删除操作,进行插入和删除操作的一端称为栈顶,另一端称为栈底。
压栈:将元素放入栈中,出栈:将元素从栈中取出。
栈遵循 先进后出,后进先出 原则。
栈在Java中代码实现的类是Stack类。
1.2 栈的模拟实现
这里用的顺序表实现的栈:
import java.util.Arrays;
public class MyStack {public int[] elem;public int stackSize;public MyStack() {elem = new int[10];}//入栈public void push(int val) {if(isFull()) {elem = Arrays.copyOf(elem,2 * elem.length);}elem[stackSize] = val;stackSize++;}//出栈public int pop() {if(isEmpty()) {return -1;}stackSize--;return elem[stackSize-1];}//查看栈顶元素public int peek() {if(isEmpty()) {return -1;}return elem[stackSize-1];}//返回栈的真实元素数量public int size() {return stackSize;}//判断栈为空public boolean isEmpty() {return stackSize == 0;}//判断栈为满public boolean isFull() {return stackSize == elem.length;}
}
1.3 栈的应用
1.3.1 将递归转为循环
逆序打印链表:
这里利用了递归的后进先出的原理,将链表的数据放入栈中,然后再从栈中取出来打印。
//链表逆序(递归)public void printList(ListNode node) {if(node != null) {printList(node.next);System.out.println(node.val);}}//链表逆序(循环)public void printList1(ListNode node) {if(node == null) {return;}Stack<Integer> stack = new Stack<>();while(node != null) {stack.add(node.val);node = node.next;}while(!stack.isEmpty()) {System.out.print(stack.pop() + " ");}}
1.3.2 括号匹配
题目链接
解题思路:这里是利用栈,将字符串中的左符号依次放入栈中,直到遇到右符号,再与栈顶元素进行比较,看是否相同。
代码实现:
class Solution {public boolean isValid(String s) {Stack<Character> stack = new Stack<>();for(int i = 0; i < s.length(); i++) {char c = s.charAt(i);if(c == '[' || c == '(' || c == '{') {stack.push(c);}else {if(stack.isEmpty()) {return false;}if(stack.peek() == '(' && c == ')' || stack.peek() == '[' && c == ']' || stack.peek() == '{' && c == '}') {stack.pop();}else {return false;}}}if(stack.isEmpty()) {return true;}return false; }
}
1.3.3 逆波兰表达式求值
题目链接
逆波兰表达式就是后缀表达式,是计算机运算使用的表达式,中缀表达式就是我们平时写的表达式,例如:2 *(3 +4)- 9 转换为后缀表达式就是: 2 3 4 + * 9 - ,转换过程:
解题思路:这道题是将数字依次压入栈中,碰到运算符,取出栈顶元素作为右操作数,栈顶下的一个元素作为左操作数,运算完后放入栈中,循环往复。
代码实现:
class Solution {public int evalRPN(String[] tokens) {Stack<Integer> stack = new Stack<>();for(int i = 0; i < tokens.length; i++) {String s = tokens[i];if(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")) {int x = stack.pop();int y = stack.pop();switch(s) {case "+":stack.push(y + x);break;case "-":stack.push(y - x);break;case "*":stack.push(y * x);break;case "/":stack.push(y / x);break;}}else {stack.push(Integer.parseInt(s));}}return stack.peek();}
}
1.3.4 出栈入栈次序匹配
题目链接
解题思路:
将第一个数组的数依次放入栈中,每次放完后跟第二个数组的元素比较,相同则抛出栈顶元素,不相同,则接着往栈中放元素。
代码实现:
import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可*** @param pushV int整型一维数组* @param popV int整型一维数组* @return bool布尔型*/public boolean IsPopOrder (int[] pushV, int[] popV) {Stack<Integer> stack = new Stack<>();int j = 0;for (int i = 0; i < pushV.length; i++) {stack.push(pushV[i]);while(!stack.isEmpty() && stack.peek() == popV[j]) {stack.pop();j++;}}if(!stack.isEmpty()) {return false;}return true;}
}
1.3.5 最小栈
题目链接
解题思路:这题需要设立两个栈,一个普通栈,一个最小栈,最小栈为空时,放入元素,然后后面新放入的元素要跟最小栈的栈顶元素比较,比栈顶元素大则不放,其他情况放。返回最小栈的栈顶元素就是最小值。
代码实现:
class MinStack {Stack<Integer> stack;Stack<Integer> minStack;public MinStack() {stack = new Stack<>();minStack = new Stack<>();}public void push(int val) {stack.push(val);if(minStack.isEmpty()) {minStack.push(val);}else {if(minStack.peek() >= val) {minStack.push(val);}}}public void pop() {if(stack.isEmpty()) {return;}int a = stack.pop();if(minStack.peek() == a) {minStack.pop();}}public int top() {if(stack.isEmpty()) {return -1;}return stack.peek();}public int getMin() {return minStack.peek();}
}/*** Your MinStack object will be instantiated and called as such:* MinStack obj = new MinStack();* obj.push(val);* obj.pop();* int param_3 = obj.top();* int param_4 = obj.getMin();*/
2. 队列
2.1 队列的概念
队列遵循先进先出,后进后出的原则,只允许在一端插入数据,另一端取出数据。插入数据的一端称为队尾,取出数据的一端称为队头。
在Java中队列是一个接口Queue,里面包含队列的抽象方法,具体实现是在LinkedList类里面实现的,队列的底层逻辑是链表。
2.2 队列中的方法
下面是队列中的抽象方法:
add和offer方法都是:往队列里面添加元素
remove和poll方法都是:删除队列中的元素
element和peek方法都是:获取队头元素
我们常用的方法是:offer() poll() peek()
2.3 队列中方法的模拟实现
下面是我自己实现的队列(Queue)里面的方法:
package Queuedemo;public class MyQueue {static class ListNode {public int val;public ListNode prev;public ListNode next;public ListNode(int val) {this.val = val;}}public ListNode head;public ListNode last;//入队操作(尾插)public void offer(int key) {ListNode node = new ListNode(key);if(isEmpty()) {head = node;last = node;return;}last.next = node;node.prev = last;last = node;}//出队操作public int poll() {if(isEmpty()) {return -1;}int val = head.val;if(head.next == null) {head = null;last = null;}else {head.next.prev = null;head = head.next;}return val;}//查询头节点的值public int peek() {if(isEmpty()) {return -1;}return head.val;}//判断队列是否为空public boolean isEmpty() {if(head == null && last == null) {return true;}return false;}//获取队列的大小public int size() {if(isEmpty()) {return -1;}ListNode node = head;int size = 0;while(head != null) {size++;}return size;}}
2.4 循环队列
2.4.1 循环队列的概念
循环队列通常是由数组实现的,图形如下:
这就是一个循环队列,将一个数组的首和尾连接起来。
rear如何走到最后再从头开始呢?
现在rear在6下标位置,假设让rear走到2位置,可以利用(rear + rear到2下标位置的距离) % 数组长度。
(6 + 4)% 8 = 2;
front如何从队头逆向移动到队尾?
现在front在1下标位置,假设让front走到5位置,可以利用(front + 数组长度 - front逆序到5下标位置的距离) % 数组长度。
(1 + 8 - 4)% 8 = 5;
如何区分队列是空还是满?
方法1:
添加size属性记录,每次入队时加1,出队时减1.
方法2:
保留一个位置,当(rear + 1) % 数组长度 等于 front 时候就停止添加元素,这时候rear所在位置没有元素,处于数组的最后一个位置。
方法3:
设置一个标记变量isFull :
初始情况下:front == rear 时,isFull = false。
添加元素时,每添加一个元素,判断front是否等于rear ,相等则isFull = true。
删除元素时,isFull = false。
2.4.2 循环队列的实现
设计循环队列
代码实现:
class MyCircularQueue {public int[] elem;public int front;//队头public int rear;//队尾//创建循环队列public MyCircularQueue(int k) {elem = new int[k+1];}//插入元素public boolean enQueue(int value) {//判断队列是否已满if(isFull()) {return false;}elem[rear] = value;rear = (rear + 1) % elem.length;return true;}//删除元素public boolean deQueue() {//判断队列是否为空if(isEmpty()) {return false;}front = (front + 1) % elem.length;return true;}//获取队首元素public int Front() {if(isEmpty()) {return -1;}return elem[front];}//获取队尾元素public int Rear() {if(isEmpty()) {return -1;}return elem[(rear + elem.length - 1) % elem.length];}//判断队列是否为空public boolean isEmpty() {return rear == front;}//判断队列是否已满public boolean isFull() {return (rear + 1) % elem.length == front;}}/*** Your MyCircularQueue object will be instantiated and called as such:* MyCircularQueue obj = new MyCircularQueue(k);* boolean param_1 = obj.enQueue(value);* boolean param_2 = obj.deQueue();* int param_3 = obj.Front();* int param_4 = obj.Rear();* boolean param_5 = obj.isEmpty();* boolean param_6 = obj.isFull();*/
2.5 双端队列
双端队列是指允许两队都可以进行插入和删除操作,在Java中对应的是Deque接口,具体实现方法的类是LinkedList类。
在实际开发中,栈和队列都可以实现该接口。
//双端队列的顺序实现Deque<Integer> deque1 = new ArrayDeque<>();//双端队列的链式实现Deque<Integer> deque2 = new LinkedList<>();
2.6 相关题目
2.6.1 用队列实现栈
题目链接
代码实现:
class MyStack {public Queue<Integer> que1;public Queue<Integer> que2;public MyStack() {que1 = new LinkedList<>();que2 = new LinkedList<>();}//将元素存入栈中public void push(int x) {if(!que1.isEmpty()) {que1.offer(x);}else if(!que2.isEmpty()) {que2.offer(x);}else {que1.offer(x);}}//移除并返回栈顶元素public int pop() {if(!que1.isEmpty()) {int size = que1.size();while(size-1 != 0) {que2.offer(que1.poll());size--;}return que1.poll();}else if (!que2.isEmpty()) {int size = que2.size();while(size-1 != 0) {que1.offer(que2.poll());size--;}return que2.poll();}else {return -1;}}//返回栈顶元素public int top() {if(!que1.isEmpty()) {int size = que1.size();int tmp = -1;while(size != 0) {tmp = que1.peek();que2.offer(que1.poll());size--;}return tmp;}else if (!que2.isEmpty()) {int size = que2.size();int tmp = -1;while(size != 0) {tmp = que2.peek();que1.offer(que2.poll());size--;}return tmp;}else {return -1;}}//判断栈是否为空public boolean empty() {if(que1.isEmpty() && que2.isEmpty()) {return true;}return false;}
}/*** Your MyStack object will be instantiated and called as such:* MyStack obj = new MyStack();* obj.push(x);* int param_2 = obj.pop();* int param_3 = obj.top();* boolean param_4 = obj.empty();*/
2.6.2 用栈实现队列
题目链接
代码实现:
class MyQueue {Stack<Integer> stack1;Stack<Integer> stack2;public MyQueue() {stack1 = new Stack<>();stack2 = new Stack<>();}//将元素放入队列中public void push(int x) {stack1.push(x);}//删除队列中元素并返回public int pop() {if(stack2.isEmpty()) {while(stack1.size() != 0) {stack2.push(stack1.pop());}}return stack2.pop();}//返回队列开头的元素public int peek() {if(stack2.isEmpty()) {while(stack1.size() != 0) {stack2.push(stack1.pop());}}return stack2.peek();}//判断队列是否为空public boolean empty() {if(stack1.isEmpty() && stack2.isEmpty()) {return true;}return false;}
}/*** Your MyQueue object will be instantiated and called as such:* MyQueue obj = new MyQueue();* obj.push(x);* int param_2 = obj.pop();* int param_3 = obj.peek();* boolean param_4 = obj.empty();*/