1. 程式人生 > >8.棧的應用-四則運算算術表示式求解(後序表示式法)

8.棧的應用-四則運算算術表示式求解(後序表示式法)

1.理論

在上節中看到使用“算符優先法”首先要自己去推導整個算符優先順序表,然後計算機按照算符優先順序表來出棧和進棧直到完成整個運算。這裡推導算符優先順序表是一個關鍵,但是這樣比較繁瑣,有沒有更為直觀的演算法呢。
觀察如下算術表示式:
1+2*3-2/3

表達成二叉樹形式如下


上述的算術表示式就是上述的二叉樹中序遍歷的結果。下面我們看看實際從左到右的計算過程:

先計算2*3再計算1-2*3再計算2/3最後計算1+2*3-2/3

即按照後序遍歷,只需要一直遍歷,遇到算符時就計算即可

顯然我們想到只要把算術表示式轉換成後序表示式,然後遍歷求值即可,這樣算術表示式求值即轉換成求後序表示式。

觀察二叉樹發現離最深層的數字越近的算符的優先順序越高,相同的或相同級別算符越先出現離數字越近,每一個算符父節點必然優先順序低於或等於子節點的算符優先順序,同一算符子節點下的每個算符右子節點優先順序必然大於算符左子結點

那麼求後序表示式的過程如下:

(1)首先,需要分配1個棧和1個線性表,棧s用於臨時儲存運算子,此運算子在棧內遵循越往棧頂優先順序越高的原則;線性表l用於儲存轉換完成的後序表示式(逆波蘭式)

(2)從中綴式的算術表示式字串左端開始逐個讀取字元x,逐序進行如下步驟:

1.若x是運算元,則分析出完整的運算數,將x直接插入線性表l末端;

2.若x是運算子,則分情況討論:

a.若x是'(',則直接壓入棧s;

b.若x是')',則將距離棧s棧頂的最近的'('之間的運算子,逐個出棧,插入線性表l末端,此時拋棄'(';

c.若x是除'('和')'外的運算子,則再分如下情況討論:

若當前棧s的棧頂元素為'(',則將x直接壓入棧s;

若當前棧s的棧頂元素不為'(',則將x與棧s的棧頂元素比較:

棧s棧頂運算子優先順序小於x的優先順序,則將x直接壓入棧s;

否者,將棧s的棧頂運算子彈出,插入線性表l末端,直到棧s的棧頂運算子優先級別低於(不包括等於)x的優先順序,或輸入的運算子為'(',此時再則將x壓入棧s。

注意這裡的算符優先順序就是指實際計算過程的算符優先順序:+、-<*、/<(、)

後序表示式求算術表示式值的過程就很簡單了,如下:

構建一個用於儲存運算元的臨時棧s

從前往後遍歷儲存有後序表示式的線性表,遇到運算元則壓入棧s,遇到運算子則s彈出兩個運算元,用運算子計算結果再壓入s,如此迴圈直到計算完畢,最後棧中剩餘一個元素即為最終結果。

要注意算術表示式非法的判斷:

1.輸入的只能為數字和算符

2.遍歷完線性表計算後棧中只剩下一個元素

2.實際程式

算符優先順序比較

