棧這種數據結構,不就後進先出?

什麼是棧

棧在咱們平常編碼中遇到的很是多,不少人對棧的接觸可能僅僅侷限在 遞歸使用的是棧StackOverflowException,棧是一種後進先出的數據結構(能夠想象生化金字塔的牢房和生化角鬥場的狗洞)。java

棧是這麼定義的:node

棧(stack)又名堆棧,它是一種運算受限的線性表。限定僅在表尾進行插入和刪除操做的線性表。這一端被稱爲棧頂,相對地,把另外一端稱爲棧底。向一個棧插入新元素又稱做進棧、入棧或壓棧,它是把新元素放到棧頂元素的上面,使之成爲新的棧頂元素;從一個棧刪除元素又稱做出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成爲新的棧頂元素。算法

稍微介紹一下關鍵名詞:數組

運算受限:也就是這個表你不能隨便的刪除插入。只能按照它的規則進行插入刪除。好比棧就只能在一端進行插入和刪除。一樣,隊列也是運算受限,只能在兩頭操做。數據結構

線性表:棧也是一種線性表,前面詳細介紹過線性表,它表達的是一種數據的邏輯關係。也就是在棧內各個元素是相鄰的。固然在具體實現上也分數組和鏈表實現,他們的物理存儲結構不一樣。可是邏輯結構(實現的目的)相同。函數

棧頂棧底: 這個描述是偏向於邏輯上的內容,由於你們知道數組在末尾插入刪除更容易,而單鏈表一般在頭插入刪除更容易。因此數組能夠用末尾作棧頂,而鏈表能夠頭作棧頂。學習

image-20210421182034079

棧的應用: 棧的應用普遍,好比你的程序執行查看調用堆棧、計算機四則加減運算、算法的非遞歸形式、括號匹配問題等等。因此棧也是必須掌握的一門數據結構。最簡單你們都經歷過,你拿一本書上下疊在一塊兒,就是一個後進先出的過程,你能夠把它當作一個棧。下面咱們介紹數組實現的棧和鏈表實現的棧。優化

數組實現

數組實現的棧用的比較多,咱們常常刷題也會用數組去實現一個簡單的棧去解決簡單的問題。this

結構設計編碼

對於數組來講,咱們模擬棧的過程很簡單,由於棧是後進先出,咱們很容易在數組的末尾進行插入和刪除。因此咱們選定末尾爲棧頂。因此對於一個棧所須要的基礎元素是 一個data[]數組和一個top(int)表示棧頂位置。

那麼初始化函數代碼爲:

private T data[];
private int top;
public seqStack() {
    data=(T[]) new Object[10];
    top=-1;
}
public seqStack(int maxsize)
{
    data=(T[]) new Object[maxsize];
    top=-1;
}

push插入

棧的核心操做之一push():入棧操做。

  • 若是top<數組長度-1。入棧,top++;a[top]=value;
  • 若是top==數組長度-1;棧滿。

image-20210421170312904

pop彈出並返回首位

  • 若是top>=0,棧不爲空,能夠彈出。return data[top--];
  • 以下圖,原本棧爲1,2,3,4,5,6(棧頂),執行pop操做,top變爲3的位置而且返回4;

image-20210421170904604

其餘操做

例如peek操做時返回棧頂不彈出.因此只需知足要求時候return data[top]便可。

數組實現:

package 隊棧;

public class seqStack<T> {
    
    private T data[];
    private int top;
    public seqStack() {
        data=(T[]) new Object[10];
        top=-1;
    }
    public seqStack(int maxsize)
    {
        data=(T[]) new Object[maxsize];
        top=-1;
    }
    boolean isEmpty()
    {
        return top==-1;
    }
    int length()
    {
        return top+1;
    }
    
    boolean push(T value) throws Exception//壓入棧
    {
        if(top+1>data.length-1)
        {
            throw new Exception("棧已滿");
        }
        else {
            data[++top]=value;
            return true;
        }
    }
    T peek() throws Exception//返回棧頂元素不移除
    {
        if(!isEmpty())
        {
            return data[top];
        }
        else {
            throw new Exception("棧爲空");
        }
    }
    T pop() throws Exception
    {
        if(isEmpty())
        {
            throw new Exception("棧爲空");
        }
        else {
           return data[top--];
        }
    }
    public String toString()
    {
        if(top==-1)
        {
            return "";
        }
        else {
            String va="";
            for(int i=top;i>=0;i--)
            {
                va+=data[i]+"  ";
            }
            return va;
        }
    }
}

鏈表實現

有數組實現,鏈表固然也能實現。對於棧的設計,大體能夠分爲兩種思路:

  • 像數組那樣在尾部插入刪除。你們都知道鏈表效率低在查詢,而查詢到尾部效率很低,就算用了尾指針,能夠解決尾部插入效率,可是依然沒法解決刪除效率(刪除須要找到前驅節點),還須要雙向鏈表。前面雖然詳細介紹過雙向鏈表,可是這樣未免太複雜
  • 因此咱們採用帶頭節點的單鏈表在頭部插入刪除,把頭當成棧頂,插入直接在頭節點後插入,刪除也直接刪除頭節點後第一個節點便可,這樣就能夠完美的知足棧的需求。

