1. 程式人生 > >棧及其應用(表示式求值、括號匹配)

棧及其應用(表示式求值、括號匹配)

一、棧(stack)

1、棧的特點

  棧(Stack)是一種線性儲存結構,它具有如下特點:

【Note】:
(1)棧中的資料元素遵守”先進後出”(First In Last Out)的原則,簡稱FILO結構。
(2)限定只能在棧頂進行插入和刪除操作。

  棧在計算機中應用相當廣泛,包括遞迴的呼叫和返回、二叉樹和森林的遍歷、呼叫子程式及從子程式返回、表示式的轉換和求值、CPU的中斷處理等等。

2、棧的相關概念

(1)棧頂(top())與棧底:允許元素插入與刪除的一端稱為棧頂,另一端稱為棧底。
(2)壓棧( push() ):棧的插入操作,叫做進棧,也稱壓棧、入棧。
(3)彈棧( pop() ):棧的刪除操作,也叫做出棧。
這裡寫圖片描述


這裡寫圖片描述

  棧的實現可以使用陣列和連結串列。

二、棧的應用之表示式求值

1、逆波蘭表示式簡介

  假定給定一個只 包含 加、減、乘、除,和括號的算術表示式,你怎麼編寫程式計算出其結果。問題是:在表示式中,括號,以及括號的多層巢狀 的使用,運算子的優先順序不同等因素,使得一個算術表示式在計算時,運算順序往往因表示式的內容而定,不具規律性。 這樣很難編寫出統一的計算指令。
  使用逆波蘭演算法可以輕鬆解決。他的核心思想是將普通的中綴表示式轉換為字尾表示式。

  轉換為字尾表示式的好處是:

  • 去除原來表示式中的括號,因為括號只指示運算順序,不是完成計算必須的元素。

  • 使得運算順序有規律可尋,計算機能編寫出程式碼完成計算。雖然後綴表示式不利於人閱讀,但利於計算機處理。

2、將中綴表示式轉換成字尾式(逆波蘭表示式)

1. 從左到右讀進中序表示式的每個字元。

2. 如果讀到的字元為運算元,則直接輸出到字尾表示式中。

