棧ADT實現及其應用

棧模型##

棧(Stack)是限制插入和刪除只能在一個位子上進行的表,該位子是表的末端,叫做棧的頂(top)。對棧的基本操作有進棧(push)和出棧(pop),相當于表插入和刪除最后一個元素。

棧的實現##

由于棧可以看作是一個表,因此任何實現表的方法都能實現棧。顯然,在Java集合中,ArrayList和LinkedList都支持棧的操作,由于棧的操作都是棧頂元素,所以對于數組實現的ArrayList和鏈表實現的LinkedList來說都花費常數時間,因此他們并沒有太大區別。但是對于代碼實現來說,數組實現更加簡潔,更易于理解,下面將給出棧的簡單實現。

public class Stack<E> {

    //棧的默認大小為10
    private static final int DEFAULT_VALUE_SIZE = 10;
    //用一個數組來存儲棧的元素
    private E[] elementData;
    //棧中元素的實際個數
    private int size;

    //構造器,調用doClear方法初始化
    public Stack() {
        doClear();
    }

    //進棧操作
    public E push(E element) {
        //存放棧元素的數組滿了,則需要對elementData擴容
        if (elementData.length == size) {
            //擴容操作
            ensureCapacity(size + 1);
        }
        elementData[size++] = element;
        return element;
    }

    //出棧操作,若棧為空時出棧,則拋出數組越界異常
    public E pop() {
        if (size == 0) {
            throw new IndexOutOfBoundsException();
        }
        return elementData[--size];
    }

    //清空棧的操作
    public void clear() {
        doClear();
    }

    //清空棧的操作
    private void doClear() {
        size = 0;
        ensureCapacity(DEFAULT_VALUE_SIZE);
    }

    //保證在進棧的,存儲棧元素的elementData數組有足夠的空間
    private void ensureCapacity(int minCapacity) {
        if (size > minCapacity) {
            return;
        }

        E[] old = elementData;
        elementData = (E[]) new Object[minCapacity];

        for (int i = 0; i < size; i++) {
            elementData[i] = old[i];
        }
    }

    //返回棧的實際元素個數
    public int size() {
        return size;
    }

}

上面的代碼實現了棧的基本操作,push/pop/clear。

棧的應用##

應用1 —— 平衡符號####

編譯器檢查程序的語法錯誤,但是常常由于缺少一個符號(如遺漏一個花括號)引起編譯不能通過。在這種情況下,一個有用的工具就是檢測是成對出現的東西。于是,一個右花括號、右方括號及右括號必然對其相應的左括號。比如[()]序列是合法的,[(])是不合法的。

通過棧,可以實現上述程序,思路如下:
做一個空棧,讀取字符直到末尾。如果字符是一個開放符號(左邊的符號),則將其推入棧內。如果字符是一個封閉符號(右邊的符號),則當空棧是報錯。否則,將棧元素彈出。如果彈出的開放符號不是當前封閉符號所對應的符號,則報錯。在字符串末尾,若棧非空則報錯。

代碼實現######
public class StackApplication {

    // 申明一個棧,用來存放開放符號
    Stack<Character> lefts = new Stack<Character>();
    // 申明一個數組,用來存放封閉符號
    ArrayList<Character> rights = new ArrayList<Character>();
    // 用來存放從控制臺讀取的字符數組
    char[] characters;

    // 從控制臺讀取字符串,并轉換成字符串
    private void readCharsFromConsole() {
        Scanner scanner = new Scanner(System.in);
        if (scanner.hasNext()) {
            // 給characters數組
            characters = scanner.next().toCharArray();
        }
    }

    // 檢查開放符號和封閉符號是否匹配
    public boolean check() {
        readCharsFromConsole();

        // 開放符號進棧lefts,封閉符號添加到數組rights
        for (int i = 0; i < characters.length; i++) {
            char ch = characters[i];
            if (ch == '(') {
                lefts.push(ch);
            }
            if (ch == ')') {
                rights.add(ch);
            }
        }

        // 如果開放符號的個數不等于封閉符號的個數則報錯
        if (lefts.size() != rights.size()) {
            return false;
        }

        // 遍歷封閉符號,如果棧lefts彈出的元素不是當前封閉元素所對應的,則返回false
        for (int i = 0; i < rights.size(); i++) {

            if (rights.get(i).charValue() == ')') {
                if (lefts.pop().charValue() != '(') {
                    return false;
                }
            }
        }

        // 最后返回true
        return true;
    }

}
測試代碼######
    public static void main(String[] args) {
        StackApplication stackApplication = new StackApplication();
        System.out.println(stackApplication.check());
    }

