栈与队列
理论基础
栈与队列的基本实现相关知识
-
无论是栈还是队列数据结构,其底层都有可能是通过数组或者链表来实现。我们可以将栈、队列视为一种受限制的数组或链表。
-
栈——先进后出。 队列——先进先出。
调用Java自带的库来完成栈的基本操作——Stack
//直接调用Java提供的栈类
Stack<Integer> stack=new Stack<>();
//执行入栈操作
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
//获取栈顶元素
System.out.println(stack.peek());
//判断栈是否为空
System.out.println(stack.isEmpty());
//获取栈的大小
System.out.println(stack.size());
//出栈
stack.pop();
调用Java自带的库来学习调用队列的操作
//直接调用Java提供的队列类
Queue<Integer> queue=new LinkedList<>();
//入队操作
queue.offer(1);
queue.offer(2);
queue.offer(3);
queue.offer(4);
//判断队列是否为空
queue.isEmpty();
//出队操作
queue.poll();
//遍历队列 —— 使用迭代器
Iterator<Integer> it=queue.iterator();
while(it.hasNext()){
int element=it.next();
System.out.println(element);
}
这里也稍微复习一下Java中关于多态的知识。Java中与栈Stack不同的是,Queue只是一个接口,而LinkedList是其的一个实现类。
Queue<Integer> queue=new LinkedList<>();
Queue是一个接口,它定义了一系列用于操作队列的方法,如add(), remove(), element(), poll(), peek()等。LinkedList是一个实现了Queue接口的类,因此它提供了这些方法的实现。
Java中多态性主要通过以下两种方式来实现:
- 编译时多态:方法重载。在一个类中,可以定义多个同名但是参数列表不同的方法,在调用时,Java编译器会根据调用时提供的参数类型 以及 参数数量 来确定调用哪个方法。
- 运行时多态:方法重写。 子类可以重写父类/父接口中的方法,并在运行时根据对象的实际类型来确定调用哪个方法。通常涉及到向上转型(把子类对象赋值给父类引用)和向下转型(把父类引用强制转换为子类引用)。
- 在这里,通过将LinkedList向上转型为Queue类型,限制了可以通过这个引用来访问的方法——即只能访问Queue接口中定义的方法,而不能访问LinkedList中的特有方法。
class Animal {
void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Cat meows");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
Animal myAnimal1 = new Dog();
Animal myAnimal2 = new Cat();
myAnimal1.makeSound(); // 输出 "Dog barks"
myAnimal2.makeSound(); // 输出 "Cat meows"
}
}
232.用栈实现队列
使用栈来实现队列,一定是要使用双栈,如果仅使用一个栈,无法完成队列的操作。
这里两个栈分为stackInput 和 stackOutput
两个关键:
-
入队操作:直接入stackInput栈即可
-
出队操作:
-
如果stackOutput栈中尚有元素,则直接stackOutput.pop()即可
-
如果stackOutput栈中没有元素,则要先将stackInput这个栈中的元素一次性全部倒入到stackOutput栈中,然后再stackOutput这个栈pop
具体代码实现:
class MyQueue {
//使用双栈来实现队列--一个是输入栈,一个是输出栈
private Stack<Integer> stackInput=new Stack<>();
private Stack<Integer> stackOutput=new Stack<>();
public MyQueue() {
}
public void push(int x) {
stackInput.push(x);
}
public int pop() {
//因为在peek()方法中一并判断了输出栈为空和不为空的情况
//所以这里就直接调用peek()方法即可。
this.peek();
return stackOutput.pop();
}
public int peek() {
//如果输出栈不为空,则输出栈的栈顶元素即为当前队列首元素
if(!stackOutput.isEmpty()){
return stackOutput.peek();
}
//由于题目中已经设置了所有操作都是有效的这个前提,故不考虑队列为空的情况
//如果输出栈为空,此时输入栈不为空.将其元素全部倒入输出栈中。
while(!stackInput.isEmpty()){
int num=stackInput.pop();
stackOutput.push(num);
}
return stackOutput.peek();
}
public boolean empty() {
//输入输出两个栈都为空,则当前队列为空
if(stackInput.isEmpty()&&stackOutput.isEmpty()){
return true;
}
else
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();
*/
225. 用队列实现栈
因为队列是先进先出的逻辑,仔细推敲会发现如果继续采用上一题的对策,即一个为input另一个为output的话,顺序丝毫没有改变。
所以两个队列实现栈,一个专门用于存储,另一个就只是备份。
class MyStack {
//如之前分析,一个队列用于存储,另一个队列是用于备份存储
//这里queue1一直用于存储,queue2始终用于备份
private Queue<Integer> queue1=new LinkedList<>();
private Queue<Integer> queue2=new LinkedList<>();
public MyStack() {
}
public void push(int x) {
//保证每次push的时候,queue1中的顺序都要和真实的栈中的顺序一致
//所以先存入到辅助队列queue2中
queue2.offer(x);
//就是在这里实现了队列中元素的顺序和真实的栈保持一致
while(!queue1.isEmpty()){
queue2.offer(queue1.poll());
}
//两个队列交换
Queue<Integer> temp=queue1;
queue1=queue2;
queue2=temp;
}
public int pop() {
return queue1.poll();
}
public int top() {
return queue1.peek();
}
public boolean empty() {
return queue1.isEmpty();
}
}
/**
* 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();
*/