棧的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<<' ';
執行結果: