Logo

郎哥编程

栈结构应用:表达式解析求值

2026-07-24 142

栈结构是一种特殊的线性表,限定仅在表的一端进行元素的插入和删除。本文使用C语言利用栈结构实现表达式解析并求值功能。

一、表达式求值

表达式求值在编译程序和计算程序中被经常应用,表达式本身是一个字符序列,要实现对表达式求值,首先要能够正确解释表达式。在编程语言中,表达式是由变量、常量和运算符的组合,它执行计算并返回计算结果。在表达式中运算符作用的变量或常量称为操作数。例如,求圆面积的公式就是一个表达式,其中S、π、r为变量或常量,r的2次方可以描述为r*r。求圆面积的表达式为:

S =π* r *r;

正确地解释表达式,就是让程序扫描表达式字符序列时,从字符序列中正确解析出操作数、运算符、界限符,并按照运算规则执行运算。例如,要对下面的算术表达式求值

2*(3+5)

程序不仅要识别出2、3、5操作数,还需要识别出*、+、(、)运算符。同时还要按照算术四则运算的规则执行运算。既先乘除,后加减;从左算到右;先括号内,后括号外。

为了叙述简便,本文仅讨论简单算术表达式的求值问题,运算符也限于加、减、乘、除四中运算符,界限符仅限于小括号。读者不难将它推广到更一般的表达式上。

二、栈结构

要对上面的表达式求值,可以采用算符优先法。它的实现思想是对表达式字符序列自左向右进行扫描,遇到操作数入操作数栈。遇到运算符,首先与运算符栈的栈顶运算符比较优先级,若栈顶运算符的优先级高于当前运算符,则栈顶运算符出栈并执行运算,否则将当前运算符入运算符栈,直至整个表达式求值完毕。

栈结构是一种特殊的线性表,限定仅在表的一端进行元素的插入和删除。当表中没有元素时,称为空栈。若给定栈:

S = (a1,a2,……,an)

则称a1是栈底元素,an是栈顶元素,表中元素按a1,a2,……,an的次序进栈,出栈的顺序是an,……,a2,a1。也就是说,栈结构的元素访问原则是后进先出,也称为后进先出的线性表的,如下图所示。

栈结构运算有入栈、出栈、访问栈顶元素、栈置空四种基本运算。

(1)入栈运算

该运算在长度为n的栈中,将元素置入栈顶。若栈已满,返回出错信息。

(2)出栈运算

该运算在长度为n的栈中,取出栈顶元素,并从栈中删除该元素。若栈为空,返回出错信息。

(3)访问栈顶元素运算

该运算在长度为n的栈中,取出栈顶元素。若栈为空,返回出错信息。

(4)栈置空运算

该运算将栈置为空栈。

三、算法设计

算式由操作数、运算符和界限符组成,操作数为需要执行运算的数,运算符为加、减、乘、除,界限符为小括号。

算符优先法是根据算式相继出现的算符优先级来确定运算顺序,要比较算符的优先级,就要确定各种算符的优先级别,可以将算符间的优先级通过矩阵表示,在解析算式过程中通过查询算符优先级矩阵获取算符间的优先关系。

算式的运算符和界限符统称为算符,它们组成一个集合op{ +,-,*,/,(,) }。集合中任意两个算符a或b的优先级至多是下面三种关系之一。

a < b    a的优先级小于b的优先级

a = b    a的优先级等于b的优先级

a > b    a的优先级大于b的优先级

在算符优先算法中,可以采用下面的矩阵定义op集合算符间的优先关系,其中a为栈顶运算符,b为当前识别的运算符。

算符间的优先矩阵可以用字符类型的二维数组表示。

在C语言中,算符间的优先矩阵可以字符类型的二维数组表示。

/**
运算符
*/
char oparray[]={'+','-','*','/','(',')'};
/**
运算符优先级
*/
char priority[6][6]={
{'>','>','>','>','<','>'},
{'>','>','>','>','<','>'},
{'<','<','<','<','<','>'},
{'<','<','<','<','<','>'},
{'<','<','<','<','<','='},
{'>','>','>','>','=','='}
};

解析表达式时可以使用操作数和运算符两个栈,操作数栈用于存储操作数和运算结果,运算符栈用于存储算符。

算法的基本思想是:依次读入表达式每个字符,如是操作数进操作数栈,如是运算符,需要和运算符栈的栈顶运算符比较优先级,优先级若低于栈顶运算符,首先需要处理栈顶运算符并执行相关运算后再入栈,直至整个算式求值完毕。

四、代码实现

下面用C语言给出算法程序代码,程序假定输入的算式语法正确,没有对算式语法进行检测。