/**
 *功能:	判斷兩個操作符的優先順序
 *引數:	operator1--操作符1
 *		operator2--操作符2
 *返回:	操作符1優先順序大於操作符2	--LEVEL_BIGGER
 *		操作符1優先順序小於操作符2	--LEVEL_SMALLER
 *		操作符1優先順序等於操作符2	--LEVEL_SAME
 *		操作符1操作符2對比不合法	--LEVEL_INVALID
 *其他:	2014/04/18 By Jim Wen Ver1.0
 *說明:	這裡'+'和'-','*'和'/'在實際四則運算時的優先順序
 *		是相同的,所以這裡的優先順序判定時設定兩個操作符級別
 *		列表,一個操作符列表是把另一個操作符級別列表中的相
 *		同級別的操作符的順序做了顛倒
**/
LEVEL_TYPE CompareLevel(char operator1, char operator2)
{
	char	levelTable1[] = {'+', '-', '*', '/', '(', ')'};
	char	levelTable2[] = {'-', '+', '/', '*', '(', ')'};
	int		nTable1Index1, nTable1Index2;
	int		nTable2Index1, nTable2Index2;

	//判斷兩個操作符在兩個優先順序表中的位置
	nTable1Index1 = nTable1Index2 = -1;
	nTable2Index1 = nTable2Index2 = -1;

	while (levelTable1[++nTable1Index1] != operator1);
	while (levelTable1[++nTable1Index2] != operator2);
	while (levelTable2[++nTable2Index1] != operator1);
	while (levelTable2[++nTable2Index2] != operator2);

	//判斷兩個操作符的優先順序關係
	if (nTable1Index1-nTable1Index2<0 && nTable2Index1-nTable2Index2<0)
	{
		return LEVEL_SMALLER;
	}
	else if(nTable1Index1-nTable1Index2>0 && nTable2Index1-nTable2Index2>0)
	{
		return LEVEL_BIGGER;
	}
	else
	{
		return LEVEL_SAME;
	}
}

中序表示式轉換成後序表示式

/**
 *功能:	將中序表示式轉換為後序表示式(逆波蘭式)
 *引數:	pExpression		--字串形式的表示式
 *		pPostArray		--由前到後儲存後序表示式的線性表
 *返回:	表示式不合法		--假(轉換不成功)
 *		表示式合法		--真(轉換成功)
 *其他:	2014/04/18 By Jim Wen Ver1.0
**/
JWArray_BOOL MidToPost(char *pExpression, JWArray *pPostArray)
{
	JWArray			*pStackOperator;
	char			*pCur;
	JWArrayElem		eTop;
	JWArray_BOOL	bResult;

	pCur	= pExpression;
	bResult	= JWARRAY_TRUE;

	//建立算符棧
	pStackOperator	= JWArrayCreate(10, 10);

	//遍歷算術表示式字串
	while(pCur[0] != '\0')
	{
		if (INVALID_TYPE == JudgeType(pCur[0]))//輸入不合法
		{
			bResult	= JWARRAY_FALSE;
			break;
		}
		else if (NUMBER == JudgeType(pCur[0]))//運算元直接輸入到線性表
		{
			eTop.elemType			= NUMBER;
			eTop.elemValue.dbNum	= atof(pCur);
			JWArrayPush(pPostArray, eTop);
		}
		else//輸入算符
		{
			if (pCur[0] == '(')//輸入'('
			{
				eTop.elemType				= OPERATOR;
				eTop.elemValue.cOperator	= pCur[0];
				JWArrayPush(pStackOperator, eTop);
			}
			else if (pCur[0] == ')')//輸入')'
			{
				//一直出棧壓入線性表直到'('
				while (JWARRAY_TRUE == JWArrayGetTop(pStackOperator, &eTop) &&
					   eTop.elemValue.cOperator != '(')
				{
					JWArrayPop(pStackOperator, &eTop);
					JWArrayPush(pPostArray, eTop);
				}

				if (JWARRAY_TRUE == JWArrayGetTop(pStackOperator, &eTop) &&
					eTop.elemValue.cOperator == '(')
				{
					JWArrayPop(pStackOperator, NULL);//直接彈出'('
				}
				else
				{
					bResult	= JWARRAY_FALSE;
					break;
				}
			}
			else if (JWARRAY_TRUE == JWArrayGetTop(pStackOperator, &eTop) &&
					 eTop.elemValue.cOperator == '(')//棧頂元素為'('則當前的算符壓入算符棧
			{
				eTop.elemType				= OPERATOR;
				eTop.elemValue.cOperator	= pCur[0];
				JWArrayPush(pStackOperator, eTop);
			}
			else if (JWARRAY_FALSE == JWArrayGetTop(pStackOperator, &eTop) ||
					 LEVEL_SMALLER == CompareLevel(eTop.elemValue.cOperator, pCur[0]))
			{
				//棧為空或棧頂算符的優先順序小於當前輸入的算符(此時當前算符不等於()),則將當前的算符壓入算符棧
				eTop.elemType				= OPERATOR;
				eTop.elemValue.cOperator	= pCur[0];
				JWArrayPush(pStackOperator, eTop);
			}
			else
			{
				//對於棧不為空且棧頂算符的優先順序大於等於當前輸入的算符(此時當前算符不等於()),
				//一直出棧壓入線性表直到不滿足這個條件
				while(JWARRAY_TRUE  == JWArrayGetTop(pStackOperator, &eTop) &&
					  eTop.elemValue.cOperator != '(' &&
					  LEVEL_SMALLER != CompareLevel(eTop.elemValue.cOperator, pCur[0]))
				{
					JWArrayPop(pStackOperator, &eTop);
					JWArrayPush(pPostArray, eTop);
				}

				//當前算符入棧
				eTop.elemType				= OPERATOR;
				eTop.elemValue.cOperator	= pCur[0];
				JWArrayPush(pStackOperator, eTop);
			}
		}

		MoveToNext(&pCur);
	}

	//將剩下的算符棧中全部出棧加入到線性表中
	if (bResult	= JWARRAY_TRUE)
	{
		while (JWARRAY_FALSE == JWArrayIsEmpty(pStackOperator))
		{
			JWArrayPop(pStackOperator, &eTop);
			JWArrayPush(pPostArray, eTop);
		}
	}

	//銷燬算符棧
	JWArrayDestroy(pStackOperator);

	return bResult;
}