結構設計

設計上和鏈表很類似,長話短說,短話不說,直接上代碼就懂。
鏈表的節點

static class node<T>
{
    T data;
    node next;
    public node() {    
    }
    public node(T value)
    {
        this.data=value;
    }
}

基本結構:

public class lisStack <T>{
    int length;
    node<T> head;//頭節點
    public lisStack() {
        head=new node<>();
        length=0;
    }
    //其餘方法
}

push插入

與單鏈表頭插入一致,若是不太瞭解能夠看看前面寫的線性表有具體講解過程。

和數組造成的棧有個區別,鏈式實現的棧理論上棧沒有大小限制(不突破內存系統限制),不須要考慮是否越界,而數組則須要考慮容量問題。

若是一個節點team入棧:

  • 空鏈表入棧head.next=team;
  • 非空入棧team.next=head.next;head.next=team;

image-20210421171338480

pop彈出

與單鏈表頭刪除一致,若是不太瞭解請先看筆者隊線性表介紹的。

和數組一樣須要判斷棧是否爲空,若是節點team出棧:head指向team後驅節點。

image-20210421171722989

其餘操做

其餘例如peek操做時返回棧頂不彈出.因此只需判空知足題意時候return head.next.data便可。而length你能夠遍歷鏈表返回長度,也能夠動態設置(本文采起)跟隨棧長變化。

鏈表實現:

package 隊棧;

public class lisStack <T>{
    static class node<T>
    {
        T data;
        node next;
        public node() {    
        }
        public node(T value)
        {
            this.data=value;
        }
    }
    int length;
    node<T> head;//頭節點
    public lisStack() {
        head=new node<>();
        length=0;
    }
    boolean isEmpty()
    {
        return head.next==null;
    }
    int length()
    {
        return length;
    }
    public void push(T value) {//近棧
       node<T> team=new node<T>(value);
       if(length==0)
       {
           head.next=team;
       }
       else {
        team.next=head.next;
        head.next=team;}
       length++;
    }
    public T peek() throws Exception {
        if(length==0) {throw new Exception("鏈表爲空");}
        else {//刪除
            return (T) head.next.data;
        }
  }
    public T pop() throws Exception {//出棧
      if(length==0) {throw new Exception("鏈表爲空");}
      else {//刪除
        T value=(T) head.next.data;
              head.next=head.next.next;//va.next
              length--;
              return value;
            }
    }
    public String toString(){
        if(length==0) {return "";}
        else {
              String va="";
            node team=head.next;
            while(team!=null)
            {
                va+=team.data+" ";
                team=team.next;
            }
            return va;
         }    
    }
}

棧能這麼玩

既然上面詳細講解設計棧,這裏來兩道棧很是經典很是經典的例題(很是高頻,很容易忘,又很重要,普通問題就不放的)

力扣20有效的括號:

題意:給定一個只包括 '(',')','{','}','[',']' 的字符串,判斷字符串是否有效。

有效字符串需知足:

左括號必須用相同類型的右括號閉合。
左括號必須以正確的順序閉合。
注意空字符串可被認爲是有效字符串。

示例 :

輸入: "()[]{}"
輸出: true

示例 :

輸入: "([)]"
輸出: false

分析:
括號類的問題是經典棧類問題,確定要想到用棧處理。判斷一個字符串滿不知足一個有效的字符串,就要看它是否是都能組成對。

從單個括號對來講,((,))都是不知足的,只有()纔可知足,即一左一右。

