java數據結構與算法之棧(Stack)設計與實現

【版權申明】轉載請註明出處(請尊重原創,博主保留追究權)
http://blog.csdn.net/javazejian/article/details/53362993
出自【zejian的博客】

關聯文章:

java數據結構與算法之順序表與鏈表設計與實現分析
java數據結構與算法之雙鏈表設計與實現
java數據結構與算法之改良順序表與雙鏈表類似ArrayList和LinkedList(帶Iterator迭代器與fast-fail機制)
java數據結構與算法之棧(Stack)設計與實現
java數據結構與算法之隊列(Queue)設計與實現
java數據結構與算法之遞歸思維(讓我們更通俗地理解遞歸)
java數據結構與算法之樹基本概念及二叉樹(BinaryTree)的設計與實現
java數據結構與算法之平衡二叉查找樹(AVL樹)的設計與實現

  本篇是Java數據結構算法的第4篇,從本篇開始我們將來了解棧的設計與實現,以下是本篇的相關知識點:

棧的抽象數據類型

  棧是一種用於存儲數據的簡單數據結構,有點類似鏈表或者順序表(統稱線性表),棧與線性表的最大區別是數據的存取的操作,我們可以這樣認爲棧(Stack)是一種特殊的線性表,其插入和刪除操作只允許在線性表的一端進行,一般而言,把允許操作的一端稱爲棧頂(Top),不可操作的一端稱爲棧底(Bottom),同時把插入元素的操作稱爲入棧(Push),刪除元素的操作稱爲出棧(Pop)。若棧中沒有任何元素,則稱爲空棧,棧的結構如下圖:

  由圖我們可看成棧只能從棧頂存取元素,同時先進入的元素反而是後出,而棧頂永遠指向棧內最頂部的元素。到此可以給出棧的正式定義:棧(Stack)是一種有序特殊的線性表,只能在表的一端(稱爲棧頂,top,總是指向棧頂元素)執行插入和刪除操作,最後插入的元素將第一個被刪除,因此棧也稱爲後進先出(Last In First Out,LIFO)或先進後出(First In Last Out FILO)的線性表。棧的基本操作創建棧,判空,入棧,出棧,獲取棧頂元素等,注意棧不支持對指定位置進行刪除,插入,其接口Stack聲明如下:

package com.zejian.structures.Stack;

/**
* Created by zejian on 2016/11/27.
* Blog : http://blog.csdn.net/javazejian/article/details/53362993 [原文地址,請尊重原創]
* 棧接口抽象數據類型
*/
public interface Stack<T> {

   /**
    * 棧是否爲空
    * @return
    */
   boolean isEmpty();

   /**
    * data元素入棧
    * @param data
    */
   void push(T data);

   /**
    * 返回棧頂元素,未出棧
    * @return
    */
   T peek();

   /**
    * 出棧,返回棧頂元素,同時從棧中移除該元素
    * @return
    */
   T pop();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

順序棧的設計與實現

  順序棧,顧名思義就是採用順序表實現的的棧,順序棧的內部以順序表爲基礎,實現對元素的存取操作,當然我們還可以採用內部數組實現順序棧,在這裏我們使用內部數據組來實現棧,至於以順序表作爲基礎的棧實現,將以源碼提供。這裏先聲明一個順序棧其代碼如下,實現Stack和Serializable接口:

/**
 * Created by zejian on 2016/11/27.
 * Blog : http://blog.csdn.net/javazejian/article/details/53362993 [原文地址,請尊重原創]
 * 順序棧的實現
 */
public class SeqStack<T> implements Stack<T>,Serializable {

    private static final long serialVersionUID = -5413303117698554397L;

    /**
     * 棧頂指針,-1代表空棧
     */
    private int top=-1;

    /**
     * 容量大小默認爲10
     */
    private int capacity=10;

    /**
     * 存放元素的數組
     */
    private T[] array;

    private int size;

    public SeqStack(int capacity){
        array = (T[]) new Object[capacity];
    }