後序表示式求值

/**
 *功能:	輸入字串形式的表示式,計算表示式結果
 *引數:	pExpression		--字串形式的表示式
 *		pResult			--表示式求解結果
 *返回:	計算成功			--真(表示式合法)
 *		計算不成功		--假
 *其他:	2014/04/18 By Jim Wen Ver1.0
**/
JWArray_BOOL CalcExpression(char *pExpression, double *pResult)
{
	JWArray			*pPostArray, *pStackNum;
	JWArrayElem		eNum1, eNum2, eResult;
	double			dbResult;
	JWArray_BOOL	bResult;
	int				i;

	bResult	= JWARRAY_TRUE;

	//建立線性表和運算元棧
	pPostArray		= JWArrayCreate(10, 10);
	pStackNum		= JWArrayCreate(10, 10);

	//得到後序表示式
	if(JWARRAY_TRUE == MidToPost(pExpression, pPostArray))
	{
		//遍歷線性表
		for (i = 0; i < pPostArray->nLength; i++)
		{
			if (pPostArray->pElem[i].elemType == NUMBER)
			{
				JWArrayPush(pStackNum, pPostArray->pElem[i]);
			}
			else
			{
				if (JWARRAY_FALSE == JWArrayPop(pStackNum, &eNum2) ||
					JWARRAY_FALSE == JWArrayPop(pStackNum, &eNum1))
				{
					//出棧錯誤(輸入不合法時)
					bResult	= JWARRAY_FALSE;					
					break;
				}
				else
				{
					if (JWARRAY_FALSE == Calc(eNum1.elemValue.dbNum,
											  pPostArray->pElem[i].elemValue.cOperator,
											  eNum2.elemValue.dbNum,
											  &dbResult))
					{
						//計算錯誤(輸入不合法時)
						bResult	= JWARRAY_FALSE;					
						break;
					}
					else
					{
						eResult.elemType = NUMBER;
						eResult.elemValue.dbNum = dbResult;
						JWArrayPush(pStackNum, eResult);
					}
				}
			}
		}
	}
	else
	{
		bResult	= JWARRAY_FALSE;
	}

	if (bResult	== JWARRAY_TRUE)
	{
		//如果運算元棧長度不等於1則運算表示式不合法
		if (JWArrayGetLength(pStackNum) != 1)
		{
			bResult	= JWARRAY_FALSE;
		}
		else
		{
			JWArrayGetTop(pStackNum, &eResult);
			*pResult = eResult.elemValue.dbNum;
		}
	}

	//銷燬運算子棧和運算元棧
	JWArrayDestroy(pPostArray);
	JWArrayDestroy(pStackNum);

	return bResult;
}