從多個括號對來講 {[(字符串還可接受任意無限([,{的括號。可是若是向左的括號只能先接收)括號(變成{[)。

從上面能夠看做一種相消除的思想。例如(({[()()]}))字符串遍歷時候能夠這樣處理:

  • (({[(下一個)消掉成(({[
  • (({[(下一個)消掉成(({[
  • (({[下一個]消掉成(({
  • (({下一個}消掉成((
  • ((下一個)消掉成(
  • (下一個)消掉成 這樣就知足題意

每次操做的時候都判斷剩餘有效括號最頂部那個括號是否可以和遍歷的相消除,這個過程利用棧判斷當前是加入棧仍是消除頂部,到最後若是棧爲空說明知足,不然不知足,固然具體括號要對應,具體實現代碼爲:

public boolean isValid(String s) {
     Stack<Character>stack=new Stack<Character>();
     for(int i=0;i<s.length();i++)
     {    
         char te=s.charAt(i);
         if(te==']')
         {
             if(!stack.isEmpty()&&stack.pop()=='[')
                 continue;
             else {
                return false;
            }
         }
         else if(te=='}')
         {
             if(!stack.isEmpty()&&stack.pop()=='{')
                 continue;
             else {
                return false;
            }
         }
         else if(te==')')
         {
             if(!stack.isEmpty()&&stack.pop()=='(')
                 continue;
             else {
                return false;
             }
         }
         else
             stack.push(te);
     }
     return stack.isEmpty(); 
 }

固然,JDK自帶的棧用起來不快,能夠用數組優化:

public boolean isValid(String s) {
    char a[]=new char[s.length()];
    int index=-1;
     for(int i=0;i<s.length();i++)
     {    
         char te=s.charAt(i);
         if(te==']')
         {
             if(index>=0&&a[index]=='[')
                 index--;
             else {
                return false;
            }
         }
         else if(te=='}')
         {
             if(index>=0&&a[index]=='{')
                 index--;
             else {
                return false;
            }
         }
         else if(te==')')
         {
             if(index>=0&&a[index]=='(')
                 index--;
             else {
                return false;
             }
         }
         else
             a[++index]=te;
     }
     return index==-1; 
 }

力扣32最長有效括號(困難)

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

示例 :

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

示例 :

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

方案一暴力

這種題核心思想就是使用棧模擬。本題的話更簡單一點由於只有()兩種括號,使用暴力的時候就能夠循環每次找到最長的有效括號。而括號匹配的時候能夠直接終止的狀況是)右括號多出沒法匹配。

例如())(到第三個不可能和前面相連。若是來(只須要期待後面可以來),一個)能夠和一個(組成一對,消除棧中的一個(

固然,在具體的實現上,咱們用數組模擬棧,實現代碼爲:

public  int longestValidParentheses(String s) {
    char str[]=s.toCharArray();//字符數組
    int max=0;
    for(int i=0;i<str.length-1;i++)
    {
        int index=-1;
        if(max>=str.length-i)
            break;
        for(int j=i;j<str.length;j++)
        {
            if(str[j]=='(')
                index++;
            else {
                if(index<0)
                {
                    i=j;
                    break;
                }
                else {
                    index--;
                }
            }
            if(index==-1&&(j-i+1>max))
            {
                max=j-i+1;
            }
        }
    }    
    return max;
}

這個複雜度過高,咱們看看如何用棧優化。

方案二棧優化

如何將這道題從一個O(n2)的時間複雜度優化到O(n)?很容易, 咱們須要注意他的過程。咱們先隨便看幾個可能的最大狀況。

  • ( ) ) ( ) ( ( ) ( ) ) 最大爲後面部分(空格分開)
  • ( ) ( ) ( ( ( ) 最大爲前面部分
  • ( ( ( ( ( ( ) ( ) ( ) ( ) 最大爲後面部分

對於這麼一次獲取你會發現不一樣括號會有些區別:
(:左括號一旦出現那麼他就期待一個)進行匹配,但它的後面可能有)而且在這中間有不少其餘括號對。
):右擴號有兩種狀況:

  • 一種是當前已經超過左括號前面已經不可能連續了。例如( ) ) ( )第三個括號出現已經使得整個串串不可能連續,最大要麼在其左面要麼再其右面。 你能夠理解其爲一種清零初始機制。
  • 另外一種狀況)就是目標棧中存在(可與其進行匹配。匹配以後要疊加到消除後平級的數量上,而且判斷是不是最大值。(下面會解釋)

具體實現的思路上,就是使用一個int數組標記當前層級(棧深)有正確的括號數量。 模擬一次棧行爲從左向右,遇到)太多(當前棧中不存在(進行匹配)就將數據清零從新開始。這樣一直到最後。你能夠把它當作臺接,遇到(就上一個臺階並清零該新臺階,遇到)就下一個臺階而且把數量加到降低後的臺階上。具體能夠看下面圖片模擬的過程:
( ) ( ( ) ( ) ( ( ) ) )

在這裏插入圖片描述

仔細看看這張圖,具體實現代碼爲:

public static int longestValidParentheses(String s) {
        int max=0;    
        int value[]=new int[s.length()+1];
        int index=0;
        for(int i=0;i<s.length();i++)
        {
            if(s.charAt(i)=='(')
            {
                index++;
                value[index]=0;
            }
            else {//")"
                if(index==0)
                {
                    value[0]=0;
                }
                else {
                    value[index-1]+=value[index--]+2;//疊加
                    if(value[index]>max)//更新
                        max=value[index];
                }
            }
        }
        return max;
 }

用棧也能夠實現,可是效率比數組略低:

public int longestValidParentheses(String s) {
  int maxans = 0;
  Stack<Integer> stack = new Stack<>();
  stack.push(-1);
  for (int i = 0; i < s.length(); i++) {
    if (s.charAt(i) == '(') {//(將當前的 
      stack.push(i);
    } else {
      stack.pop();
      if (stack.empty()) {
        stack.push(i);
      } else {//i-stack.peek就是i是出現的總個數 peek是還沒匹配的個數
        maxans = Math.max(maxans, i - stack.peek());
      }
    }
  }
  return maxans;
}

總結

到這裏,本文對棧的介紹就結束了,相信你能夠手寫個棧而且能夠小試牛刀解決括號匹配問題!固然棧能解決的問題還有不少好比接雨水問題、二叉樹非遞歸遍歷等等,有些重要的還會再總結。

歡迎關注個人公衆號:bigsai第一手學習知識,最後不要吝嗇你的一鍵三連,原創求支持,謝謝

相關文章
相關標籤/搜索