資料結構學習—— 棧(stack)
- 什麼是棧
- 無處不在的棧——棧的應用
- 使用陣列實現ArrayStack及時間複雜度分析
- Leetcode20題:有效的括號
什麼是棧(stack)
棧(stack)是一種運算受限的線性資料結構,運算受限指的是棧這種資料結構僅允許在一端新增元素,刪除元素,這一端被稱作棧頂,相對應的另一端則稱為棧底。如圖所示:
當前棧,如果想要新增元素“D”只能從棧頂部新增,從棧中取出元素則還是從棧頂開始取元素,所以棧是一種後進先出的資料結構即:LIFO(Last In First Out)。
無處不在的棧——棧的應用
一:Undo(撤銷操作)
當我們在文件編輯器中輸入文字,當發現輸入錯誤時,想要撤銷到前一步,這個操作就是Undo。撤銷的原理實際上就是棧這種資料結構來設計實現的。例如:李雷在某個文件編輯器上輸入文字“我愛韓梅梅”,結果,由於李雷滿腦子想的都是韓梅梅的音容笑貌,不小心將內容輸入成了“我愛含梅梅”。李雷想將內容恢復到“我愛” 這一步,所以他按了三次“Ctrl+z”,然後又依次將“韓”,“梅”,“梅”三個字輸入了進去。
Undo(撤銷)看似很高階的操作,背後的原理就是棧。
二:C語言printf()函式
來看一個C語言的問題:
#include<stdio.h>
int main(void){
int i=1;
printf("%d%d%d",i,i++,i++);
return 0;
}
複製程式碼
這個程式的執行結果是什麼?如果只是知道i++與++i的區別是不足以解決這道問題的。先公佈答案,這個程式的執行結果為:321,這與printf的底層原理有關,因為printf的底層實現就是棧。還是拿李雷韓梅梅來舉例說明。
printf("我愛韓梅梅");
複製程式碼
printf函式首先會將字串內容從右至左 push到棧中。
然後,再將棧裡面的元素依次pop出來,這樣我們就能看到"我愛韓梅梅"這個字串被列印出來了。這道題也是一樣,首先將最右邊的%d即“i++” push到棧中,i==1,1入棧後,執行++操作,i的值變成了2。按照上述思路依次將所有元素推入棧中,棧的情況為:
將所有的元素出棧,出棧的順序就是我們看到的列印結果即:321。
三:程式呼叫系統棧
有如下程式:
A();
function A(){
1 ...
2 B();
3 ...
4 end
}
function B(){
1 ...
2 C();
3 ...
4 end
}
function C(){
1 ...
2 ...
3 ...
4 end
}
複製程式碼
程式從A方法開始呼叫,執行到 A方法的第二行,計算機發現需要執行B方法,這時就會將執行到哪一步這樣一個資訊壓入到系統棧中。例如,定義A2為A方法的第二行,計算機此時將A2壓入系統棧,表明執行到了A方法的第二行。
計算機在系統棧壓入這樣一個資訊後,開始執行B方法,執行到B方法的第二行,發現需要執行C方法,於是計算機將B2壓入系統棧中。
計算機開始執行C方法,C方法中沒有呼叫其他的函式,執行結束後,計算機發現系統棧中有殘留的任務,於是pop stack 發現需要回去執行完B方法,且執行到了B方法的第二行。B方法執行完畢後,計算機又去看了看系統棧,發現仍有殘留的任務需要執行,於是乎又 pop stack 發現原來A方法還沒有執行完畢,且執行到了第二行,所以計算機又將A方法執行完畢。這時系統棧為空,計算機終於鬆了一口氣,知道所有的任務已經執行完畢了~
使用陣列實現ArrayStack及時間複雜度分析
本文中ArrayStack的底層實現陣列為動態陣列:動態陣列,DobbyKim's Blog。
public interface Stack<E> {
void push(E e);
E pop();
E peek();
int getSize();
boolean isEmpty();
}
public class ArrayStack<E> implements Stack<E>{
Array<E> array;
public ArrayStack(int capacity){
array = new Array<>(capacity);
}
public ArrayStack(){
array = new Array<>();
}
public void push(E e){...}
public E pop(){...}
public int getSize(){...}
public int getCapacity(){...}
public boolean isEmpty(){...}
public E peek(){...}
public String toString(){...}
}
複製程式碼
點選檢視原始碼
ArrayStack的方法push 與 pop 的均攤時間複雜度為O(1),因為這裡面涉及到底層實現Array為動態陣列,resize()擴容操作為一個O(n)的演算法。getSize()方法,peek()方法,isEmpty()方法的時間複雜度均為O(1)。
Leetcode20題:有效的括號
給定一個只包括 '(',')','{','}','[',']' 的字串,判斷字串是否有效。
有效字串需滿足:
左括號必須用相同型別的右括號閉合。
左括號必須以正確的順序閉合。
注意空字串可被認為是有效字串。
- 示例 1:
輸入: "()"
輸出: true
複製程式碼
- 示例 2:
輸入: "()[]{}"
輸出: true
複製程式碼
- 示例 3:
輸入: "(]"
輸出: false
複製程式碼
- 示例 4:
輸入: "([)]"
輸出: false
複製程式碼
- 示例 5:
輸入: "{[]}"
輸出: true
複製程式碼
問題解決思路:棧。只要是左側的括號為'(','[','{'就push到棧中,遇到與之匹配的右側括號則pop,最後棧如果為空則說明匹配成功。Java程式碼如下:
import java.util.Stack
class Solution {
public boolean isValid(String s) {
Stack<Character>stack = new Stack<>();
for(int i=0;i<s.length();i++){
if(s.charAt(i)=='(' || s.charAt(i)=='[' || s.charAt(i)=='{'){
stack.push(s.charAt(i));
}else{
if(stack.isEmpty())
return false;
char c = stack.pop;
if(s.charAt(i)==')' && c!='(')
return false;
if(s.charAt(i)==']' && c!='[')
return false;
if(s.charAt(i)=='}' && c!='{')
return false;
}
}
return stack.isEmpty();
}
}
複製程式碼