1. 程式人生 > >棧的C++實現及其應用

棧的C++實現及其應用

棧是一種先進後出的資料結構,是一種功能受限的線性表。因為這世間存在這後進先出的計算順序,為簡化計算的過程,棧得以應用。好比裝水的桶,好比裝子彈的彈夾。

棧的C++實現

本文主要是程式設計實現棧,使用的是順序儲存結構(動態陣列)來實現棧,需要注意的是當記憶體空間不夠用,即棧滿的時候,應該將重新開闢新的空間(大小為源空間大小+STACK_INCREMENT),然後將資料複製到新空間,在重新釋放舊空間。
下面給出標頭檔案定義(Stack.h):

#define STACK_INIT_SIZE 100 ///the size of stack after initialized
#define
STACK_INCREMENT 10 ///the increment of stack growth
class Stack{ private: int *base;///using array to store element int top;///use index(index of base) to find the top element of stack int stacksize; ///will be called when push a element to a full stack(top = stacksize) bool ReallocateMemory(); public
: ///apply for memory and form a empty stack Stack(); ///destroy the stack s and s won't exist anymore void DestroyStack(); ///clear the stack s and s become a empty stack void ClearStack(); ///get top element of stack while s is not empty otherwise return false bool GetTop(int &e); ///
insert a element to the stack
bool Push(int e); ///remove the top element and return it bool Pop(int &e); ///get stack size int GetSize(); };

不難看出,棧的函式比較少,可以很容易寫出其函式定義(Stack.cpp):

#include "Stack.h"
///apply for memory and form a empty stack
Stack::Stack(){
    base = new int[STACK_INIT_SIZE];
    top = 0;
    stacksize = STACK_INIT_SIZE;
}
///will be called when push a element to a full stack(top = stacksize)
bool Stack::ReallocateMemory(){
    int * tmp = new int[stacksize+STACK_INCREMENT];
    for(int i = 0; i < top; ++i){
        tmp[i] = base[i];
    }
    delete [] base;
    base = tmp;
    stacksize +=STACK_INCREMENT;
    if(base!=nullptr)
        return true;
    return false;
}
///destroy the stack s and s won't exist anymore
void Stack::DestroyStack()
{
    delete [] base;
    base = nullptr;
    top = 0;
    stacksize = 0;
}
///clear the stack s and s become a empty stack
inline void Stack::ClearStack(){
    top = 0;
}
///get top element of stack while s is not empty otherwise return false
bool Stack::GetTop(int &e){
    if(top==0)
        return false;
    e=base[top-1];
    return true;
}
///insert a element to the stack
bool Stack::Push(int e){
    bool flag;
    if(top==stacksize)
        flag = ReallocateMemory();
    base[top++]=e;
    return flag;
}
///remove the top element and return it
bool Stack::Pop(int &e){
    if(top==0)
        return false;
    e = base[--top];
    return true;
}
///get stack size
inline int Stack::GetSize(){
    return top;
}

這裡top是動態陣列的下標,指向棧頂元素的下一個位置,當top=0時,表示棧空,當top=stacksize時,表示棧滿。
測試
下面給出測試程式碼(main.cpp):

#include <iostream>
using namespace std;
#include "Stack.h"

int main()
{
    Stack s;
    int e;
    for(int i = 0;i<110;++i)
        s.Push(i);
    for(int i = 0;i<110;++i)
    {
        if(s.Pop(e))
            cout<<e<<' ';
    }
    cout<<endl;
    return 0;
}

上述程式碼的思路是依次向棧壓入0/1/2/3/4…109,那麼輸出應該是109/108…0。
測試結果:
這裡寫圖片描述

棧的應用

1、表示式求值
(1)我們知道,對於中綴表示式而言,不好判斷計算順序,於是波蘭學者提出了字尾表示式的概念,而後綴表示式非常適合程式的處理。如果不清楚這個演算法的話,可以參考博文:字尾表示式
(2)對演算法有所瞭解以後,我們可以發現,要想計算表示式的值,需要先將中綴表示式轉換成字尾表示式(步驟一),然後利用字尾表示式進行求值(步驟二);而將中綴表示式轉換成字尾表示式的關鍵在於算符優先表的構建;上述的兩個過程都涉及到棧的應用;
這裡寫圖片描述
(3)步驟一程式設計實現:

///算符優先表,1表示>,0表示=,-1表示<,2表示不存在優先關係
int opTable[7][7]={
1,1,-1,-1,-1,1,1,
1,1,-1,-1,-1,1,1,
1,1,1,1,-1,1,1,
1,1,1,1,-1,1,1,
-1,-1,-1,-1,-1,0,2,
1,1,1,1,2,1,1,
-1,-1,-1,-1,-1,2,0
};
char op[7]={'+','-','*','/','(',')','#'};
int indexOf(char c){
    int j;
    for(j = 0;j<7;++j){
            if(op[j]==c)
                break;
    }
    return j;
}
bool convert(char *mExp,char *bExp){
/**<
1、建立符號棧*/
Stack opStack;

/**   2、順序掃描中序表示式
a) 是數字, 直接輸出
b) 是運算子*/
int n = strlen(mExp);
int k = 0;
for(int i=0;i<n;++i){
    char c = mExp[i];
    if(isdigit(c)){
        bExp[k++]=c;
    }
    else{
        int j = indexOf(c);
        ///不是合法算符
        if(j==7)
            return false;
        ///i : “(” 直接入棧
        if(c=='('){
            ///由於我的stack是int型,這裡會有型別轉換
            int x = (int)c;
            opStack.Push(x);
        }
        ///ii : “)” 將符號棧中的元素依次出棧並輸出, 直到 “(“, “(“只出棧, 不輸出
        else if(c==')'){
           char ch;
           int xx = ch;
           opStack.GetTop(xx);
           ch = (char)xx;
           while(ch!='('){
                opStack.Pop(xx);
                bExp[k++]=(char)xx;
                opStack.GetTop(xx);
                ch = (char)xx;
           }
           opStack.Pop(xx);
        }
        ///iii: 其他符號, 將符號棧中的元素依次出棧並輸出,
        ///直到遇到比當前符號優先順序更低的符號或者”(“。 將當前符號入棧。
        else{
            if(opStack.GetSize()!=0){
                char ch;
                int x = (int)ch;
                opStack.GetTop(x);
                ch = (char)x;
                int column = indexOf(ch);
                int tuplE = indexOf(c);
                while(opTable[column][tuplE]!=-1 && ch != '('){
                    bExp[k++]=ch;
                    opStack.Pop(x);
                    opStack.GetTop(x);
                    ch=(char)x;
                    column=indexOf(ch);
                }
            }
            opStack.Push(c);
        }
    }
}
///掃描完後, 將棧中剩餘符號依次輸出
while(opStack.GetSize()!=0){
    int x;
    opStack.Pop(x);
    bExp[k++]=char(x);
}
bExp[k]='\0';
return true;
}

需要注意的是,我這裡用的棧是自己寫的棧,棧的元素是整型,而處理表達式用的是字元型,在程式執行過程中存在型別轉換。因此,這是容易出錯的。比較好的做法是在編寫棧的實現的時候使用泛型程式設計,使得棧的資料型別可變,但是對每一種資料型別的操作都是一樣的。
(4)步驟二程式設計實現:

/** 建立一個棧S 。從左到右讀表示式,
如果讀到運算元就將它壓入棧S中,如果
讀到n元運算子(即需要引數個數為n的運算子)
則取出由棧頂向下的n項按操作符運算,
再將運算的結果代替原棧頂的n項,
壓入棧S中。如果字尾表示式未讀完,
則重複上面過程,最後輸出棧頂的數值則為結束。
 */
int calculate(char *bExp){
    Stack s;
    int n = strlen(bExp);
    char c;
    int x,y,op1,op2;
    int result;
    for(int i=0;i<n;++i){
        c = bExp[i];
        x = (int)c;
        if(isdigit(c)){
            s.Push(x-'0');
        }
        else{
            if(c=='+'){
                s.Pop(op2);
                s.Pop(op1);
                result = op1+op2;
                //cout<<op1<<c<<op2<<'='<<result<<endl;
                s.Push(result);
            }
            if(c=='-'){
                s.Pop(op2);
                s.Pop(op1);
                result = op1-op2;
                //cout<<op1<<c<<op2<<'='<<result<<endl;
                s.Push(result);
            }
            if(c=='*'){
                s.Pop(op2);
                s.Pop(op1);
                result = op1*op2;
                //cout<<op1<<c<<op2<<'='<<result<<endl;
                s.Push(result);
            }
            if(c=='/'){
                s.Pop(op2);
                s.Pop(op1);
                result = op1/op2;
                //cout<<op1<<c<<op2<<'='<<result<<endl;
                s.Push(result);
            }
        }

    }
    s.Pop(x);
    return x;
}

(4)測試

int main()
{
    char mExp[100],bExp[100];
    cout<<"請輸入中綴表示式:"<<endl;
    cin>>mExp;
    if(convert(mExp,bExp)){
         cout<<"轉換得到的字尾表示式:"<<endl;
         for(int i=0;i<strlen(bExp);++i)
            cout<<bExp[i]<<' ';
        cout<<endl;
    }
    cout<<"根據字尾表示式計算得到值為:"<<calculate(bExp)<<endl;
    return 0;
}

執行結果:
這裡寫圖片描述
2、利用兩個棧實現一個佇列
演算法原理:我們知道佇列是先進先出的線性表,而棧是先進後出的線性表。那麼兩次先進後出,就會變成先進先出,於是程式設計的關鍵在於保證兩次先進後出的順序,比如對於輸入1,2,3,4來說,到達第一個棧後輸出變成4/3/2/1;而到達第二個棧後輸出就變成1/2/3/4了,這就實現了佇列的功能。設s1是輸入的第一個棧,s2是第二個棧,那麼,當s2不為空時,不可以將s1的資料匯入s2,否則將打破兩次先進後出的順序。

#include "Stack.h"
///利用兩個棧實現一個佇列的功能
class Queue{
private:
    Stack s1,s2;///s1是輸入棧、s2是輸出棧
public:
    Queue(){}
    bool push(int e){
        s1.Push(e);
    }
    bool pop(int &e){
        if(s2.GetSize()!=0){
            s2.Pop(e);
            return true;
        }
        else if(s1.GetSize()!=0){
            int x;
            while(s1.GetSize()!=0){
                s1.Pop(x);
                s2.Push(x);
            }
            s2.Pop(e);
            return true;
        }
        else{
            return false;
        }
    }
};

我這裡的棧都是可以無限增長的,所以不用判斷s1是否為滿。否則需要判斷s1是否為滿。
測試:

Queue q;
    q.push(1);
    q.push(2);
    int e;
    q.pop(e);
    cout<<e<<' ';
    q.push(3);
    q.push(4);
    q.pop(e);
    cout<<e<<' ';
    q.pop(e);
    cout<<e<<' ';
    q.pop(e);
    cout<<e<<' ';

執行結果:
這裡寫圖片描述