棧 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();
}
}