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.运行结果如下



完整程序下载链接

原创,转载请注明来自http://blog.csdn.net/wenzhou1219

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值