棧的操作和c語言實現算術表示式求值
</pre><p><span style="font-size:18px;">棧是一種特殊的線性表,按照“後進先出”的原則處理資料。</span></p><p><span style="font-size:18px;">棧的基本操作有兩種:一種是入棧(Push),即把資料儲存到棧頂,注意在入棧前應該先檢查棧是否已滿,如滿則不能入棧操作,未滿則修改棧頂指標,使其向上移動一個元素的位置,然後將資料儲存到棧頂指標所指的位置。 一種是出棧(Pop),即把棧頂資料彈出,注意在出棧之前檢查棧是否為空,如果棧為空沒有資料則提示不能進行出棧操作,若不空,則修改棧頂指標,使其指向棧中的下一個元素。</span></p><p><span style="font-size:18px;">1.編寫操作棧的標頭檔案 </span></p><p><pre name="code" class="cpp">typedef struct stack { DATA data[SIZE+1]; //資料元素 top為o表示棧為空 int top; //定義棧頂 } seqStack;
其中DATA定義棧中元素的資料型別,SIZE表示棧的最大容量,SIZE+1表示棧的資料從下標為1開始,當top為0時,表示棧為空。
2.初始化棧
seqStack *seqStackInit()
{
seqStack *p;
if(p=(seqStack *)malloc(sizeof(seqStack))) //申請記憶體
{
p->top=0;
return p;
}
return NULL; //申請記憶體失敗
}
初始化棧需要做兩件事,首先按照SIZE指定的大小申請一片記憶體空間,儲存棧中資料,然後設定棧頂指標的值為0,表示空棧。
3.判斷棧的狀態
int seqStackIsempty(seqStack *s) //棧是否為空 { return(s->top==0); } int seqStackIsfull(seqStack *s) //棧是否滿 { return(s->top==SIZE); }
對棧操作前先判斷棧的狀態,入棧前棧是否滿,出棧前棧是否為空。
4.清空棧 釋放棧所佔的記憶體空間
void seqStackClear(seqStack *s)
{
s->top=0; //清空棧
}
void seqStackfree(seqStack *s) //釋放棧的空間
{
if(s)
free(s);
}
清空棧時,即把棧頂指標top置0,釋放棧時,因為棧是用malloc函式分配的,所以不使用時用free函式釋放分配的記憶體空間。
5.入棧操作
int seqStackPush(seqStack *s,DATA data) { if((s->top+1)>SIZE) //棧的上一個位置超過最大位置 { printf("棧溢位\n"); return 0; } s->data[++s->top]=data; //修改棧頂指標,把元素入棧 return 1; }
入棧時,判斷若top已經滿了(s->top==size),則top+1就超出棧的最大記憶體空間,提示棧溢位,做出錯處理。若沒有溢位,則棧頂指標向上移動一位,把資料放入其中,即入棧。
6.出棧操作
int seqStackPop(seqStack *s) //出棧
{
if(s->top==0)
{
printf("棧為空:\n");
exit(0);
}
return (s->data[s->top--]); //從棧頂彈出元素
}
出棧與入棧相反,從棧頂彈出一個數據元素。先判斷top是否為0,若為0,則表示空棧,提示出錯,若不為0,修改棧頂指標減一,即往下移動一位,返回此時棧頂指標所指位置,原來棧頂元素彈出,即出棧。
7.獲取棧頂元素
DATA seqStackPeek(seqStack *s) //讀出棧頂元素
{
if(s->top==0)
{
printf("棧為空");
exit(0);
}
return (s->data[s->top]); //返回棧頂元素
}
在出棧操作中,是把原來的棧頂元素彈出(拋棄了),即出棧後棧頂元素就不存在了,若只是讀取一下棧頂的元素而不刪除,就不需要將top指標往下移動,不修改指標位置,這樣返回棧頂後棧頂資料元素還在。
以上為棧的基本操作,下面舉一個實用例子深入理解棧的相關操作。
棧的實際運用:算術表示式求值
在計算機中輸入一個算式如:(2+3)*5 容易知道先計算2+3=5,再將5*5=25,,但計算機在讀入算式時的具體過程是怎樣的?
計算機先逐步掃描:先是(括號,這時不知道右括號在哪裡出現,所以先儲存起來,然後是2,也不知道該字元下一位是什麼,也先儲存,然後是+,這時知道是加上一個數,但不知道加什麼,先儲存起來,然後是3,這時知道是2+3,直接相加嗎?不能,如果3後面是*,或者/的高優先順序話就先計算,所以也先儲存,然後是),這時括號內的2+3就可以取出運算了,得出結果5後再儲存起來(由此可以看出加減的優先順序大於()的優先順序),然後是*,儲存起來,然後是5,儲存起來,然後是=號,表示整個算式結束了,將2+3得到的5與5相乘,輸出最終結果。
由上分析可知,程式需要儲存運算元,儲存運算子(+,-,*,/,=,(,)),還需要確定優先順序(乘除大於加減,加減大於括號,括號可以改變優先順序的順序,相同優先順序從左往右計算)。
為了簡化程式,只考慮加減乘除四則運算。
首先呼叫庫函式:
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
#define SIZE 50
typedef int DATA; //定義棧元素型別
#include "biaodashi.h"
第六行引用最開始的棧的基本操作程式為標頭檔案,
具體函式模組分析:
1.每次讀取檢查字元是否為運算子
int isOperator(char c) //檢查字元是否為運算子
{
switch(c)
{
case '+':
case '-':
case '*':
case '/':
case '(':
case ')':
case '=':
return 1;
break;
default:
return 0;
break; //字元c是運算子則返回1,,否則返回0
}
}
定義isOperator函式,如果字元為+,-,*,/,(,),=,運算子,則返回1,否則返回0,。
2.比較運算子的優先順序:
注:在計算表示式時,為了方便判斷增設“=”為表示式定界符,即以=表示開始,=表示結束
int PRI(char oper1,char oper2) //判斷兩個運算子的優先順序
//如果oper1>oper2返回1 如果oper1<oper2返回-1 如果oper1,,oper2是左右符號返回0
{
int pri;
switch(oper2) //判斷優先順序
{
case '+':
case '-':
if(oper1=='('||oper1=='=') //為左括號
pri=-1; //oper1<oper2
else
pri=1; //oper1>oper2
break;
case '*':
case '/':
if(oper1=='*'||oper1=='/'||oper1==')')
pri=1; //oper1>oper2
else
pri=-1; //oper1<oper2
break;
case '(':
if(oper1==')') //右括號右側不能馬上出現左括號
{
printf("語法錯誤\n");
exit(0);
} else
pri=-1; //oper1<oper2
break;
case ')':
if(oper1=='(')
pri=0;
else if(oper1=='=')
{
printf("括號不匹配\n");
exit(0);
}else
pri=1;
break;
case '=':
if(oper1=='(')
{
printf("括號不匹配\n");
exit(0);
}else if(oper1=='=')
pri=0; //等號匹配,返回0
else
pri=1; //oper1>oper2
break;
}
return pri;
}
在該函式中對oper1和oper2兩個運算子比較,34到40行表示:oper2為+或-號,若oper1為(或=號,oper1<oper2,則pri=-1,否則oper1>oper2,pri=1。41到47行同理。48到55行表示:oper2等於(時,若oper1等於),即)(,這樣右括號後直接左括號是不對的,oper2等於(,若oper1不為),則oper1<oper2,pri=1。56到65表示:oper2等於),若oper1等於(,則括號匹配,pri等於0,如果oper1等於=,即:=),括號不匹配。第66到75行表示:oper2為=號,若oper1為(,即:(=,括號不匹配,如果oper1等於=,表示結束算式,如果oper1為其他,則oper1>oper2,pri=1。 最後返回pri。
3.對於優先順序高的運算子,需要將運算子兩側的資料進行計算,
int Calc(int a,int oper,int b) //計算兩個運算元的結果
{
switch(oper) //根據運算子計算
{
case '+':return a+b;
case '-':return a-b;
case '*':return a*b;
case '/':
if(b!=0)
return a/b;
else
{
printf("除數不能為0!\n");
exit(0);
}
}
}
當為除數時,注意考慮除數不能為0,
4.寫好以上函式部分,就可以開始計算表示式的值:
int CalcExp(char exp[]) //表示式計算函式
{
seqStack *StackOper,*StackData; //指向兩個棧的指標變數,一個操作符,一個運算數
int i=0,flag=0; //flag作為標誌,用來處理多位數
DATA a,b,c,q,x,t,oper;
StackOper=seqStackInit();
StackData=seqStackInit(); //初始化兩個棧
q=0; //變數q儲存多位數的操作
x='=';
seqStackPush(StackOper,x); //首先把等號=進入操作棧
x=seqStackPeek(StackOper); //獲取操作棧的首元素
c=exp[i++];
while(c!='='||x!='=') //迴圈處理表達式中的每一個字元
{
if(isOperator(c)) //如果是運算子
{
if(flag){
seqStackPush(StackData,q); //表示式入棧
q=0; //運算元清零
flag=0; //標誌清零,表示運算元已經入棧
}
switch(PRI(x,c)) //判斷運算子優先順序
{
case -1:
seqStackPush(StackOper,c); //運算子進棧
c=exp[i++];
break;
case 0:
c=seqStackPop(StackOper); //運算子括號,等號出棧,被拋棄
c=exp[i++]; //取下一個 字元
break;
case 1:
oper=seqStackPop(StackOper); //運算子出棧
b=seqStackPop(StackData);
a=seqStackPop(StackData); //兩個操作數出棧
t=Calc(a,oper,b); //計算結果
seqStackPush(StackData,t); //將運算結果入棧
break;
}
}else if(c>='0'&&c<='9') //如果輸出的字元在0到9之間
{
c-='0'; //把字元轉換成數字
q=q*10+c; //多位數的進位處理
c=exp[i++]; //取出下一位字元
flag=1; //設定標誌,表示運算元未入棧
}
else {
printf("輸入錯誤\n");
getch();
exit(0);
}
x=seqStackPeek(StackOper); //獲取棧頂操作符
}
q=seqStackPop(StackData);
seqStackfree(StackOper);
seqStackfree(StackData); //釋放記憶體佔用空間
return q; //出棧,返回結果
}
該部分註解:
首先是以一個字串exp為引數,返回一個整型數(即表示式的結果),一個標誌變數flag,作用是處理當運算元是多位數時處理多位數的情況(程式碼一次只能從表示式中獲取一個字元),當運算元是多位數就把每一位獲取後再入棧。flag為0表示沒有運算元入棧,為1表示有運算元需要入棧。
然後將變數x為“=”,將該符號作為表示式的第一個運算子入棧,然後迴圈諸葛處理表達式字串中的每一個字元,直到遇到表示式的結束字元“=”為止。
5.主函式
int main()
{
int c;
char exp[80];
printf("請輸入需要計算的表示式(以=結束):\n");
scanf("%s",exp);
printf("%s%d\n",exp,CalcExp(exp));
getch();
return 0;
}
最後編譯結果: