栈和队列理论基础
队列是先进先出
栈是先进后出
参考学习
1.栈
在Java中,栈和队列是常用的数据结构,它们的底层依赖于Java的集合框架,可以使用不同的类和接口实现各种具体的功能。
Stack是Java提供的内置类,继承自Vector ,支持线程安全操作。
import java.util.Stack;
public class StackExample {
public static void main(String[] args) {
// 创建一个栈
Stack<Integer> stack = new Stack<>();
// 入栈操作
stack.push(10);
stack.push(20);
stack.push(30);
// 查看栈顶元素
System.out.println("栈顶元素: " + stack.peek()); // 输出 30
// 出栈操作
System.out.println("弹出元素: " + stack.pop()); // 输出 30
System.out.println("弹出元素: " + stack.pop()); // 输出 20
// 判断栈是否为空
System.out.println("栈是否为空: " + stack.empty()); // 输出 false
}
}
注:容器适配器 并不是 Java 官方类库中的正式术语,但它通常指代那些通过封装容器操作接口而使得容器能适应不同需求的类。
在 Java 中,栈 (Stack)、队列 (Queue)、双端队列 (Deque) 等数据结构就可以看作是容器适配器,它们提供了不同的操作接口(例如 LIFO、FIFO),并通过适配不同的底层实现来满足特定的需求。
队列
在 Java 中,队列(Queue)是一种先进先出(FIFO)的数据结构,用于存储元素。队列在 java.util 包中有多种实现,如 LinkedList、ArrayDeque 和 PriorityQueue。只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾(Tail/Rear)
出队列:进行删除操作的一端称为队头(Head/Front)
注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。
public static void main(String[] args) {
Queue<Integer> q = new LinkedList<>();
q.offer(1);
q.offer(2);
q.offer(3);
q.offer(4);
q.offer(5); // 从队尾入队列
System.out.println(q.size());
System.out.println(q.peek()); // 获取队头元素
q.poll();
System.out.println(q.poll()); // 从队头出队列,并将删除的元素返回
if(q.isEmpty()){
System.out.println("队列空");
}else{
System.out.println(q.size());
}
}
这个示例展示了队列的基本操作,包括入队、出队、查看队头元素和判空。
队列在 Java 中常用于实现任务调度、消息传递等场景,能够有效管理元素的顺序和执行顺序。
用栈实现队列
使用栈实现队列,----->借助另一个栈
这是一道模拟题,不涉及到具体算法,考察的就是对栈和队列的掌握程度。
使用栈来模拟队列的行为,如果仅仅用一个栈,是一定不行的,所以需要两个栈一个输入栈,一个**输出栈,**这里要注意输入栈和输出栈的关系。
在push数据的时候,只要数据放进输入栈就好,但在pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入),再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据就可以了。
class MyQueue {
Stack<Integer> stackIn;
Stack<Integer> stackOut;
/** Initialize your data structure here. */
public MyQueue() {
stackIn = new Stack<>(); // 负责进栈
stackOut = new Stack<>(); // 负责出栈
}
/** Push element x to the back of queue. */
public void push(int x) {
stackIn.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
dumpstackIn();
return stackOut.pop();
}
/** Get the front element. */
public int peek() {
dumpstackIn();
return stackOut.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
return stackIn.isEmpty() && stackOut.isEmpty();
}
// 如果stackOut为空,那么将stackIn中的元素全部放到stackOut中
private void dumpstackIn(){
if (!stackOut.isEmpty()) return;
while (!stackIn.isEmpty()){
stackOut.push(stackIn.pop());
}
}
}
这段代码实现了一个 队列(FIFO) 的功能,而底层使用了两个栈(Stack)来实现。这种实现方式是经典的 双栈实现队列 思想。
// 如果stackOut为空,那么将stackIn中的元素全部放到stackOut中
private void dumpstackIn(){
if (!stackOut.isEmpty()) return;
while (!stackIn.isEmpty()){
stackOut.push(stackIn.pop());
}
}
用队列实现栈
用一个元素去模拟栈的进元素和出元素。
20. 有效的括号
括号匹配是使用栈解决的经典问题。
有左括号 相应的位置必须有右括号。
由于栈结构的特殊性,非常适合做对称匹配类的题目。
首先要弄清楚,字符串里的括号不匹配有几种情况。在写代码之前要分析好有哪几种不匹配的情况。
有三种不匹配的情况:
-
第一种情况,字符串里左方向的括号多余了 ,所以不匹配(左括号多了)。
-
第二种情况,括号没有多余,但是 括号的类型没有匹配上(左右括号不匹配)。
-
第三种情况,字符串里右方向的括号多余了,所以不匹配(右括号多了)。
代码只要覆盖了这三种不匹配的情况,就不会出问题,可以看出 动手之前分析好题目的重要性。
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。
还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
class Solution {
public boolean isValid(String s) {
Deque<Character> deque = new LinkedList<>();
char ch;
for(int i = 0;i<s.length();i ++){
ch = s.charAt(i);
//碰到左括号,就把相应的右括号入栈
if(ch=='('){
deque.push(')');
}else if(ch == '{'){
deque.push('}');
}else if(ch == '['){
deque.push(']');
}else if (deque.isEmpty() || deque.peek() != ch) {
return false;
}else {//如果是右括号判断是否和栈顶元素匹配
deque.pop();
}
}
//最后判断栈中元素是否匹配
return deque.isEmpty();
}
}
技巧:
1.遇到左括号存对应的右括号。方便比较。
2.遇到空,就return。
3.剪枝:奇数一定不符合匹配原则。可以直接return
1047. 删除字符串中的所有相邻重复项
class Solution {
public String removeDuplicates(String s) {
ArrayDeque<Character> deque = new ArrayDeque<>();
char ch;
for(int i = 0;i < s.length(); i++){
ch = s.charAt(i);
if(deque.isEmpty()|| deque.peek() !=ch){
deque.push(ch);
}else{
deque.pop();
}
}
String str = "";
while(!deque.isEmpty()){
str = deque.pop() + str;
}
return str;
}
}
在删除相邻重复项的时候,其实就是要知道当前遍历的这个元素,我们在前一位是不是遍历过一样数值的元素,那么如何记录前面遍历过的元素呢?
所以就是用栈来存放,那么栈的目的,就是存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素。
从栈中弹出剩余元素,此时是字符串ac,因为从栈里弹出的元素是倒序的,所以再对字符串进行反转一下,就得到了最终的结果。
String str = "";
//剩余的元素即为不重复的元素
while (!deque.isEmpty()) {
str = deque.pop() + str;
}
return str;