    public SeqStack(){
        array= (T[]) new Object[this.capacity];
    }
    //.......省略其他代碼
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

其獲取棧頂元素值的peek操作過程如下圖(未刪除只獲取值):

代碼如下:

/**
  * 獲取棧頂元素的值,不刪除
  * @return
  */
 @Override
 public T peek() {
     if(isEmpty())
         new EmptyStackException();
     return array[top];
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

從棧添加元素的過程如下(更新棧頂top指向):

代碼如下:

/**
 * 添加元素,從棧頂(數組尾部)插入
 * 容量不足時,需要擴容
 * @param data
 */
@Override
public void push(T data) {
    //判斷容量是否充足
    if(array.length==size)
        ensureCapacity(size*2+1);//擴容

    //從棧頂添加元素
    array[++top]=data;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

棧彈出棧頂元素的過程如下(刪除並獲取值):

代碼如下:

/**
  * 從棧頂(順序表尾部)刪除
  * @return
  */
 @Override
 public T pop() {
     if(isEmpty())
         new EmptyStackException();
     size--;
     return array[top--];
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

到此,順序棧的主要操作已實現完,是不是發現很簡單,確實如此,棧的主要操作就這樣,當然我們也可以通過前一篇介紹的MyArrayList作爲基礎來實現順序棧,這個也比較簡單,後面也會提供帶代碼,這裏就不過多囉嗦了。下面給出順序棧的整體實現代碼:

package com.zejian.structures.Stack;

import java.io.Serializable;
import java.util.EmptyStackException;

/**
 * Created by zejian on 2016/11/27.
 * Blog : http://blog.csdn.net/javazejian/article/details/53362993 [原文地址,請尊重原創]
 * 順序棧的實現
 */
public class SeqStack<T> implements Stack<T>,Serializable {

    private static final long serialVersionUID = -5413303117698554397L;

    /**
     * 棧頂指針,-1代表空棧
     */
    private int top=-1;

    /**
     * 容量大小默認爲10
     */
    private int capacity=10;

    /**
     * 存放元素的數組
     */
    private T[] array;

    private int size;

    public SeqStack(int capacity){
        array = (T[]) new Object[capacity];
    }

    public SeqStack(){
        array= (T[]) new Object[this.capacity];
    }

    public  int size(){
        return size;
    }


    @Override
    public boolean isEmpty() {
        return this.top==-1;
    }

    /**
     * 添加元素,從棧頂(數組尾部)插入
     * @param data
     */
    @Override
    public void push(T data) {
        //判斷容量是否充足
        if(array.length==size)
            ensureCapacity(size*2+1);//擴容

        //從棧頂添加元素
        array[++top]=data;

        size++;
    }

    /**
     * 獲取棧頂元素的值,不刪除
     * @return
     */
    @Override
    public T peek() {
        if(isEmpty())
            new EmptyStackException();
        return array[top];
    }

    /**
     * 從棧頂(順序表尾部)刪除
     * @return
     */
    @Override
    public T pop() {
        if(isEmpty())
            new EmptyStackException();
        size--;
        return array[top--];
    }

    /**
     * 擴容的方法
     * @param capacity
     */
    public void ensureCapacity(int capacity) {
        //如果需要拓展的容量比現在數組的容量還小,則無需擴容
        if (capacity<size)
            return;

        T[] old = array;
        array = (T[]) new Object[capacity];
        //複製元素
        for (int i=0; i<size ; i++)
            array[i]=old[i];
    }

    public static void main(String[] args){
        SeqStack<String> s=new SeqStack<>();
        s.push("A");
        s.push("B");
        s.push("C");
        System.out.println("size->"+s.size());
        int l=s.size();//size 在減少,必須先記錄
        for (int i=0;i<l;i++){
            System.out.println("s.pop->"+s.pop());
        }

        System.out.println("s.peek->"+s.peek());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118

鏈式棧的設計與實現

  瞭解完順序棧,我們接着來看看鏈式棧,所謂的鏈式棧(Linked Stack),就是採用鏈式存儲結構的棧,由於我們操作的是棧頂一端,因此這裏採用單鏈表(不帶頭結點)作爲基礎,直接實現棧的添加,獲取,刪除等主要操作即可。其操作過程如下圖:

從圖可以看出,無論是插入還是刪除直接操作的是鏈表頭部也就是棧頂元素,因此我們只需要使用不帶頭結點的單鏈表即可。代碼實現如下,比較簡單,不過多分析了:

package com.zejian.structures.Stack;

import com.zejian.structures.LinkedList.singleLinked.Node;

import java.io.Serializable;

/**
 * Created by zejian on 2016/11/27.
 * Blog : http://blog.csdn.net/javazejian/article/details/53362993 [原文地址,請尊重原創]
 * 棧的鏈式實現
 */
public class LinkedStack<T> implements Stack<T> ,Serializable{

    private static final long serialVersionUID = 1911829302658328353L;

    private Node<T> top;

    private int size;

    public LinkedStack(){
        this.top=new Node<>();
    }

    public int size(){
        return size;
    }


    @Override
    public boolean isEmpty() {
        return top==null || top.data==null;
    }

    @Override
    public void push(T data) {
        if (data==null){
            throw new StackException("data can\'t be null");
        }
        if(this.top==null){//調用pop()後top可能爲null
            this.top=new Node<>(data);
        }else if(this.top.data==null){
            this.top.data=data;
        }else {
           Node<T> p=new Node<>(data,this.top);
            top=p;//更新棧頂
        }
        size++;
    }

    @Override
    public T peek()  {
        if(isEmpty()){
            throw new EmptyStackException("Stack empty");
        }

        return top.data;
    }

    @Override
    public T pop() {
        if(isEmpty()){
            throw new EmptyStackException("Stack empty");
        }

        T data=top.data;
        top=top.next;
        size--;
        return data;
    }
    //測試
    public static void main(String[] args){
        LinkedStack<String> sl=new LinkedStack<>();
        sl.push("A");
        sl.push("B");
        sl.push("C");
        int length=sl.size();
        for (int i = 0; i < length; i++) {
            System.out.println("sl.pop->"+sl.pop());
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

最後我們來看看順序棧與鏈式棧中各個操作的算法複雜度(時間和空間)對比,順序棧複雜度如下:

操作 時間複雜度
SeqStack空間複雜度(用於N次push) O(n)
push()時間複雜度 O(1)
pop()時間複雜度 O(1)
peek()時間複雜度 O(1)
isEmpty()時間複雜度 O(1)

鏈式棧複雜度如下:

操作 時間複雜度
SeqStack空間複雜度創建(用於N次push) O(n)
push()時間複雜度 O(1)
pop()時間複雜度 O(1)
peek()時間複雜度 O(1)
isEmpty()時間複雜度 O(1)

由此可知棧的主要操作都可以在常數時間內完成,這主要是因爲棧只對一端進行操作,而且操作的只是棧頂元素。

棧的應用

棧是一種很重要的數據結構,在計算機中有着很廣泛的應用,如下一些操作都應用到了棧。

  • 符號匹配
  • 中綴表達式轉換爲後綴表達式
  • 計算後綴表達式
  • 實現函數的嵌套調用
  • HTML和XML文件中的標籤匹配
  • 網頁瀏覽器中已訪問頁面的歷史記錄

接下來我們分別對符合匹配,中綴表達式轉換爲後綴表達式進行簡單的分析,以加深我們對棧的理解。

  • 符號匹配
    在編寫程序的過程中,我們經常會遇到諸如圓括號“()”與花括號“{}”,這些符號都必須是左右匹配的,這就是我們所說的符合匹配類型,當然符合不僅需要個數相等,而且需要先左後右的依次出現,否則就不符合匹配規則,如“)(”,明顯是錯誤的匹配,而“()”纔是正確的匹配。有時候符合如括號還會嵌套出現,如“9-(5+(5+1))”,而嵌套的匹配原則是一個右括號與其前面最近的一個括號匹配,事實上編譯器幫我檢查語法錯誤是也是執行一樣的匹配原理,而這一系列操作都需要藉助棧來完成,接下來我們使用棧來實現括號”()”是否匹配的檢測。
    判斷原則如下(str=”((5-3)*8-2)”):

    • a.設置str是一個表達式字符串,從左到右依次對字符串str中的每個字符char進行語法檢測,如果char是,左括號則入棧,如果char是右括號則出棧(有一對匹配就可以去匹配一個左括號,因此可以出棧),若此時出棧的字符char爲左括號,則說明這一對括號匹配正常,如果此時棧爲空或者出棧字符不爲左括號,則表示缺少與char匹配的左括號,即目前不完整。
    • b.重複執行a操作,直到str檢測結束,如果此時棧爲空,則全部括號匹配,如果棧中還有左括號,是說明缺少右括號。

    整個檢測算法的執行流程如下圖:

    接着我們用棧作爲存儲容器通過代碼來實現這個過程,代碼比較簡單,如下:

    package com.zejian.structures.Stack;
    
    /**
    * Created by zejian on 2016/11/27.
    * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創]
    * 表達式檢測
    */
    public class CheckExpression {
    
      public static String isValid(String expstr)
      {
          //創建棧
          LinkedStack<String> stack = new LinkedStack<>();
    
          int i=0;
          while(i<expstr.length())
          {
              char ch=expstr.charAt(i);
              i++;
              switch(ch)
              {
                  case '(': stack.push(ch+"");//左括號直接入棧
                      break;
                  case ')': if (stack.isEmpty() || !stack.pop().equals("(")) //遇見右括號左括號直接出棧
                      return "(";
              }
          }
          //最後檢測是否爲空,爲空則檢測通過
          if(stack.isEmpty())
              return "check pass!";
          else
              return "check exception!";
      }
    
      public static void main(String args[])
      {
          String expstr="((5-3)*8-2)";
          System.out.println(expstr+"  "+isValid(expstr));
      }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
  • 中綴表達式轉換爲後綴表達式
    我們先來了解一下什麼是中綴表達式,平常所見到的計算表達式都算是中綴表達式,如以下的表達式:

    //1+3*(9-2)+9 --->中綴表達式(跟日常見到的表達式沒啥區別)
    • 1
    • 1

    瞭解中綴表達式後來看看其定義:將運算符寫在兩個操作數中間的表達式稱爲中綴表達式。在中綴表達式中,運算符擁有不同的優先級,同時也可以使用圓括號改變運算次序,由於這兩點的存在,使用的中綴表達式的運算規則比較複雜,求值的過程不能從左往右依次計算,當然這也是相對計算機而言罷了,畢竟我們日常生活的計算使用的還是中綴表達式。既然計算機感覺複雜,那麼我們就需要把中綴表達式轉化成計算機容易計算而且不復雜的表達式,這就是後綴表達式了,在後綴表達式中,運算符是沒有優先級的,整個計算都是遵守從左往右的次序依次計算的,如下我們將中綴表達式轉爲後綴表達式:

    //1+3*(9-2)+9        轉化前的中綴表達式
    //1 3 9 2 - * + 9 +  轉化後的後綴表達式
    • 1
    • 2
    • 1
    • 2

    中綴轉後綴的轉換過程需要用到棧,這裏我們假設棧A用於協助轉換,並使用數組B用於存放轉化後的後綴表達式具體過程如下:
    1)如果遇到操作數,我們就直接將其放入數組B中。
    2)如果遇到運算符,則我們將其放入到棧A中,遇到左括號時我們也將其放入棧A中。
    3)如果遇到一個右括號,則將棧元素彈出,將彈出的運算符輸出並存入數組B中直到遇到左括號爲止。注意,左括號只彈出並不存入數組。
    4)如果遇到任何其他的操作符,如(“+”, “*”,“(”)等,從棧中彈出元素存入數組B直到遇到發現更低優先級的元素(或者棧爲空)爲止。彈出完這些元素後,纔將遇到的操作符壓入到棧中。有一點需要注意,只有在遇到” ) “的情況下我們才彈出” ( “,其他情況我們都不會彈出” ( “。
    5)如果我們讀到了輸入的末尾,則將棧中所有元素依次彈出存入到數組B中。
    6)到此中綴表達式轉化爲後綴表達式完成,數組存儲的元素順序就代表轉化後的後綴表達式。
    執行圖示過程如下:

    簡單分析一下流程,當遇到操作數時(規則1),直接存入數組B中,當i=1(規則2)時,此時運算符爲+,直接入棧,當i=3(規則2)再遇到運算符*,由於棧內的運算符+優先級比*低,因此直接入棧,當i=4時,遇到運算符’(‘,直接入棧,當i=6時,遇運算符-,直接入棧,當i=8時(規則3),遇’)’,-和’(‘直接出棧,其中運算符-存入後綴數組B中,當i=9時(規則5),由於*優先級比+高,而+與+平級,因此和+出棧,存入數組B,而後面的+再入棧,當i=10(規則5),結束,+直接出棧存入數組B,此時數組B的元素順序即爲1 3 9 2 - * + 9 +,這就是中綴轉後綴的過程。
    接着轉成後綴後,我們來看看計算機如何利用後綴表達式進行結果運算,通過前面的分析可知,後綴表達式是沒有括號的,而且計算過程是按照從左到右依次進行的,因此在後綴表達的求值過程中,當遇到運算符時,只需要取前兩個操作數直接進行計算即可,而當遇到操作數時不能立即進行求值計算,此時必須先把操作數保存等待獲取到運算符時再進行計算,如果存在多個操作數,其運算次序是後出現的操作數先進行運算,也就是後進先運算,因此後綴表達式的計算過程我們也需要藉助棧來完成,該棧用於存放操作數,後綴表達式的計算過程及其圖解如下:

    藉助棧的程序計算過程:

    簡單分析說明一下:
    1)如果ch是數字,先將其轉換爲整數再入棧
    2)如果是運算符,將兩個操作數出棧,計算結果再入棧
    3)重複1)和2)直到後綴表達式結束,最終棧內的元素即爲計算的結果。
    整體整體呈現實現如下:

    package com.zejian.structures.Stack;
    
    /**
    * Created by zejian on 2016/11/28.
    * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創]
    * 中綴轉後綴,然後計算後綴表達式的值
    */
    public class CalculateExpression {
    
      /**
       * 中綴轉後綴
       * @param expstr 中綴表達式字符串
       * @return
       */
      public static String toPostfix(String expstr)
      {
          //創建棧,用於存儲運算符
          SeqStack<String> stack = new SeqStack<>(expstr.length());
    
          String postfix="";//存儲後綴表達式的字符串
          int i=0;
          while (i<expstr.length())
          {
              char ch=expstr.charAt(i);
              switch (ch)
              {
                  case '+':
                  case '-':
                      //當棧不爲空或者棧頂元素不是左括號時,直接出棧,因此此時只有可能是*/+-四種運算符(根據規則4),否則入棧
                      while (!stack.isEmpty() && !stack.peek().equals("(")) {
                          postfix += stack.pop();
                      }
                      //入棧
                      stack.push(ch+"");
                      i++;
                      break;
                  case '*':
                  case '/':
                      //遇到運算符*/
                      while (!stack.isEmpty() && (stack.peek().equals("*") || stack.peek().equals("/"))) {
                          postfix += stack.pop();
                      }
                      stack.push(ch+"");
                      i++;
                      break;
                  case '(':
                      //左括號直接入棧
                      stack.push(ch+"");
                      i++;
                      break;
                  case ')':
                      //遇到右括號(規則3)
                      String out = stack.pop();
                      while (out!=null && !out.equals("("))
                      {
                          postfix += out;
                          out = stack.pop();
                      }
                      i++;
                      break;
                  default:
                      //操作數直接入棧
                      while (ch>='0' && ch<='9')
                      {
                          postfix += ch;
                          i++;
                          if (i<expstr.length())
                              ch=expstr.charAt(i);
                          else
                              ch='=';
                      }
                      //分隔符
                      postfix += " ";
                      break;
              }
          }
          //最後把所有運算符出棧(規則5)
          while (!stack.isEmpty())
              postfix += stack.pop();
          return postfix;
      }
    
      /**
       * 計算後綴表達式的值
       * @param postfix 傳入後綴表達式
       * @return
       */
      public static int calculatePostfixValue(String postfix)
      {
          //棧用於存儲操作數,協助運算
          LinkedStack<Integer> stack = new LinkedStack<>();
          int i=0, result=0;
          while (i<postfix.length())
          {
              char ch=postfix.charAt(i);
              if (ch>='0' && ch<='9')
              {
                  result=0;
                  while (ch!=' ')
                  {
                      //將整數字符轉爲整數值ch=90
                      result = result*10 + Integer.parseInt(ch+"");
                      i++;
                      ch = postfix.charAt(i);
                  }
                  i++;
                  stack.push(result);//操作數入棧
              }
              else
              {  //ch 是運算符,出棧棧頂的前兩個元素
                  int y= stack.pop();
                  int x= stack.pop();
                  switch (ch)
                  {   //根據情況進行計算
                      case '+': result=x+y; break;
                      case '-': result=x-y; break;
                      case '*': result=x*y; break;
                      case '/': result=x/y; break;   //注意這裏並沒去判斷除數是否爲0的情況
                  }
                  //將運算結果入棧
                  stack.push(result);
                  i++;
              }
          }
          //將最後的結果出棧並返回
          return stack.pop();
      }
      //測試
      public static void main(String args[])
      {
          String expstr="1+3*(9-2)+90";
          String postfix = toPostfix(expstr);
          System.out.println("中綴表達式->expstr=  "+expstr);
          System.out.println("後綴表達式->postfix= "+postfix);
          System.out.println("計算結果->value= "+calculatePostfixValue(postfix));
      }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138

    以上便是利用轉實現中綴與後綴的轉換過程並且通過後綴計算機能及其簡單計算出後綴表達式的結果。ok~,到此我們對棧的分析就結束了,本來還想聊聊函數調用的問題,但感覺這個問題放在遞歸算法更恰當,嗯,源碼地址如下:
    github源碼下載,歡迎star(含文章列表,持續更新)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章