3.程式說明

1.程式只是對常見的非法輸入做了校驗,可能有些非法輸入還是沒有考慮到

2.使用算符優先法需要最後一次性來完成出棧入棧操作最後完成整個計算,計算量比較大時會比較耗時;使用後序表示式法可以在輸入的過程中將中序表示式轉換成後序表示式,最後求解時只需要遍歷線性表即可,耗時相對較小。

3.執行結果如下


完整程式下載連結

相關推薦

8.應用-四則運算算術表示式求解(表示式)

1.理論 在上節中看到使用“算符優先法”首先要自己去推導整個算符優先順序表,然後計算機按照算符優先順序表來出棧和進棧直到完成整個運算。這裡推導算符優先順序表是一個關鍵,但是這樣比較繁瑣,有沒有更為直觀的演算法呢。 觀察如下算術表示式: 1+2*3-2/3表達成二叉樹形式如下

java 實現 中表示式表示式(逆波蘭式) 以及 表示式求值

第一次寫部落格,還是個學生,沒有什麼經驗,只是單純的記錄下自己實現一個問題的方法與思想,如有錯誤之處,請各路大神批評指出。 大概思想:跟參考部落格的思想差不多,單程式碼完全自己思考,實現過程有較多的if..else語句 看起來可能會有點暈。各處都有註釋有不懂的可以私信我

表示式求值-中表示式轉換成表示式然後求值

