數據結構之棧(以字符反轉和分隔符匹配爲例子)

我是野豬。

棧的概念:棧作爲一種數據結構,是一種只能在一端插入和刪除操作的特殊線性表。它按照新進後出的原則存儲數據,先進入的數據被壓入棧底,最後的數據在棧頂,需要讀數據的時候從棧頂開始彈數據。

關鍵點:1.先入後出  2.訪問限制,在特定的時刻,只允許一個元素被讀取或刪除(棧頂的元素)。3.棧操作:棧空、棧滿、彈棧、入棧等

棧相關知識:

1.數據結構棧和隊列更多的是作爲程序員的工具來運用。他們主要作爲構思算法的輔助工具,而不是完全的數據存儲工具。這些數據結構的生命週期比那些數據庫類型的結構要短暫的多。在程序執行期間他們才被創建,通常用他們去執行某項特殊的任務,當任務完成之後,他們就被銷燬。

2.棧的主要機制可以通過數組來實現,也可以通過鏈表來實現。優先級隊列的內部實現可以通過數組或堆來實現。

3.實際計算機系統中,大部分微處理器運用棧的體系結構。棧在程序的運行中有舉足輕重的作用。最重要的是棧保存了一個函數調用時所需要的維護信息,這常常稱之爲堆棧幀。堆棧幀一般包含下面幾個信息:①函數的返回地址和參數 ②臨時變量:包含函數的非靜態局部變量以及編譯器自動生成的其他臨時變量。當調用一個方法時,把他的返回地址和參數壓入棧,當方法結束返回時,那些數據出棧。棧操作就嵌入在微處理器中。

4.棧通常很小,是臨時的數據結構。


爲了更好理解數據結構棧,我們根據棧的屬性,編寫自己的棧,並且根據棧先入後出的原則實現了兩個最基本的例子:字符串反轉和分隔符匹配問題。

根據性質編寫自己的棧:

/**
 * 創建一個自己的棧
 * 1.先入後出的原則
 * 2.訪問權限  在特定時刻只有一個數據項可以讀取或者被刪除  只能操作位於棧頂的元素
 * 組織數據的方式 入棧 出棧 棧空 棧滿
 */

public class YezhuStack {

    //類型爲Object的數組
    private final Object[] oStack;

    //數組的長度
    private int size;

    //棧頂標誌
    private int top = -1;

    public YezhuStack(int max) {
        this.size = max;
        oStack = new Object[max];
    }

    /**
     * 入棧
     * 先判斷是不是棧滿的
     *
     * @param object
     */
    public void push(Object object) {
        if (!isFull()) {
            oStack[++top] = object;
        } else {
            throw new IndexOutOfBoundsException("the stack is full");
        }
    }

    /**
     * 棧滿否
     *
     * @return
     */
    private boolean isFull() {
        return top == size - 1;
    }

    /**
     * 出棧
     * 出棧先判斷棧是不是爲空
     * 訪問權限:在特定時刻只有一個數據項被允許讀取或刪除
     *
     * @return
     */
    public Object pop() {
        if (!isEmpty()) {
            return oStack[top--];
        } else {
            throw new IndexOutOfBoundsException("the stack is empty");
        }
    }

    /**
     * 棧是不是空
     *
     * @return
     */
    public boolean isEmpty() {
        return top == -1;
    }


    /**
     * 查看棧頂的元素
     */
    public Object peek() {
        if (!isEmpty()) {
            return oStack[top];
        } else {
            throw new IndexOutOfBoundsException("the stack is empty");
        }
    }
}

注意點:初始值top=-1,是根據代碼做的修改。push時oStack[++top],++top =>先+1後賦值;pop時,oStack[top--],top-- => 先賦值後-1;還有判斷棧是否爲空,條件是top==-1而不是size=0,因爲一開始初始化的時候根據要操作的數據的長度創建的棧,而在壓棧和彈棧期間,其size並沒有變化。


例子1:字符串反轉;字符串反轉就是輸入一字符串,把字符串中的一個個元素從頭壓入棧,然後彈出棧時就是倒序。

/**
 * 測試字符串反轉
 */

public class ReverseStrTest {
    private static YezhuStack yezhuStack;

    public static void main(String[] args) throws IOException {
        System.out.println("please import str:");
        InputStreamReader inputStreamReader = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(inputStreamReader);
        String str = br.readLine();

        System.out.println("print reversed str:" + reverseStr(str));

    }

    /**
     * 字符串反轉後
     *
     * @param str
     */
    private static String reverseStr(String str) {
        String out = "";

        if (str != null) {
            yezhuStack = new YezhuStack(str.length());
        }

        //1.字符串放入棧中
        for (int i = 0; i < str.length(); i++) {
            yezhuStack.push(str.charAt(i));
        }

        //2.棧直接輸出即可
        for (int i = 0; i < str.length(); i++) {
            out = out + yezhuStack.pop();
        }
        return out;
    }
}

運行效果:



例子2:分隔符匹配;分隔符匹配程序從字符串中不斷的讀取字符,每次讀取一個字符。若發現他是左分隔符,將他壓入棧。當從輸入中讀取一個右分隔符,彈出棧頂的左分隔符,並且查看是否和右分隔符相匹配。比如eclipes編譯器,當你少一個“}”時,會報錯,其原理就是如此。

/**
 * 分隔符匹配問題
 * 用棧結構模擬的分隔符匹配;
 * 編譯器報錯的 比如當少{[(   )]} 左分隔符 壓入棧,遇到右分隔符時 與棧頂元素進行匹配, 不匹配則編譯器報錯
 */

public class SeparatorMatchTest {
    private static YezhuStack yezhuStack;

    public static void main(String[] args) throws IOException {

        System.out.println("please import str:");
        InputStreamReader inputStreamReader = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(inputStreamReader);
        String str = br.readLine();

        checkSeparator(str);

    }

    /**
     * 檢查輸入的字符串
     *
     * @param str
     */
    private static void checkSeparator(String str) {

        if (str != null) {
            yezhuStack = new YezhuStack(str.length());
        }

        //1.整個字符串中遇到左匹配符號就入棧 因此循環入棧的時候要匹配條件
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            switch (ch) {
                case '{':
                case '(':
                case '[':
                    //以上是左匹配符 遇到左匹配符入棧
                    yezhuStack.push(ch);
                    break;
                case '}':
                case ')':
                case ']':
                    //遇到右匹配符然後出棧 做對比
                    if (!yezhuStack.isEmpty()) {
                        char chRight = (char) yezhuStack.pop();
                        if ((ch == '}' && chRight != '{')
                                || (ch == '(' && chRight != ')')
                                || (ch == ']' && chRight != '[')) {
                            //ch是循環拿字符串中匹配符 chRight是棧中的左匹配符
                            System.out.println("Error: " + ch + " at " + i);
                        }
                    }
                    break;
                default:
                    break;

            }

        }

        //最後做一次判斷 正常來說要是左右分隔符能全部匹配成功的話 此時棧中已經空了 如果不爲空 說明右分隔符少了
        if (!yezhuStack.isEmpty()) {
            System.out.println("輸入含有左右分隔符的字符串缺少右分隔符");
        }
    }
}
運行效果:



以上就是兩個基本使用棧的例子,如果有問題可以私信我。

補充在java基礎講過的棧和堆內存的概念:

1.棧內存用來存儲局部變量和方法調用,堆內存用來存儲java中new出來的對象。

2.棧內存屬於單個線程,每個線程都會有一個棧內存,其存儲的變量只能在其所屬線程中可見;而堆內存中的對象對所有線程可見,可以被所有線程訪問。




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