經過棧實現中綴表達式到後綴表達式的轉換

在編程的世界中數據結構和算法老是如影隨行, 難捨難分的.git

棧做爲一種常見的數據結構(抽象數據類型)在程序的世界中有很是的意義.算法

在計算機科學中,棧是一種抽象數據類型(ADT / Abstract Data Type),用做數據的集合表示.棧有兩個主要的操做編程

  • push用於將元素推入棧中數據結構

  • pop 用於將元素從棧頂部彈出數據結構和算法

簡單來講,棧就是一個後入先出(LIFO / Last In First Out)的隊列.現實生活中疊盤子就是一個形象的棧,新盤子只能在頂部堆疊進去,而抽盤子是從頂部一個個抽走.函數

棧的應用

棧在計算機中有很是普遍的應用,好比說函數的調用堆棧.談點更實在的應用的話,棧能夠很是方便的用來作平衡符號, 表達式求值和語法解析.
今天的重點是經過棧實現一箇中綴表達式到後綴表達式的轉換,爲以後的構建表達式樹作鋪墊.指針

使用棧來實現平衡符號其實很是簡單code

  • 遇到 '(', '[', '{'就將符號推入棧中遞歸

  • 遇到 ')', ']', '}'就彈出棧中的一個元素,查看是否匹配隊列

  • 處理完全部數據以後棧應該爲空.

這個算法的時間複雜度爲O(N)而且這個算法是在線的.

後綴表達式

常見的表達式如a + b * c + g / f在計算這個表達式時,咱們必須明確記住操做符的優先級, +, -的優先級小於*, /因此表達式處理上就會比較複雜.若是咱們換種思路,將中綴表達式轉換爲後綴表達式,那處理就會簡單不少.

這種記法其實就是將咱們口頭上的說法搬到紙上.

  • b和c相乘 => b c *(A1)

  • a與b和c相乘的結果相加 => a (A1) +(A2)

  • g被f除 => g f /(A3)

  • 前面的結果與後面的結果相加 => A2 A3 +

  • A2 A3 +展開以後就獲得了a b c * + g f / +

最後展開的結果就是中綴表達式a + b * c + g / f轉換成後綴表達式的結果a b c * + g f / +,這種記法叫作後綴記法或者逆波蘭記法.
計算這個表達式的值的簡單方法就是使用棧.

  • 當遇到一個操做數時就將這個操做數推入棧中

  • 當遇到一個操做符時就從棧中彈出兩個操做數, 並將這個操做符運用於彈出的兩個操做數上

這種方法顯而易見的一個大優勢就是沒有必要知道任何優先級規則.並且這個算法的時間複雜度是O(N), 並且是在線的.

那麼問題來了,如何將中綴表達式轉換成後綴表達式呢?

中綴 => 後綴

顯然,咱們也能夠經過棧來進行轉換.記住如下幾個規則

  • 當遇到一個操做數時,將這個操做數輸出

  • 當遇到一個操做符時,將棧中大於等於該符號優先級的操做符彈出(並輸出).而後將該操做符壓入棧中

  • 當遇到特殊操做符開括號('(', '[', '{')時,將該符號壓入棧中.可是把從這個符號開始當作一個新棧(能夠考慮遞歸的調用棧)

  • 當遇到特殊操做符閉括號(')', ']', '}')時,將最新的那個棧中的元素所有彈出(並輸出, 可是不輸出彈出的開括號)

規則就這麼多,是否是感受很難理解?咱們來看個栗子~

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h> // 用於判斷字符類型

int sp = -1; // 棧頂指針, 始終指向棧頂, 空棧時指向棧底(0)之下
char symbol_stack[64] = { 0 }; // 顯然,咱們須要個符號棧來存放操做符

// 定義幾個棧操做
void push(char op); // 將符號 op 推入棧中
char pop(void); // 彈出棧頂元素
char top(void); // 查看棧頂的元素

int main(void) {
    // 定義一個輸入表達式
    char *input = "a + b * c - g / f";
    
    // 開始處理輸入表達式
    // 咱們只進行四則運算的轉換
    for (int index = 0; index < strlen(input); ++index) {
        char current_character = input[index];
        char stack_top_character = top();

        // 若是是操做數,那就直接輸出
        if (isdigit(current_character) || isalpha(current_character)) {
            printf("%c ", current_character);
            continue;
        }

        // 若是不是操做數,而且不是 '+', '-', '*', '/', '(', ')', ' ' 那就報錯
        if (current_character != '+' && current_character != '-' &&
            current_character != '*' && current_character != '/' &&
            current_character != '(' && current_character != ')' &&
            current_character != ' ') {
            fprintf(stderr, "Unknown symbol `%c`\n", current_character);
            exit(1);
        }

        // 跳過空格和結束標誌
        if (current_character == ' ' || current_character == '\0') {
            continue;
        }

        switch (current_character) {
            // 特殊操做符 (
            case '(':
                push(current_character);
                break;
            // 特殊操做符 )
            case ')':
                // 彈出新棧的全部元素, 或者直到爲空棧
                while (stack_top_character != '(' && stack_top_character != '\0') {
                    printf("%c ", stack_top_character);
                    stack_top_character = pop();
                }
                pop(); // 彈出多餘的 (
                break;
            case '*':
            case '/':
                // 除了 '(' ')' 以外, * / 具備最高的優先級, 除非棧中不可能有比 * / 更大的優先級
                // 因此只能彈出優先級相等的操做符(自己)
                while (stack_top_character == '*' || stack_top_character == '/') {
                    printf("%c ", pop());
                    stack_top_character = top();
                }
                // 執行完以後壓入當前的符號
                push(current_character);
                break;
            case '+':
            case '-':
                // + - 具備最低的優先級, 因此彈出全部的操做符,除非是表明新棧的 (
                while (stack_top_character != '(' && stack_top_character != '\0') {
                    printf("%c ", pop());
                    stack_top_character = top();
                }
                // 執行完以後壓入當前的符號
                push(current_character);
                break;
        }
    }
    // 最後執行清空全部棧
    while (top() != '\0') {
        printf("%c ", pop());
    }

    return 0;
}

// 入棧操做定義
void push(char op) {
    symbol_stack[++sp] = op;
}
// 出棧操做定義
char pop(void) {
    return symbol_stack[sp--];
}
// 查看操做定義
char top(void) {
    return sp == -1 ? '\0' : symbol_stack[sp];
}

看我代碼是否是感受就一目瞭然了呢?若是仍是不懂的話,那是個人鍋-^-(面壁思過).其實本身再紙上演算一下就好啦~接下來就是使用轉換出的後綴表達式構建表達式樹了,其實也是用棧的方式啦~

相關文章
相關標籤/搜索