/*表示式求值,先轉換成字尾表示式,再計算。 //從中綴表示式中從左往右依次取出資料 //如遇到運算元,直接輸出到字尾的佇列裡。 //如果遇到操作符(包括括號),這裡再定義一個存放操作符的棧,則: //i.如果操作符是'(',入棧 //ii.如果操作符是')',則把棧裡的操作符依次出棧並插入到字尾序

中綴表示式表示式並求值(多位數完整版)

看了很多的中綴表示式轉換字尾表示式求值問題及程式碼,可是網路上的很多都是一位數的運算,我將程式碼進行了完善補充。 #include #include #include<math.h> #include #include #include using

根據二叉樹前遍歷和中遍歷序列求解遍歷的演算法

問題模型:已知某二叉樹前序遍歷序列為1,2,3,4,5,6,中序遍歷為3,2,4,1,6,5,設計程式計算後序序列。 關於這個問題你可以通過前序遍歷和中序遍歷建立一顆樹,然後通過後序遍歷進行求解,當然還可以直接根據已知兩序列直接推理後序序列,本文將對這種分析進行介紹。 利用

(二叉樹)(應用)二叉樹的中遍歷

題目描述 題目就不用多說了,即是對二叉樹進行左-根-右的中序遍歷。 題目分析 不管是對二叉樹進行中序遍歷,還是前序或者後序遍歷,最簡單的方法也是最容易想的方法就是遞迴,遞迴程式碼如下: vector<int> inorderTraversal(TreeNode*

表示式樹】表示式構造表示式二叉樹

之前在棧的資料結構中有見到將中序表示式轉換成後序表示式的方法,現在來看看如何用後序表示式構造一個表示式二叉樹static DoubleTree CreatExpressTree(char *expres

前輟表示式,中輟表示式表示式詳解

/*對於科學計算器的演算法運用,有簡單的演算法也有難的演算法, 比如我部落格中的基本功能的演算法程式碼,思維簡單,但是程式碼編寫起來卻是麻煩有難度。 所以牛逼的大神麼就搞出了幾種牛逼的表示式,通過運用

應用----四則運算綴逆波蘭表示(RPN)

數據結構 棧 逆波蘭表示法(rpn) 我們從小就學習四則運算——加減乘除四則。我們也知道,要先乘除後加減,遇到括號要先算括號內的。可是,想讓計算機進行這樣的四則運算可不容易,它可不知道什麽乘除優先,然後加減。那麽,該如何讓計算機也能進行這樣的四則運算呢?就是通過棧。 我們人類非常熟悉也非

應用-字尾表示式求解

# ifndef LINKSTACK_H # define LINKSTACK_H # include <stdio.h> # include <string.h> # include <stdlib.h> //鏈式棧的結點 type

應用1:算術表示式求值

主要要理解字尾表示式是怎麼由中綴表示式得來的 多寫寫算術表示式實際是怎麼運算的(順序) 字尾表示式是怎麼運算的 使用(來提升運算子的優先順序 運算子總是跟在第二個操作元的後面 package Evaluation; import java.uti

【資料結構】應用---四則運算表示式求值(中綴表示式與字尾表示式轉換)

用計算機實現帶括號的四則運算的方式。 這裡的困難在於乘除運算的優先順序高於加減運算,並且加入了括號,使得問題變得更加困難。 20世紀50年代,波蘭邏輯學家想到了一種不需要括號的字尾表達法,我們也把它稱為逆波蘭表示。 比如:9+(3-1)*3+10/2,如果

應用-四則運算(中綴與字尾表示式轉換--Java原始碼)

參考連結 結合原文章,做了一定修改,增加Java原始碼實現 1. 概述 對於四則運算表示式的計算,是輸入資料結構中棧的應用,即重點是中綴表示式轉換為字尾表示式 2. 字尾表示式計算 為了解釋字尾表示式的好處,我們先來

[資料結構與演算法] 5,應用-四則運算表示式求值

1,字尾(逆波蘭)表示法定義 計算器可以幫忙計算一些簡單的加減乘除,但是如果遇到一些比較複雜的,比如說有大中小括號的四則運算,那麼一些普通的計算器就無法實現運算了,但是觀察發現,所有的括號都是成對出

求解逆波蘭表示式(Calculate the reverse Polish notation)。有關的最基礎應用

描述 編寫函式int add(char s[]);計算字串形式的逆波蘭表示式(即兩個運算元在前,計算符在後)。本題內,保證每個運算元均為1位數。操作符有’+’,’-‘,’*’,’/’四種。且保證計算過程中除法運算全部為整數除法,結果為整數。 如

應用-四則運算表示式求值

Java實現四則運算表示式求值 前言 最近在複習資料結構與演算法,在棧的應用中瞭解到計算機計算四則運算表示式的演算法。 計算機計算四則運算主要分兩步: 將中綴表示式轉化為字尾表示式; 將字尾表示式進行運算得出結果。 字尾(逆波蘭)表示式

應用--四則運算表示式求值(java語言)

棧的應用–四則運算表示式求值(java語言) 前言 在複習資料結構的過程中,採用單鏈表實現了棧Stack,具體功能有如下幾個功能:判斷其是否為空棧、輸出棧的長度、入棧、出棧並且實現Iterable藉口,可以採用Iterator遍歷棧。在測試了棧之後,覺得應該

應用 - 綴表達式的計算

structure str hub def csdn oid 應用 ref pri 有關棧API詳情參看我的還有一篇博文: 棧的鏈式存儲 - API實現 遍歷後綴表達式中的數字和符號 對於數字:進棧 對於符號: 從棧中彈出右操作數 從棧中彈出左操作數 依

java應用表示式求值

原始碼的github地址,可以下載到本地執行 package stack.demo; import java.io.IOException; import java.util.Scanner; import java.util.Stack; /** * 表示式求值 算符優先

應用:中綴表示式求值

字尾表示式求值比較簡單,基本過程為:遇到數字則進棧,遇到運算子則出棧倆數字然後計算結果,再把結果入棧,過程比較簡單,不再複習了,下面著重記錄中綴表示式求值 中綴表示式求值可以先將中綴轉字尾,再用字尾計算結果,但是,有點太麻煩,而另一種方式是利用兩個棧直接求值,思想與上一個筆記中綴轉字尾幾