1. 程式人生 > >棧的操作和c語言實現算術表示式求值

棧的操作和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;
}

最後編譯結果: