我所知道的數據結構之棧

做者前言

你們好,我是阿濠,今篇內容跟你們分享的是數據結構之棧,很高興分享到segmentfault與你們一塊兒學習交流,初次見面請你們多多關照,一塊兒學習進步.

1、什麼是棧

官方定義:棧(stack)是一個後進先出的線性表,它要求只在表尾作進行刪除和插入操做。

棧:後進者先出,先進者後出,這就是典型的棧結構。java

舉個例子:就像疊盤子同樣,後來放的盤子老是在上面,拿的時候也是從上面拿,也就是先拿後來放上面的盤子,最後拿最先放的盤子。segmentfault

棧是一種重要的線性結構,棧是線性表的一種形式,在生活中,咱們的瀏覽器,每點擊一次「後退」都是返回最近的一次瀏覽頁面,至關於就是後進先出數組

棧有一些特殊限制:
1.棧元素必須後進先出
2.棧的操做只能在這個線性表的表尾進行
3.對於棧來講,表尾則是棧頂(Top),表頭則是棧底(bottom)瀏覽器

棧的插入和刪除操做

由於棧的本質是線性表,線性表有兩種存儲方式,因此棧也分兩種存儲方式分別爲棧的順序存儲結構、棧的鏈式存儲結構數據結構

棧的插入操做(Push)叫作進棧,也稱爲入棧,對於順序棧的進棧操做只需將新的數據元素存入棧內,而後讓記錄棧內元素個數的變量加1,程序便可再次經過arr[size-1]從新訪問新的棧頂元素。進棧操做示意圖以下:學習

因爲順序棧底層一般會採用數組來保存數據元素,所以可能出現的狀況是:當程序試圖讓一個數據元素進棧時,底層數據已滿,那麼就必須擴充底層數組的長度來容納新進棧的數據元素。spa

棧的刪除操做(Pop)叫作出棧,也稱爲彈棧,對於順序棧的出棧操做而言,須要將棧頂元素彈出棧,程序要作兩件事。3d

  • 讓記錄棧內元素個數的變量減1.
  • 釋放數組對棧頂元素的引用。

對於刪除操做來講,只要讓記錄棧內元素個數的size減1,程序便可經過arr[size-1]訪問到新的棧頂元素。但不要忘記釋放原來棧頂的數組引用,不然會引發內存泄漏。code

棧比普通線性表的功能更弱,棧是一種被限制過的線性表,只能從棧頂插入,刪除數據元素。blog

棧的鏈式存儲結構及實現

能夠採用單鏈表來保存棧中全部元素,這種鏈式結構的棧也被稱爲棧鏈。對於棧鏈而言,棧頂元素不斷地改變,程序只要使用一個top引用來記錄當前的棧頂元素便可。

鏈式的進棧操做,只須要作幾件件事:

  • 讓top引用指向新元素添加的元素
  • 新元素的next引用指向原來的棧頂元素。
  • 讓記錄棧內元素個數的size變量加1.

鏈式的出棧操做,只須要作幾件件事:

  • 釋放原來的棧頂元素
  • 讓top引用指向原棧頂元素的下一個元素
  • 讓記錄棧內元素個數的size變量減1.

從空間利用率的角度說,鏈棧的空間利用率比順序棧的空間利用率要高一些。

2、認識棧

java中有封裝好的類,能夠直接調用

  • push(element): 添加一個新元素到棧頂位置.
  • pop():移除棧頂的元素,同時返回被移除的元素。
  • peek():返回棧頂的元素,不對棧作任何修改(僅僅返回棧頂的元素)。
  • isEmpty():若是棧裏沒有任何元素就返回true,不然返回false
  • clear():移除棧裏的全部元素。
  • size():返回棧裏的元素個數。這個方法和數組的length屬性很相似。

咱們能夠作一個二進制轉換十進制的方式去看看棧的後進先出,從數學的角度來講二進制轉換爲十進制是最低位起去乘以N位的2^(n-1),而後所有加起來

簡單的來講示例二進制:1000 轉換成十進制爲8 why?

代入公式:0*2^(1-1)+0*2^(2-1)+0*2^(3-1)+1*2^(4-1)=0+0+0+8

假設咱們輸入的數字是11001001這樣的二進制數,那麼放入棧就是
圖片.png

若是就會發現top棧頂就是最低位,依次算完發現棧底就是最高位

經典使用場景 - 表達式求值

經過兩個棧實現的這個功能的
一個棧保存操做數,另外一個棧保存運算符,咱們從左到右遍歷表達式
1.當遇到數字時,將其壓入操做數棧;
2.當遇到運算符時,就與運算符棧的棧頂元素進行比較。

若是比運算符棧頂元素的優先級高,就將當前運算符壓入棧;
若是比運算符棧的棧頂元素的優先級低或相同,那麼就從操做數棧的棧頂取2個操做數,而後進行計算,再把計算完成的結果壓入操做數棧,繼續比較。

下面咱們舉個例子:3+5*8-6這個表達式

經典使用場景 - 現瀏覽器的前進、後退功能

  • 使用兩個棧X和Y,首次瀏覽的界面入X棧。
  • 後退時,依次從X中出棧,並將出棧的數據依次放入Y棧
  • 前進時,從Y棧中出棧,在依次入X棧。
  • 當X棧中沒有數據時,說明不能後退了;
  • 當Y棧中沒有數據時,說明不能前進了。

