數據結構與算法學習七:棧、數組模擬棧、單鏈表模擬棧、棧應用實例 實現 綜合計算器

前言

  • 學習棧
  • 瞭解棧的特點。先進後出,後進先出
  • 使用數組模擬棧、鏈表模擬棧。(相比於雙鏈表和單向環形鏈表來說是相對簡單的
  • 棧的應用實例:用棧+中綴表達式 實現綜合計算器
  • 2020.4.21、22 日學習
  • 相對於前面的雙鏈表和單向環形鏈表,這裏的數組模擬棧、鏈表模擬棧是比較簡單的。
  • 比較重要的是 棧的應用:棧+中綴表達式 實現綜合計算器。關於中綴在下一節博客學習、講解。

一、棧

1.1 棧的介紹

  1. 棧的英文爲(stack)
  2. 棧是一個 先入後出 (FILO-First In Last Out)的有序列表。
  3. 棧(stack)是限制線性表中元素的插入和刪除 只能在線性表的同一端 進行的一種特殊線性表。允許插入和刪除的一端,爲變化的一端,稱爲 棧頂 (Top),另一端爲固定的一端,稱爲 棧底(Bottom)。
  4. 根據棧的定義可知,最先放入棧中元素在棧底,最後放入的元素在棧頂,而刪除元素剛好相反,最後放入的元素最先刪除,最先放入的元素最後刪除。
  5. 棧的基礎方法爲入棧、出棧、顯示棧數據

1.2 棧的應用實例【重點】

請輸入一個表達式
計算式:[722-5+1-5+3-3] 點擊計算【如下圖】
在這裏插入圖片描述
請問: 計算機底層是如何運算得到結果的? 注意不是簡單的把算式列出運算,因爲我們看這個算式 7 * 2 * 2 - 5, 但是計算機怎麼理解這個算式的(對計算機而言,它接收到的就是一個 字符串 ),我們討論的是這個問題。-> 棧

1.3 棧的應用場景

  1. 子程序的調用:在跳往子程序前,會先將下個指令的地址存到堆棧中,直到子程序執行完後再將地址取出,以回到原來的程序中。
  2. 處理遞歸調用:和子程序的調用類似,只是除了儲存下一個指令的地址外,也將參數、區域變量等數據存入堆棧中。
  3. 表達式的轉換:[中綴表達式轉後綴表達式]與求值(實際解決)。
  4. 二叉樹的遍歷
  5. 圖形的深度優先(depth一first)搜索法。

1.4 入棧與出棧

直接上圖,看圖理解:
在這裏插入圖片描述
在這裏插入圖片描述

二、數據模擬棧

2.1 思路分析

  1. 由於棧是一種有序列表,當然可以使用數組的結構來儲存棧的數據內容,下面我們就用數組模擬棧的出棧,入棧等操作
  2. 實現思路分析,並畫出示意圖
  1. 使用數組來模擬棧
  2. 定義一個 top 來表示棧頂,初始化 爲 -1
  3. 入棧 的操作,當有數據加入到棧時, top++; stack[top] = data;
  4. 出棧 的操作,int value = stack[top]; top--, return value

在這裏插入圖片描述

2.2 代碼結構

ArrayStack: 數組模擬棧的 類
ArrayStackMain:測試類
在這裏插入圖片描述

2.3 ArrayStack 棧類

package com.feng.ch06_stack.s1_arraystack;

/*
 * 定義一個 ArrayStack 表示棧,數組模擬 棧
 * */
public class ArrayStack {

    private int maxSize; // 棧的大小
    private int[] stack; // 數組,數組模擬棧,數據就放在數組中。
    private int top = -1; // top 表示棧頂,初始化爲-1

    public ArrayStack(int maxSize) {
        this.maxSize = maxSize;
        this.stack = new int[this.maxSize];
    }

    //棧滿
    public boolean isFull(){
        return top == maxSize-1;
    }

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

    // 入棧
    public void push(int value){
        if (isFull()){
            System.out.println("棧已滿,無法添加~");
            return;
        }
        top++;
        stack[top] = value;
    }

    // 出棧,將棧頂的數據返回
    public int pop(){
        if (isEmpty()){
            throw new RuntimeException("棧已空,無法獲取數據\n");
        }
        int num = stack[top];
        top--;
        return num;
    }

    // 顯示棧的情況【遍歷棧】,遍歷時,需要從棧頂開始顯示數據
    public void list(){
        if (isEmpty()){
            System.out.printf("棧空,無數據\n");
        }
        for (int i = top; i>=0; i--){
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }
    }
}

2.4 ArrayStackMain 測試類

package com.feng.ch06_stack.s1_arraystack;

import java.util.Scanner;

public abstract class ArrayStackMain {
    public static void main(String[] args) {
        // 測試 ArrayStack 是否正確
        // 先創建一個 ArrayStack 對象 -》棧
        ArrayStack stack = new ArrayStack(4);
        String key = "";
        boolean loop = true;
        Scanner scanner = new Scanner(System.in);

        while (loop) {
            System.out.println("show:表示顯示棧");
            System.out.println("exit:退出程序");
            System.out.println("push:表示添加數據到棧(入棧)");
            System.out.println("pop:表示從棧取數據(出棧)");
            System.out.println("請輸入您的選擇");
            key = scanner.next();
            switch (key) {
                case "show":
                    stack.list();
                    break;
                case "push":
                    System.out.println("請輸入一個數:");
                    int value = scanner.nextInt();
                    stack.push(value);
                    break;
                case "pop":
                    try {
                        int res = stack.pop();
                        System.out.printf("出棧的數據是 %d\n", res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case "exit":
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }
        System.out.printf("程序退出成功~~~");
    }
}

2.5 測試結果

簡單運行測試一下,運行即可
在這裏插入圖片描述

三、單鏈表模擬棧

3.1 方法分析

棧的基礎方法爲入棧(push)、出棧(pop)、顯示棧數據(list)

  1. 先創建頭節點head。
  2. push():第一次添加時,直接添加到頭結點的後面:head.setNext(newNode);
    第二次往後,先將頭結點後面的節點掛載到newNode上,再將newNode掛載到head 上。即:
    newNode.setNext(head.getNext());
    head.setNext(newNode);
  3. pop():直接輸出頭結點head後的一個節點即可,再將後一個節點的後一個節點掛載到 頭節點即可:
    StackNode next = head.getNext();
    head.setNext(next.getNext());
    return next;
  4. list():設置輔助指針。循環遍歷輸出即可。

3.2 代碼結構

StackNode: 鏈表節點
LinkedStack: 棧方法
LinkedStackMain: 測試方法
在這裏插入圖片描述

3.3 StackNode 鏈表節點

package com.feng.ch06_stack.s2_linkedstack;

public class StackNode {
    private int no;
    private StackNode next;

    public StackNode(int no) {
        this.no = no;
    }
    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public StackNode getNext() {
        return next;
    }

    public void setNext(StackNode next) {
        this.next = next;
    }

    @Override
    public String toString() {
        return "StackNode{" +
                "no=" + no +
                '}';
    }
}

3.4 LinkedStack 棧方法

package com.feng.ch06_stack.s2_linkedstack;

public class LinkedStack {
    private StackNode head = new StackNode(0);

    public StackNode getHead() {
        return head;
    }

    /*
     * 入棧
     * */
    public void push(StackNode newNode) {
        if (head.getNext() == null) { // 第一次天機
            head.setNext(newNode);
        } else {
            newNode.setNext(head.getNext());
            head.setNext(newNode);
        }
    }

    /*
     * 出棧
     * */
    public StackNode pop(){
        if (head.getNext() == null){
            throw new RuntimeException("棧爲空,無法出棧");
        }
        /*
        * 兩種返回方式
        * */
//        int value = head.getNext().getNo();
//        head.setNext(head.getNext().getNext());
//        return value;
        StackNode next = head.getNext();
        head.setNext(next.getNext());
        return next;
    }


    /*
     * 遍歷
     * */
    public void list(){
        if (head.getNext() == null){
            System.out.println("棧爲空,無法遍歷");
            return;
        }
        StackNode temporary = head.getNext();
        while (true){
            if (temporary == null){
                break;
            }
            System.out.printf("節點編號%d\n",temporary.getNo());
            temporary = temporary.getNext();
        }
    }
}

3.5 LinkedStackMain 測試方法

package com.feng.ch06_stack.s2_linkedstack;

public class LinkedStackMain {
    public static void main(String[] args) {
        LinkedStack stack = new LinkedStack();

        StackNode node1 = new StackNode(1);
        StackNode node2 = new StackNode(2);
        StackNode node3 = new StackNode(3);
        StackNode node4 = new StackNode(4);
        StackNode node5 = new StackNode(5);

        stack.push(node1);
        stack.push(node2);
        stack.push(node3);
        stack.push(node4);
        stack.push(node5);

        System.out.println("初始化的棧:");
        stack.list();

        // 測試出棧
        System.out.println();
        System.out.println("出棧的數據:"+stack.pop());

        System.out.println();
        System.out.println("出棧後的棧:");
        stack.list();
    }
}

3.5 測試結果

在這裏插入圖片描述

四、棧+中綴表達式 實現綜合計算器

4.1 需求描述

給定一個表達式字符串:【3+2*6-2】, 進行計算。

中綴表達式 就是 3+2*6-2 這種我們熟知的計算表達式。關於中綴表達式在下一節詳細講解

4.2 思路分析

  1. 使用上面開發的數組模擬棧,新建兩個棧,一個數棧、一個符號棧
  2. 通過一個 index 值(索引),來遍歷我們的表達式
  3. 如果我們 發現是一個數字, 就直接入數棧
  4. 如果發現 掃描到是一個符號, 就分如下情況
    4.1 如果發現當前的符號棧爲 空,就直接入棧
    4.2 如果符號棧有操作符,就進行比較, 如果 當前的操作符的優先級小於或者等於棧中的操作符 , 就需要從數棧中pop出兩個數,在從符號棧中pop出一個符號,進行運算,將得到結果,入數棧,然後將當前的操作符入符號棧, 如果當前的操作符的優先級大於棧中的操作符, 就直接入符號棧
  5. 當表達式掃描完畢,就順序的從 數棧和符號棧中pop出相應的數和符號,並運行.
  6. 最後在數棧只有一個數字,就是表達式的結果

驗證: 3+2*6-2 = 13
在這裏插入圖片描述

4.3 代碼實現

  • 先實現一位數的運算。
  • 擴展到多位數的運算, 擴展多位的運算,主要改變的地方:
  1. 增加了一個變量keepNum 用戶多位數的拼接,
  2. 然後在掃描時,遇到的字符 爲數字時,再往後多看一位,如果是數字,則拼接,如果不是數組,則到此結束。

4.3.1 Calculator.java 計算類

package com.feng.ch06_stack.s3_calculator;

public class Calculator {
    public static void main(String[] args) {
        // 根據前面思路,完成表達式的運算
        String expression = "3+2*6-2"; // 3+2*6-2=13
        // 思考:如果處理多位數的問題? 30+2*6-2=40 。添加 keepNum 用於拼接多位數。

        // 創建兩個棧,數棧、一個符號棧
        ArrayStack numStack = new ArrayStack(10);
        ArrayStack operStack = new ArrayStack(10);
        // 定義需要的相關變量
        int index = 0; // 用於掃描
        int num1 = 0;
        int num2 = 0;
        int oper = 0;
        int res = 0;
        char ch = ' '; // 將每次掃描得到插入保存到 ch
        String keepNum = ""; // 用於拼接多位數
        // 開始循環的掃描 expression
        while (true) {
            // 依次得到expression 的每一個字符
            ch = expression.substring(index, index + 1).charAt(0);
            /*
            * 判斷 ch 是什麼,然後做相應的處理
            * 如果是運算符
            * */
            if (operStack.isOperation(ch)) {
                // 判斷當前的符號棧是否爲空
                if (!operStack.isEmpty()) {
                    // 如果符號棧有操作符,就進行比較,如果當前的操作符的優先級小於或者等於棧中的操作符,就需要從數棧中 pop 出兩個數,
                    // 在符號棧中 pop 出一個符號,進行運算,將得到結果,入數棧,然後將當前的操作符入符號棧。
                    if (operStack.priority(ch) <= operStack.priority(operStack.peek())) {
                        oper = operStack.pop();
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        res = numStack.calculation(num1, num2, oper);
                        // 把運算的結果入數棧
                        numStack.push(res);
                        // 然後把當前的操作符入符號棧
                        operStack.push(ch);
                    } else {
                        // 如果當前的操作符的優先級大於棧中的操作符,就直接入符號棧
                        operStack.push(ch);
                    }
                } else {
                    // 如果爲空,直接入棧。。
                    operStack.push(ch);
                }
            } else {// 如果是數
                /*
                * 如果是數,就直接入數棧, 這樣不能滿足 多位數。。。
                * // ? "1+3" '1' => 1, ch 爲字符,轉換爲 數字要 減48。
                * 如果是個位數,用字符 char 來表示,則要減48,如果是多位數,下邊變成了字符串,就直接使用包裝類的方法轉換成 整型
                * */
                //numStack.push(ch-48);

                /*
                 * 解決多位數的 分析思路:
                 * 1、當處理多位數時,不能發現是一個數就立即入棧,因爲他可能是多位數
                 * 2、在處理數時,需要向expression 的表達式的index 後在多看一位,如果是數就立刻掃描,如果是符號在入棧
                 * 3、因此定義一個 變量 字符串,用於拼接
                 * */

                // 處理多位數
                keepNum += ch;

                // 如果 ch 已經是expression 的最後一位,就直接入棧
                if (index == expression.length() - 1) {
                    numStack.push(Integer.parseInt(keepNum));
                } else {
                    //判斷下一個字符是不是數字,如果是數字,就繼續掃描,如果是運算符,則入棧
                    // 注意是看後一位,不是 index++
                    if (operStack.isOperation(expression.substring(index + 1, index + 2).charAt(0))) {
                        // 如果後一位是運算符,則入棧, keepNum = "1" 或者 "123"
                        numStack.push(Integer.parseInt(keepNum));
                        keepNum = "";
                    }
                }
            }

            // 讓index +1 ,並判斷 是否掃描到 expression 最後
            index++;
            if (index >= expression.length()) {
                break;
            }

        }
        /*
        * 當表達式掃描完畢,就順序的從數棧 和符號棧 中 pop 出相應的數和符號,並運行
        * */
        while (true) {
            // 如果符號棧爲空,則計算到最後的結果,數棧中只有一個數字【結果】
            if (operStack.isEmpty()) {
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            res = numStack.calculation(num1, num2, oper);
            numStack.push(res);
        }
        // 將數棧的最後數,pop出,就是結果
        int result = numStack.pop();
        System.out.printf("表達式 %s = %d", expression, result);
    }
}

4.3.2 ArrayStack.java 使用的棧(自定義,數組模擬棧)

對於之前的數組模擬棧、鏈表模擬棧,這裏需要增加幾個方法:
peek(): 查看棧頂數據,但不是出棧。
priority(int oper):返回運算符的優先級,優先級是程序員來確定,優先級使用數字表示。
calculation(int num1, int num2, int oper):計算方法

package com.feng.ch06_stack.s3_calculator;

/*
 * 先創建一個棧,直接使用前面創建好的,
 * 需要擴展功能:增加
 * 定義一個 ArrayStack 表示棧
 * */
public class ArrayStack {

    private int maxSize; // 棧的大小
    private int[] stack; // 數組,數組模擬棧,數據就放在數組中。
    private int top = -1; // top 表示棧頂,初始化爲-1

    public ArrayStack(int maxSize) {
        this.maxSize = maxSize;
        this.stack = new int[this.maxSize];
    }

    //棧滿
    public boolean isFull() {
        return top == maxSize - 1;
    }

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

    // 入棧
    public void push(int value) {
        if (isFull()) {
            System.out.println("棧已滿,無法添加~");
            return;
        }
        top++;
        stack[top] = value;
    }

    // 出棧,將棧頂的數據返回
    public int pop() {
        if (isEmpty()) {
            throw new RuntimeException("棧已空,無法獲取數據\n");
        }
        int num = stack[top];
        top--;
        return num;
    }

    // 顯示棧的情況【遍歷棧】,遍歷時,需要從棧頂開始顯示數據
    public void list() {
        if (isEmpty()) {
            System.out.printf("棧空,無數據\n");
        }
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d]=%d\n", i, stack[i]);
        }
    }

    /*
    * 查看棧頂的 數據, 不是真正出棧
    * */
    public int peek(){
        return stack[top];
    }

    /*
     * 返回運算符的優先級,優先級是程序員來確定,優先級使用數字表示
     * 數字越大,則優先級越高。
     * */
    public int priority(int oper) {
        if (oper == '*' || oper == '/') {
            return 1;
        } else if (oper == '+' || oper == '-') {
            return 0;
        } else {
            return -1; // 假定目前的表達式只有 + - * /
        }
    }

    /*
     * 判斷是不是一個雲算法
     * */
    public boolean isOperation(char value) {
        return value == '+' || value == '-' || value == '*' || value == '/';
    }

    /*
    * 計算方法
    * @param num1
    * @param num2
    * @param oper
    * @return
    * */
    public int  calculation(int num1, int num2, int oper){
        int res = 0;
        switch (oper){
            case '+':
                res = num1 + num2;
                break;
            case '-':
                res = num2 - num1; // 注意順序
                break;
            case '*':
                res = num1 * num2;
                break;
            case '/':
                res = num1/num2;
                break;
            default:
                break;
        }
        return res;
    }
}

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