測試結果######

在控制臺輸入((())),回車,結果如下圖所示,返回true。

結果

在控制臺輸入((()),回車,結果如下圖所示,返回false。

結果

應用二 —— 后綴表達式(逆波蘭表達式)####

假設我們需要一個計算器來計算我們購物的花費,你的計算公式可能是這樣的:2 * 2 + 3 * 3,如果你手上的計算器的普通計算器的話,答案為21。但是現在大多數時候我們希望使用科學計算器,它可以判定運算的優先級,科學計算器的結果為13。

我們簡單地分析一下科學計算器計算上面例子的過程,首先判斷優先級,乘法有限,所以依次計算2*2和#3*3并將其結果分別存儲到兩個臨時變量A1和A2中,最后計算加法,將A1和A2求和。我們可以將這種操作寫為:2 2 * 3 3 * +。這就是逆波蘭表達式。

計算細節如下:

首先假設有一個空棧stack,遍歷后綴表達式,將數字壓入棧中,直到遇到一個操作符,這個操作符的操作數為連續兩次出棧的結果。

  • 第一步:stack=[2,2],讀到一個操作符為*,于是操作數連續兩次出棧的結果2和2。所以計算結果為2*2=4,將結果壓入棧中,stack=[4]。
  • 第二步:繼續讀入數字,直到一個操作符。stack=[4,3,3],操作符為*,操作數為連續兩次出棧的結果3和3。所以計算結果為3*3=9,將結果壓入棧中,stack=[4,9]。
  • 第三部:繼續讀入,下一個元素為+操作數,所以執行加法操作,操作數為連續兩次出棧的結果4和9,所以計算結果為4+9=13。壓棧,stack=13。到此遍歷完了整個后綴表達式,最后結果就為棧頂元素13。
代碼######
public class StackApplication2 {
    // 字符數組,用來保存后綴表達式的每一個字符
    private char[] postfix;
    // 操作數棧
    Stack<Integer> numberStack = new Stack<Integer>();

    // 構造器,傳入一個后綴表達式字符串
    public StackApplication2(String postfix) {
        this.postfix = postfix.toCharArray();
    }

    public StackApplication2() {

    }

    // 判斷后綴表達式中字符是否為數字
    private boolean isNumeric(char ch) {
        return (ch >= '0' && ch <= '9') ? true : false;
    }

    // 科學計算器的實現方法
    public int scientificCaculator() {
        // 兩個操作數
        int num1;
        int num2;
        // 遍歷后綴表達式
        for (int i = 0; i < postfix.length; i++) {
            char temp = postfix[i];
            // 如果是數字就壓棧
            if (isNumeric(temp)) {
                numberStack.push(temp - '0');// char轉int
            } else {
                // 如果是操作符就從棧中彈出操作數并執行相關運算
                num1 = numberStack.pop();
                num2 = numberStack.pop();
                if (temp == '+') {
                    numberStack.push(num1 + num2);
                } else if (temp == '-') {
                    numberStack.push(num1 - num2);
                } else if (temp == '*') {
                    numberStack.push(num1 * num2);
                } else if (temp == '/') {
                    numberStack.push(num1 / num2);
                }
            }
        }

        // 返回最后的棧頂元素,即結果
        return numberStack.pop();
    }

}
測試代碼#######
    public static void main(String[] args) {
        StackApplication2 stackApplication2 = new StackApplication2("6523+8*+3+*");
        System.out.println(stackApplication2.scientificCaculator());
    }
結果######

測試結果

計算一個后綴表示花費的時間是O(N),該算法是一個十分簡單的算法。注意,當一個表達式以后綴表示給出時,我們就不用知道運算的優先級,這是一個明顯的優點。

中綴到后綴的轉換####

棧不僅僅可以用來計算后綴表達式的值,還可以用來用來講一個標準形式的表達式(中綴表達式)轉換成后綴表達式。 我們假設只有運算+,*,(,),并且表達式合法以及使用普通的優先級法則,即括號>乘>加。
假設中綴表示1+2*3+(4*5+6)*7,則轉換后的后綴表達式為:123*+45*6+7*+

轉換方法######

我們需要兩個數據結構,一個用來存放操作符的棧operatorStack,一個用來存放后綴表達式的字符數組postfix。遍歷中綴表達式,如果讀到的是一個操作數,則立即添加到postfix數組中,如果是一個操作符則相對麻煩。為了說明方便,我們將operatorStack棧中的棧頂操作符稱為top,當前遍歷的操作符為temp。
操作符的處理規則如下:

  • 如果棧為空,則直接將temp壓入operatorStack棧中。
  • 如果棧不為空并且temp= * 或 + 或 (,然后將temp的優先級和top比較,如果temp的優先級大于top優先級,則將temp壓棧;否則,依次將棧中優先級大于或等于temp的操作符彈出,添加到postfix,直到top優先級高于temp,然后將temp壓棧,但是有一個情況例外,即top=(,temp的優先級低于(時,這種情況,top不會出棧,而是將temp直接壓棧。
  • 如果讀到的),則依次彈出操作符,加入postfix的末尾,直到(。

現在我們對剛才提到的例子1+2*3+(4*5+6)*7采用上面的算法依次計算:

  1. 讀取到操作數1,直接添加到postfix數組中。此時,operatorStack為空,postfix=[1]。
  2. 讀取到操作符+,因為此時棧為空,所以直接壓棧。此時,operatorStack={+},postfix=[1]。
  3. 讀取到操作數2,直接添加到postfix數組中。此時,operatorStack={+},postfix=[1,2]。
  4. 讀取到操作符*,此時temp = *,top = +,優先級temp > top,所以temp壓棧。此時,operatorStack={+,*},postfix=[1,2]。
  5. 讀取到操作數3,直接添加到postfix數組中。此時,operatorStack={+,*},postfix=[1,2,3]。
  6. 讀取到操作符+,此時temp = +,top = *,優先級top>=temp,所以top出棧并添加到postfix數組。此時operatorStack={+},postfix=[1,2,3,*],top=+,top>=temp,所以top出棧并添加到postfix數組。此時operatorStack={},postfix=[1,2,3,*,+}。此時operatorStack為空,所以temp壓棧。所以此時operatorStack={+},postfix=[1,2,3,*,+}。
  7. 讀取操作符(,此時temp = (,top = +,優先級temp > top,所以temp直接壓棧。此時,operatorStack={+,(},postfix=[1,2,3,*,+]。
  8. 讀取操作數4,直接添加到postfix數組中。此時operatorStack={+,(},postfix=[1,2,3*,+,4]。
  9. 讀取操作符*,此時temp = *,top = (,雖然優先級top >= temp,但是對于(特殊處理,不出棧。所以temp直接壓棧。此時operatorStack={+,(,*},postfix=[1,2,3,*,+,4]。
  10. 讀取操作數5,直接添加到postfix數組中。此時operatorStack={+,(,*},postfix=[1,2,3,*,+,4,5]。
  11. 讀取操作符+,此時temp = +,top = *,優先級top >= temp,所以top出棧并且添加到postfix數組,此時operatorStack={+,(},postfix=[1,2,3,*,+,4,5,*],top = (,優先級temp < top,temp直接壓棧。此時operatorStack={+,(,+},postfix=[1,2,3,*,+,4,5,*]。
  12. 讀取操作數6,直接添加到postfix數組中。此時operatorStack={+,(,+},postfix=[1,2,3,*,+,4,5,*,6]。
  13. 讀取操作符),依次彈出棧頂操作符,直到(。此時operatorStack={+},postfix=[1,2,3,*,+,4,5,*,6,+]。
  14. 讀取操作符*,此時temp = *,top = +,優先級temp > top,所以temp壓棧。此時operatorStack={+,*},postfix=[1,2,3,*,+,4,5,*,6,+]。
  15. 讀取操作數7,直接添加到postfix數組中。此時operatorStack={+,*},postfix=[1,2,3,*,+,4,5,*,6,+,7]。
  16. 依次彈出棧中剩余操作符,最終operatorStack={},postfix=[1,2,3,*,+,4,5,*,6,+,7,*,+]。
代碼實現######
public class InfixToPostfix {
    // 存放操作符的棧
    private Stack<Character> operatorStack = new Stack<Character>();
    // 存放后綴表達式的字符數組
    private char[] postfix;
    // 存放中綴表達式的字符數組
    private char[] infix;

    // 定義操作符
    private static final char multiply = '*';
    private static final char divide = '/';
    private static final char add = '+';
    private static final char substract = '-';
    private static final char leftParenthesis = '(';
    private static final char rightParenthesis = ')';

    // 構造器
    public InfixToPostfix(String infix) {
        this.infix = infix.toCharArray();
        postfix = new char[infix.length()];
    }

    public InfixToPostfix() {

    }

    // 判斷一個字符是否為數字
    private boolean isNumeric(char ch) {
        return (ch >= '0' && ch <= '9') ? true : false;
    }

    private String infixToPostfix() {
        // 插入后綴的表達式下標
        int insertIndex = 0;
        // 操作符棧的大小
        int size;
        // 返回結果,后綴表達式字符串
        String result;

        for (int i = 0; i < infix.length; i++) {
            if (isNumeric(infix[i])) {
                postfix[insertIndex++] = infix[i];
            } else {

                char temp = infix[i];
                size = operatorStack.size();
                // 如果棧為空,就直接壓棧
                if (size == 0) {
                    operatorStack.push(temp);
                } else { // 棧不為空時

                    // 彈出棧頂元素
                    char top = operatorStack.pop();

                    if (temp == rightParenthesis) {
                        while (top != leftParenthesis) {
                            postfix[insertIndex++] = top;
                            top = operatorStack.pop();
                        }
                    } else {

                        // 如果當前操作符的優先級大于棧頂操作符的優先級,則當前操作符進棧

                        if (operatorPriorityCompare(temp, top)) {
                            /*
                             * 因為上一行為了比較當前操作符和棧頂元素的操作符, 
                             * 上面彈出了棧頂元素,
                             * 所以這里要先將彈出的操作符壓棧
                             */
                            operatorStack.push(top);
                        }

                        /*
                         * 如果當前操作符的優先級低于或等于棧頂操作符的優先級, 則棧頂操作符出棧,然后一直比較,
                         * 直到棧頂操作符優先級大于當前操作符或棧為空,然后當前操作符進棧。
                         */

                        while (!operatorPriorityCompare(temp, top)) {
                            if (top == leftParenthesis) {
                                operatorStack.push(top);
                                break;
                            } else {
                                postfix[insertIndex++] = top;
                                size = operatorStack.size();
                                if (size == 0) {
                                    break;
                                }
                                top = operatorStack.pop();
                            }
                        }

                        // 當前操作符進棧
                        operatorStack.push(temp);

                    }
                }
            }
        }

        // 遍歷完畢,一次彈出棧中剩余操作符
        size = operatorStack.size();
        if (size != 0) {
            for (int i = 0; i < size; i++) {
                postfix[insertIndex++] = operatorStack.pop();
            }
        }

        result = String.valueOf(postfix).trim();
        return result;
    }

    // 判斷操作符的優先級:括號>乘除>加減
    // true: temp>top false: top >= temp
    private boolean operatorPriorityCompare(char current, char top) {
        if (((current == multiply || current == divide) && (top == substract || top == add))
                || (current == leftParenthesis
                        && (top == multiply || top == divide || top == substract || top == add))) {
            return true;
        } else {
            return false;
        }

    }

}
測試代碼######
    public static void main(String[] args) {
        InfixToPostfix infixToPostfix = new InfixToPostfix("1+2*3+(4*5+6)*7");
        String result = infixToPostfix.infixToPostfix();
        System.out.println(result);
    }
測試結果######
測試結果

與上面相同,這種轉換需要O(N)時間。可以通過制定減法和加法有相同優先級以及乘法和除法有相同優先級二將減法和乘法添加到指令中去。需要注意的是,表達式a-b-c應該轉化為ab-c-,而不是abc--。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,757評論 25 708
  • ArrayList和LinkedList的實現方式 ArrayList的底層實現是可以增長的數組,LinkedLi...
    wqbu閱讀 398評論 0 0
  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,075評論 6 13
  • 早上一陣手機鈴聲將我從睡夢里驚醒,習慣性的從床上彈起來,條件反射的穿好衣服。忽然覺得血沖頭頂,有點不舒服。我馬上警...
    行動派健身閱讀 197評論 0 0
  • 如果你結婚的那天新郎不是我,我就會把我們的那些曖昧的聊天記錄、短信、電話錄音、相片什么的都弄成CD寄給你對象,然后...
    不說是不說閱讀 255評論 0 0