3. 如果遇到“)”,則彈出棧內的運算子,直到彈出到一個“(”,兩者相互抵消。

4. “(”的優先順序在棧內比任何運算子都小,任何運算子都可以壓過它,不過在棧外卻是優先順序最高者。

5. 當運算子準備進入棧內時,必須和棧頂的運算子比較,如果外面的運算子優先順序高於棧頂的運算子的優先順序,則壓棧;如果優先順序低於或等於棧頂的運算子的優先順序,則彈棧。直到棧頂的運算子的優先順序低於外面的運算子優先順序或者棧為空時,再把外面的運算子壓棧。

6. 中綴表示式讀完後,如果運算子棧不為空,則將其內的運算子逐一彈出,輸出到字尾表示式中。

//比較lhs的優先順序是否不高於rhs,rhs表示棧頂的符號
bool priority(const char &lhs, const char &rhs)
{
    if (rhs == '(')//左括號在棧外優先順序最高
        return false;
    if (lhs == '+' || lhs == '-')
        return true;
    if ((lhs == '*' || lhs == '/') && (rhs == '*' || rhs == '/'))
        return true;
    return false;
}
//將中綴表示式轉換成字尾式(逆波蘭表示式)
string exchange(const string &str)
{
    vector<char> vec;
    string res;
    stack<char> st;//操作符堆疊
    for (int i = 0; i < str.size(); ++i)
    {
        if (isdigit(str[i]))//如果是數字,直接輸出到後序表示式中
        {
            vec.push_back(str[i]);
        }
        else//如果是符號,需要與棧頂的元素進行比較
        {
            if (st.empty() || str[i] == '(')//小括號在棧外優先順序最高,直接壓棧
                st.push(str[i]);
            else
            {
                if (str[i] == ')')//遇到右括號,則彈棧,直到遇到左括號,兩者相互抵消
                {
                    while (!st.empty() && st.top() != '(')
                    {
                        vec.push_back(st.top());
                        st.pop();
                    }
                    st.pop();
                }
                else//遇到的是其他操作符
                {
                    if (priority(str[i], st.top()))//優先順序比棧頂元素低
                    {
                        while (!st.empty())
                        {
                            vec.push_back(st.top());
                            st.pop();
                        }
                        st.push(str[i]);
                    }
                    else//優先順序比棧頂元素高,壓棧
                    {
                        st.push(str[i]);
                    }
                }
            }
        }
    }
    while (!st.empty())//如果堆疊不為空,則將其中的元素全部彈出
    {
        vec.push_back(st.top());
        st.pop();
    }
    for (auto v : vec)
        res += v;
    return res;
}

3、字尾表示式求值

  字尾表示式具有和字首表示式類似的好處,沒有優先順序的問題。

1. 直接讀取表示式,如果遇到數字就壓棧。

2. 如果遇到運算子,就彈出兩個數進行運算,隨後再將運算結果壓棧。

//定義四則運算
int operate(int first, int second, char op)
{
    int res = 0;
    switch (op)
    {
        case '+':
            res = first + second;
            break;
        case '-':
            res = first - second;
            break;
        case '*':
            res = first*second;
            break;
        case '/':
            res = first / second;
            break;
        default:
            break;
    }
    return res;
}

int calculate(string input)
{
    stack<int> st;//運算元堆疊
    for (auto &s : input)
    {
        if (isdigit(s))//如果是數字就壓棧
        {
            st.push(s - '0');
        }
        else//遇到字元就彈出兩個運算元進行運算
        {
            int a = st.top();
            st.pop();
            int b = st.top();
            st.pop();
            st.push(operate(b, a, s));
        }
    }
    return st.empty() ? 0 : st.top();//最後的結果為棧頂元素
}
int main(int argc, char const *argv[])
{
    string str = "1+(3+4)*5-2";
    cout << exchange(str) << endl;
    cout << calculate(exchange(str)) << endl;
    system("pause");
    return 0;
}

三、棧的應用之括號匹配

  ​括號匹配在很多字串處理的場景中時常被用到,諸如各大IDE括號不匹配的錯誤提示,編譯器編譯時檢查應該成對出現的括號是否符合要求等,在這裡我們就直接使用一種比較常規,但效率不差的方法去解決括號匹配的問題就行了。

  Description: 給定一個字串,其中的字元只包含三種括號:花括號{ }、中括號[ ]、圓括號( ),即它僅由 “( ) [ ] { }” 這六個字元組成。設計演算法,判斷該字串是否有效,即字串中括號是否匹配。括號匹配要求括號必須以正確的順序配對,如“{ [ ] ( ) }” 或 “[ ( { } [ ] ) ]” 等為正確的格式,而 “[ ( ] )” 或 “{ [ ( ) }” 或 “( { } ] )” 均為不正確的格式。

  ​定義一個棧,遇到左括號就壓棧,當遇到一個右括號時,首先棧頂元素是否是左括號,如果是的則對棧進行pop操作表明頂部元素已被匹配,否則為不匹配情況,直接返回false。當整個字串遍歷結束,我們就可以通過判斷該棧是否為空來判斷整個字串中的符號是否匹配。

  具體程式碼如下:

#include <iostream>
#include <stack>
#include <string>
using namespace std;
int main(int argc, char const *argv[])
{
    string str;
    getline(cin,str);
    stack<char> st;
    int l = str.size();
    int flag = 1;
    for (int i=0 ; i<l ; ++i)
    {
        switch(str[i])
        {
            case '(' : st.push(str[i]) ; break;
            case '[' : st.push(str[i]) ; break;
            case '{' : st.push(str[i]) ; break;
            case ')' : 
                if(st.top() == '(')
                    st.pop();
                else
                    flag = 0; 
                break;
            case ']' : 
                if(st.top() == '[')
                    st.pop();
                else
                    flag = 0; 
                break;
            case '}' : 
                if(st.top() == '{')
                    st.pop();
                else
                    flag = 0; 
                break;
        }
    }
    if(!flag)
    {
        cout << "no" << endl;
    }
    else
    {
        if(!st.empty())
        {
            cout << "no" << endl;
        }
        else
        {
            cout << "yes" << endl;
        }
    }
    system("pause");
    return 0;
}