float evaluate(char* expression) 
{
	int length = strlen(expression);
	int i,j;
	char  *p;
	char ch,ch1,ch2,op;
	double temp;
	// 运算符栈
	PSTACK pStackOp = (PSTACK)init_stack(0);
	// 操作数栈
	PSTACK pStackNum = (PSTACK)init_stack(0);
	for( i = 0; i < length; i++ )
	{
		if( expression[i]==' ' )
			continue;
		if (expression[i] >= '0' && expression[i] <= '9') 
		{
			p = (char*)malloc(sizeof(char)*length);
			j = 0;
			while(i < length && expression[i] >= '0' && expression[i] <= '9')
			{
				p[j++] = expression[i++];
			}
			p[j] = '\0';
			i--;
			temp = atof(p);
			// 操作数入栈
			push(pStackNum,0,&temp);
		}
		if( isOperator(expression[i]) )
		{
			// 判断运算符栈是否为空
			if( isEmpty(pStackOp) )
			{
				// 运算符入栈
				push(pStackOp,1,&expression[i]);
			}
			else
			{
				// 判断字符的优先级
				ch1 = *((char*)peek(pStackOp,1));
				ch = hasPrecedence(expression[i],ch1);
				if( ch == '<' )
				{
					// 运算符入栈
					push(pStackOp,1,&expression[i]);
				}
				else if( ch == '>' )
				{
					if( expression[i] == ')' )
					{
						while(1)
						{
							if( isEmpty(pStackOp) )
								break;
							ch2 = *((char*)peek(pStackOp,1));
							op = hasPrecedence(expression[i],ch2);
							if( op == '=' )
							{
								pop(pStackOp,1);
								break;
							}
							else
							{
								temp = caculate(*((char*)pop(pStackOp,1)),*((double*)pop(pStackNum,0)),*((double*)pop(pStackNum,0)));
								push(pStackNum,0,&temp);
							}
						}
					}
					else
					{
						temp = caculate(*((char*)pop(pStackOp,1)),*((double*)pop(pStackNum,0)),*((double*)pop(pStackNum,0)));
						push(pStackNum,0,&temp);
						push(pStackOp,1,&expression[i]);
					}
				}
			}
		}
	}
	while(!isEmpty(pStackOp))
	{
		temp = caculate(*((char*)pop(pStackOp,1)),*((double*)pop(pStackNum,0)),*((double*)pop(pStackNum,0)));
		push(pStackNum,0,&temp);
	}
	return *((double*)pop(pStackNum,0));
}

函数evaluate()是算法的核心,传入的参数expression是待求值的表达式。函数evaluate()基本体现了前面给出的算法思想。其中对小括号做了特殊处理,当遇到算符')'时,需要对算符栈连续执行出栈操作,并执行相关运算,直至与栈顶运算符优先级相等或者栈已空。

在函数evaluate()内部调用了一些已定义的函数,下面对这些函数给出说明。

isOperator()函数判断传入的字符是否为运算符,若为运算符返回1,否则返回0。

int isOperator(char op)
{
	int i;
	for(i = 0; i < sizeof(oparray)/sizeof(oparray[0]); i++ )
	{
		if( op == oparray[i] )
			return 1;
	}
	return 0;
}

getOperator()函数获取运算符在数组中的索引,代码如下:

int getOperator(char op)
{
	int i;
	for(i = 0; i < sizeof(oparray)/sizeof(oparray[0]); i++ )
	{
		if( op == oparray[i] )
			return i;
	}
	return -1;
}

hasPrecedence()函数用于判断两个算符的优先级关系,算符的优先级矩阵由priority二维数组给出,函数返回矩阵给出的优先级关系。

char  hasPrecedence(char op1, char op2) 
{
	int suffixop1 = getOperator(op1);
	int suffixop2 = getOperator(op2);
	if( suffixop1 == -1 || suffixop2 == -1 )
		return 0;
	return priority[suffixop1][suffixop2];
}

caculate()函数对传入的运算符和操作数做求值运算。代码如下:

float caculate(char op, float b, float a) 
{
	switch (op) {
		case '+':
			return a + b;
		case '-':
			return a - b;
		case '*':
			return a * b;
		case '/':
			if (b == 0) {
				return 0;			
			}
			return a / b;
		}
		return 0;
}

函数evaluate()内部还调用了peek()、isEmpty()函数。peek()函数从栈中获取栈顶运算,但不弹出元素,peek()函数的代码可以借鉴pop()函数,由读者自行给出。isEmpty()函数判断栈是否为空,栈结构的top成员为-1时,该栈为空,isEmpty()函数的代码由读者自行给出。

评论区

登录 后发表评论
暂无评论