好比你順序查看了 a,b,c 三個頁面,咱們就依次把 a,b,c 壓入棧,這個時候,兩個棧的數據就是這個樣子:

當你經過瀏覽器的後退按鈕,從頁面 c 後退到頁面 a 以後,咱們就依次把 c 和 b 從棧 X 中彈出,而且依次放入到棧 Y。這個時候,兩個棧的數據就是這個樣子:

這個時候你又想看頁面 b,因而你又點擊前進按鈕回到 b 頁面,咱們就把 b 再從棧 Y 中出棧,放入棧 X 中。此時兩個棧的數據是這個樣子:

這個時候,你經過頁面 b 又跳轉到新的頁面 d 了,頁面 c 就沒法再經過前進、後退按鈕重複查看了,因此須要清空棧 Y。此時兩個棧的數據這個樣子:

3、代碼實戰

最長有效括號
題目描述: 給定一個只包含 '('')' 的字符串,找出最長的包含有效括號的子串的長度。

示例 1:
輸入: "(()"
輸出: 2
解釋: 最長有效括號子串爲 "()"

示例 2:
輸入: ")()())"
輸出: 4
解釋: 最長有效括號子串爲 "()()"

//    解法一:棧+暴力            
public boolean isValid(String s) { // 判斷任何一個子字符串s是否有效
    Stack<Character> stack = new Stack<Character>();
    for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) == '(') {
            stack.push('(');
        } else if (!stack.empty() && stack.peek() == '(') {
            stack.pop();
        } else {
            return false;
        }
    }
    return stack.empty();
}
//* 時間複雜度  O(n^2) 空間複雜度  O(n) *
public int longestValidBracketLengthOne(String s) {
    int maxLength = 0;
    for (int i = 0; i < s.length(); i++) {
        for (int j = i + 2; j <= s.length(); j+=2) {
            if (isValid(s.substring(i, j))) {
                maxLength = Math.max(maxLength, j - i);
            }
        }
    }
    return maxLength;
}

假設咱們按照思路輸入")())" 初始的時候maxLength=0
1.i=0時,j=2,進入isValid方法的s=")(",這時的stack=0,方法裏的for循環i=0時,無符合要求,直接return false結束j+=2
2.j=4,進入isValid方法的s=")())"這時的stack=0,方法裏的for循環i=0時,無符合要求,直接return false結束i++
圖片.png

3.i=1,j=3,進入isValid方法的s="()",方法裏的for循環i=0時,符合第一個if則push加入棧,stack=1,i++判斷i=1時,不符合第一個if,符合第二個if,由於剛剛加入棧的棧頂='(',這時作了出棧的操做,stack=0,結束方法此時maxLength=3-1=2 結束i++
圖片.png

4.i=2,j=2,進入isValid方法的s="))",不符合for循環,j=4,j=4,進入isValid方法的s=")())",也不符合..

圖片.png

........

此方法一外面for循環作一次,裏面for也要作一次,即作n*n次,因此時間複雜度 O(n^2)

//    解法二:棧                    
public int longestValidBracketLengthTwo(String s) {
    if (s == null || s.length() == 0) {
        return 0;
    }
    int maxLength = 0; // 設定返回值 即最長有效括號的長度
    int n = s.length(); // 保存須要循環的次數 也就是字符串的最初長度
    char[] sArr = s.toCharArray(); // 字符串生成相應的字符數組

    Stack<Integer> stack = new Stack<Integer>(); // 建立系統的棧類 存放字符數組中的下標 
    stack.push(-1); // -1 入棧用於處理邊界條件

    for (int i = 0; i < n; i++) {
        // 循環第i項對應的字符是 ')'
        // 且 stack.size() > 1 表示棧不爲空
        // 且 必須保證棧頂元素(下標 )對應的字符是 '('
        if (sArr[i] == ')' && stack.size() > 1 && sArr[stack.peek()] == '(') {
            stack.pop(); // 對應字符'(' 的棧頂元素(下標 ) 出棧
            // 記錄最長長度 由於i是線性往前增長  而棧頂元素(下標 )是線性增減
            maxLength = Math.max(maxLength, i - stack.peek()); 
        } else { // 其餘狀況,直接將當前位置入棧
            stack.push(i);
        }
     }
    return maxLength;
}

假設咱們按照思路輸入")())" 初始的時候maxLength=0

我定義n=輸入的字符串長度也是循環的次數,並生成相對應的char[]字符數組,與系統棧,
給棧定義邊界爲-1,避免對應不上char字符數組

圖片.png

並根據char數組肯定循環次數,從而先找到')'的下標再判斷棧的棧頂是否'(',若是對應的下標不符合要求則將下標加入棧

圖片.png

咱們找到下標0是')',可是目前的棧頂的值對應數組不是'(',因此將下標0放入棧裏,對應的')'稱爲棧頂。

圖片.png

下標1對應的數組是'('不是咱們要找的值

圖片.png

下標2對應的數組是')',符合咱們的要求,目前的棧頂是1,對應的數組是'('符合要求,咱們將目前的棧頂彈出,此時站的個數由3-1=2,且棧頂對應的值就是0,maxLength=2-0

圖片.png

......

由於只須要將字符串變成char數組,而且將數組裏的char判斷符合邏輯問題,因此是 時間複雜度 O(n) 空間複雜度 O(n)

相關文章
相關標籤/搜索