栈 Stack
特性:LIFO 后进先出,相当于只保留尾插尾删的顺序表
栈中需要注意的问题:
-
pop() 和 peek() 的区别
pop():返回栈顶元素并删除
peek():只是取出栈顶元素,并没有删除操作 -
栈可以将递归转化为循环
比如说是逆序打印链表,如果用递归的形式就是
public static void print(Node head){
if(head != null){
print(head.next);
System.out.println(head.data);
}
}
用栈的方式将递归化成循环
public static void print(Node head){
Node cur = head;
Stack<Node> s = new Stack();
while(cur != null){
s.push(cur);
cur = cur.next;
}
while(!s.empty()){
Node top = s.peek();
System.out.println(top.data);
s.pop();
}
}
Q1: 为什么需要将递归转化成循环?
- 如果说函数中所给的链表比较多,那么实现他的逆序打印时,递归调用的深度比较深,而每一次函数调用都得需要占用栈空间 ,而栈空间是有一定大小的,一但调用深度比较深,就有可能会栈溢出,所以需要转化为循环。而有些转化可以直接转,有些就需要借助栈来转换
Q2:为什么栈可以将递归转化成循环?
- 递归调用时,后调用的先结束,最先调用的最后结束,和栈的特性一样,所以可以通过栈将递归转化成循环
Q3:上面说到的每个函数运行的时候系统会分配栈空间和栈数据结构有什么区别
- 栈空间是指具有特殊功能的内存空间,也具有后进先出的特性
- Stack是一种具有后进先出特性的数据结构
熟悉栈的几个接口:push,pop,peek,size,empty
队列(Queue)
特性:FIFO 先进先出
队列中要注意的问题:
-
队列分队头队尾,要从队尾入队列,从对头出队列,就好比吃饭排队一样,站队的时候我们要从队伍的尾部开始排,等排到队头才可以打饭离开队伍
-
如何用连续的空间实现队列?
设置一个队头front,一个队尾rear,
当要让一个元素入队时,相当于尾插,rear后移
当要让一个元素出队时,有两种:- 头删,把front指向的元素删掉,将后面的元素前移,但这样的话时间复杂度时O(N)
- front移动,如果要删除当前的队头,只需要将front往后移一位即可
但是因为空间是一定的,让front移动来删除元素可能会出现假溢出的情况
-
如何解决假溢出? 循环队列
但是由于front指向的是队头元素,rear指向的是队尾元素(也就是下一个要插的位置),此时就会存在下图的情况:
- 观察发现循环队列中队列空和队列满时front,rear都是指向同一个位置,所以如何区分队列空和队列满呢?
有三种方式可以解决:
- 少用一个存储位置 让(rear+1)%空间总大小==front
但这样因为少用一个空间,会造成空间利用率较小
2. 设置一个标记boolean flag = false;
入队列:在rear位置尾插元素 flag = true
出队列:front往后移动 flag = false;
这种情况下 当front == rear && flag == false时队列为空;front == rear && flag == true队列为满
但这种也用的少,因为比较复杂
3. 记录队列中有效元素的个数
- 观察发现循环队列中队列空和队列满时front,rear都是指向同一个位置,所以如何区分队列空和队列满呢?
这样使用连续空间实现队列头删非常不方便,所以一般不用连续空间实现队列,而是采用链式结构实现队列,底层队列的实现就是通过LinkedList实现
- 创建队列要注意不能直接new Queue,因为Queue时抽象类,不能被实例化,所以创建的时候要
Queue<String> q = new LinkedList<>();
栈和队列的互相实现
- 用队列实现栈
思路
可以借用两个队列q1,q2来模拟实现栈-
入栈:
实际将元素放到底层的队列q1当中
-
出栈:
将q1中的size-1个元素先搬移到q2中;
将q1中剩下的元素删掉;
交换q1和q2里面的值(此时q1为空q2里面放了剩下的元素); -
判空:
因为将元素放在q1当中,q2只是起一个辅助出战的操作,所以判空只需:判断q1是否为空即可 -
获取栈顶元素:
和出栈类似,只是不进行元素删除操作
将q1中的size-1个元素先搬移到q2中;
获取q1中栈顶元素;
将q1中剩下的这一个元素搬到q2中;
交换q1和q2里面的值(此时q1为空q2里面放了剩下的元素);
-
class MyStack{
private Queue<Integer> q1;
private Queue<Integer> q2;
public MyStack(){
q1 = new LinkedList<>();
q2 = new LinkedList<>();
}
//入栈
public void push(int x){
q1.offer();
}
public void pop(){
//1. 将q1中的size-1个元素先搬移到q2中
while(q1.size() > 1){
q2.offer(q1.poll());
}
//2. 将q1中剩下的元素删掉
int ret = q1.poll();
//3. 交换q1和q2里面的值(此时q1为空q2里面放了剩下的元素)
Queue<Interger> temp = q1;
q1 = q2;
q2 = temp;
teturn ret;
}
public int top(){
//1. 将q1中的size-1个元素先搬移到q2中
while(q1.size() > 1){
q2.offer(q1.poll);
}
//2. 获取q1中栈顶元素
int ret = q1.peek();
//3. 将q1中剩下的这一个元素搬到q2
q2.offer(q1.poll);
//4. 交换q1和q2里面的值(此时q1为空q2里面放了剩下的元素)
Queue<Interger> temp = q1;
q1 = q2;
q2 = temp;
teturn ret;
}
public boolean empty(){
return q1.empty();
}
}
- 用栈实现队列
思路:
用两个栈实现队列实现入队列,出队列,获取队头元素,检测是否为空-
入队列:往s1中放
-
出队列:
检测s2是否为空,
如果为空:将s1中的元素搬移到s2中,删除s2栈顶元素;
如果不为空:直接删除s2栈顶元素; -
获取队头元素:和出队列类似
-
检测队列是否非空:检测两个栈都空,则队列为空
-
class MyQueue{
private Stack<Integer> s1;
private Stack<Integer> s2;
public MyQueue(){
s1 = new Stack<Integer>;
s2 = new Stack<Integer>;
}
public void push(int x){
s1.push(x);
}
public int pop(){
if(s2.empty){
while(!s1.empty){
s2.push(s.pop());
}
}
return s2.pop();
}
public int peek(){
if(s2.empty){
while(!s1.empty){
s2.push(s.pop());
}
}
return s2.peek();
}
public boolean empty{
return s1.empty() && s2.empty();
}
}