用二叉树或栈求表达式的值--代码实现+算法分析


解决表达式求值问题有两种方法,一种是利用栈和后缀表达式求解,另一种是二叉树中序存储表达式。所以本文分为栈和二叉树两大部分带领读者求解表达式。

1. 利用栈解决表达式求值问题

所谓表达式,就是由变量、常量以及运算符组合而成的式子。其中,常用的运算符无非 !(阶乘运算符)、^(指数 运算符)、+、-、*、/ 、( ) 这几种,比如 3!+4*2/(1-5)^2 就是一个表达式。 那么,如何用求一个表达式的值呢。用后缀表达式法。

什么是后缀表达式?就是将表达式中所有运算符放在它的运算项后面

这里以 3!+4*2/(1-5)^2 为例:6+8/16

  • ! 运算符对应的运算项为 3,转换后得到3!
  • +运算符对应的运算项是 3!4*2/(1-5)^2,转换之后得到:3! 4*2/(1-5)^2 +
  • *运算符对应的运算项是 4 和 2,转换之后得到 4 2 *
  • / 运算符对应的运算项是4 2 *(1-5)^2,转换后得到 4 2 * (1-5)^2 /
  • -运算符对应的运算项是 1 和 5,转换后得到1 5 -
  • ^运算符对应的运算项是1 5 -2,转换后得到 1 5 - 2 ^

整合之后,整个普通表达式就转换成了 3 ! 4 2 * 1 5 - 2 ^ / +这就是其对应的后缀表达式。

得到的后缀表达式,如何计算?首先创建一个栈。接着按照从左到右的顺序扫描后缀表达式,遇到运算项就入栈。遇到n元运算符,就出栈顶元素n个计算并将计算结果压回栈中。代码实现应注意的是:从栈出来的先后顺序,对应原来运算的哪一个运算项!

如:遇到阶乘(一元运算符),出栈顶计算。遇到乘法(二元运算符),出栈顶两元素计算并压回栈中。循环上述操作,最后栈中最后一个元素,即为此运算项即为整个表达式的值。

  • 根据后缀表达式求值代码实现
double calculate(char* out)
{
	int index = 0;
	stack<double>result;
	while (out[index] != '\0')
	{
		char c = out[index];
		switch (c)
		{
			case '!':
			{
				double i = result.top();
				result.pop();
				double end = 1;
				while (i != 1) end *= i-- ;
				result.push(end);
				break;
			}
			case '*':
			{
				double right = result.top();
				result.pop();
				double left = result.top();
				result.pop();
				result.push(left * right);
				break;
			}
			case '/':
			{
				double right = result.top();//被除数
				result.pop();
				double left = result.top();//除数
				result.pop();
				if (!right)
				{
					cout << "分母为零,错误" << endl;
					exit(-1);
				}
				else
				{
					result.push(left / right);
					break;
				}
			}
			case '+':
			{
				double right = result.top();
				result.pop();
				double left = result.top();
				result.pop();
				result.push(left + right);
				break;
			}
			case '-':
			{
				double right = result.top();//被减数
				result.pop();
				double left = result.top();//减数
				result.pop();
				result.push(left - right);
				break;
			}
			case '^':
			{
				double exp = result.top();//指数
				result.pop();
				double base = result.top();//底数
				result.pop();
				if (!base)
				{
					cout << "底数为零" << endl;
					exit(-1);
				}
				else
				{
					double end = 1;
					for (int i = 0; i < exp; i++)
					{
						end *= base;
					}
					result.push(end);
					break;
				}
			}
			default:
			{
				result.push(c - 48);
			}
		}
		index++;
	}
	return result.top();

}

1.2 根据表达式求后缀表达式

表达式求值的核心就是将波兰式(一般常见的表达式)转化为逆波兰式(后缀表达式)。上面讲过了如何根据后缀表达式求解表达式的值,那么如何获得后缀表达式?可以用二叉树,也可以用栈,这里讲解用栈的方式。

首先看规则:

普通表达式转换为后缀表达式需要用到一个空栈(假设名为Optr)和一个空数组(数组名)

  • 如果为 ‘0’~‘9’ 的字符,将其添加到 postexp 数组的末尾;
  • 如果该字符为除 ‘(’ 和 ‘)’ 以外的运算符,将其与 Optr 栈顶的运算符进行优先级比较(如乘法高于加法),如果该运算符优先级高于或等于栈顶运算符,则将其入栈;反之,如果该运算符优先级小于或等于栈顶运算符,则将栈顶运算符出栈并添加到 postexp 数组的尾部,然后继续拿当前运算符同新的栈顶运算符做大小比较,以此类推。
  • 如果该字符为 ‘(’ 运算符,直接入栈;如果为 ‘)’ 运算符,依次取 Optr 栈顶运算符并将其添加到 postexp 数组末尾,直到遇到 ‘(’ 字符为止(注意,‘(’ 字符也从栈顶取出,但不将其添加 postexp 数组中)。

依照以上处理过程,直到将普通表达式遍历完毕,如果 Optr 栈中仍有运算符,依次将它们出栈并添加到 postex数组尾部。最终,postexp 数组内存储的表达式就是转换后的后缀表达式。

总结一句:运算项直接放数组中,运算符压入栈中,只有遇到比栈顶运算符优先级低的或栈空才出栈放入数组中。括号单独考虑。

如此一来运算符优先级高的就紧随运算项,先运算。运算符优先级低的往往在后缀表达式最右边。

下面是表达式3 ! 4 2 * 1 5 - 2 ^ / +转换为后缀表达式的过程:(按序号看)

image-20221029002310515

代码实现

void transform(char* expr, char* out)
{
	stack<char>temp;
	int index = 0;//作为输出数组的下标
	int i = 0;//表达式的下标
	while(expr[i]!='\0')
	{
		char c = expr[i];
		switch (c)
		{
			case '!':
			{
				while (!temp.empty()) 
				{
					if (temp.top() == '!')
					{
						char ch = temp.top();
						temp.pop();
						out[index++] = ch;
					}
					else//说明优先级变高了,跳出循环直接入栈
					{
						break;
					}
				}
				temp.push('!');
				i++;
				break;
			}case '*':
			{
				while (!temp.empty())
				{
					if (temp.top() == '!' || temp.top() == '^' || temp.top() == '*' || temp.top() == '/')
					{
						char ch = temp.top();
						temp.pop();
						out[index++] = ch;
					}
					else//说明优先级变高了,跳出循环直接入栈
					{
						break;
					}
				}
				temp.push('*');
				i++;
				break;
			}case '/':
			{
				while (!temp.empty())
				{
					if (temp.top() == '!' || temp.top() == '^' || temp.top() == '*' || temp.top() == '/')
					{
						char ch = temp.top();
						temp.pop();
						out[index++] = ch;
					}
					else//说明优先级变高了,跳出循环直接入栈
					{
						break;
					}
				}
				temp.push('/');
				i++;
				break;
			}case '+':
			{
				while (!temp.empty())
				{
					if (temp.top() == '!' || temp.top() == '^' || temp.top() == '*' 
						|| temp.top() == '/' || temp.top() == '+' || temp.top() == '-')
					{
						char ch = temp.top();
						temp.pop();
						out[index++] = ch;
					}
					else//说明优先级变高了,跳出循环直接入栈
					{
						break;
					}
				}
				temp.push('+');
				i++;
				break;
			}case '-':
			{
				while (!temp.empty())
				{
					if (temp.top() == '!' || temp.top() == '^' || temp.top() == '*'
						|| temp.top() == '/' || temp.top() == '+' || temp.top() == '-')
					{
						char ch = temp.top();
						temp.pop();
						out[index++] = ch;
					}
					else//说明优先级变高了,跳出循环直接入栈
					{
						break;
					}
				}
				temp.push('-');
				i++;
				break;
			}case '(':
			{
				temp.push('(');
				i++;
				break;
			}case ')':
			{
				while (temp.top() != '(')
				{
					char ch = temp.top();
					temp.pop();
					out[index++] = ch;
				}
				temp.pop();//此时栈顶为(
				i++;
				break;
			}case '^':
			{
				while (!temp.empty())
				{
					if (temp.top() == '!'||temp.top()=='^')
					{
						char ch = temp.top();
						temp.pop();
						out[index++] = ch;
					}
					else//说明优先级变高了,跳出循环直接入栈
					{
						break;
					}
				}
				temp.push('^');
				i++;
				break;
			}default :
			{
				out[index++] = c;
				i++;
				break;
			}
		
		}
	}
	//此时将栈中多有的数据逐一出栈
	while (!temp.empty())
	{
		out[index++] = temp.top();
		temp.pop();
	}
	out[index] = '\0';//最后给后缀表达式加上尾\0
}
int main() {
	char* s = (char*)malloc(15 * sizeof(char));
	char* out = (char*)malloc(13 * sizeof(char));
	char temp[] = "3!+4*2/(1-5)^2";
	//cout << strlen(s);
	for (int i = 0; i < 15; i++)
	{
		s[i] = temp[i];
	}
	transform(s,out);
	cout << "后缀表达式为:"<< out << endl;
	cout <<"表达式运算结果为:"<< calculate(out);

}
image-20221029141426025

2. 二叉树求表达式值

2.1 二叉树存储表达式

表达式转换成二叉树的思路和栈其实类似,下面是具体算法思路

【算法步骤】

  1. 初始化OPTR栈和EXPT栈

  2. 按序逐个读取表达式字符。则循环执行以下操作。

    • 若ch不是运算符,则以ch为根创建一棵只有根节点的二叉树,且将该树根节点压入EXPT栈。

    • 若ch是运算符。若栈为空直接入栈,不用处理。若栈非空,则将ch运算符和根据OPTR的栈顶元素优先级比较结果,进行不同的处理;

      若ch优先级大于栈顶,则将ch压入OPTR栈

      若ch优先级小于或等于栈顶,则弹出OPTR栈顶的运算符,从EXPT栈弹出两个表达式子树的根节点。运算符为根节点,以EXPT栈中弹出的第二个子树作为左子树,以EXPT栈中弹出的第第一个子树作为右子树,创建一棵新二叉树并将该树根节点压入EXPT栈,成为新的表达式子树

此处的代码只考虑了±*/(),代码包括按序取字符,针对不同字符用switch语句处理,代码量主要就是在switch语句这里

#include<iostream>
#include<stack>
using namespace std;

typedef char BTDataType;
struct BTNode {
	BTDataType data;
	BTNode* left;
	BTNode* right;
};

BTNode* newNode(BTDataType data) {
	BTNode* root = new BTNode;
	root->data = data;
	root->left = NULL;
	root->right = NULL;
	return root;
}
//将表达式换成对应的二叉树
BTNode* transform(string exp) {
	stack<char>OPTR;//运算符栈
	stack<BTNode*>EXPT;//表达式栈

	for (int i = 0; i < exp.size(); i++) {
		if (exp[i] >= 48 && exp[i] <= 57) {
			BTNode* root = newNode(exp[i]);
			EXPT.push(root);
		}
		else {
			if (OPTR.empty()||exp[i]=='(')OPTR.push(exp[i]);
			else {
				//考虑到的运算有:+-*/()
				switch (exp[i]) {
					case '*': {
						while (!OPTR.empty()) {
							char top = OPTR.top();
							if (top == '+' || top == '-' || top == '(') {
								//说明优先级变大或栈顶为(,直接进栈退出循环在循环外统一入栈
								break;
							}
							else {
								//说明优先级变小或相等,创建二叉树,表达式栈前两个为其左右子树
								char top = OPTR.top();
								BTNode* root = newNode(top);
								OPTR.pop();

								//取得前两个表达式
								BTNode* exp1 = EXPT.top();
								EXPT.pop();
								BTNode* exp2 = EXPT.top();
								EXPT.pop();

								//左右子树链接
								root->left = exp2;
								root->right = exp1;
								
								//表达式压回栈
								EXPT.push(root);

							}
						}
						OPTR.push('*');
						break;
					}
					case '/': {
						while (!OPTR.empty()) {
							char top = OPTR.top();
							if (top == '+' || top == '-' || top == '(') {
								//说明优先级变大或栈顶为(,直接进栈退出循环和switch语句
								break;
							}
							else {
								//说明优先级变小或相等,创建二叉树,表达式栈前两个为其左右子树
								char top = OPTR.top();
								BTNode* root = newNode(top);
								OPTR.pop();

								//取得前两个表达式
								BTNode* exp1 = EXPT.top();
								EXPT.pop();
								BTNode* exp2 = EXPT.top();
								EXPT.pop();

								//左右子树链接
								root->left = exp2;
								root->right = exp1;

								//表达式压回栈
								EXPT.push(root);

							}
						}
						OPTR.push('/');
						break;
					}
					case '-': {
						while (!OPTR.empty()) {
							char top = OPTR.top();
							if (top == '(') {
								//说明优先级变大或栈顶为(,直接进栈退出循环和switch语句
								OPTR.push('-');
								break;
							}
							else {
								//说明优先级变小或相等,创建二叉树,表达式栈前两个为其左右子树
								char top = OPTR.top();
								BTNode* root = newNode(top);
								OPTR.pop();

								//取得前两个表达式
								BTNode* exp1 = EXPT.top();
								EXPT.pop();
								BTNode* exp2 = EXPT.top();
								EXPT.pop();

								//左右子树链接
								root->left = exp2;
								root->right = exp1;

								//表达式压回栈
								EXPT.push(root);

							}
						}
						OPTR.push('-');
						break;
					}
					case '+': {
						while (!OPTR.empty()) {
							char top = OPTR.top();
							if (top == '(') {
								//说明优先级变大或栈顶为(,直接进栈退出循环和switch语句
								break;
							}
							else {
								//说明优先级变小或相等,创建二叉树,表达式栈前两个为其左右子树
								char top = OPTR.top();
								BTNode* root = newNode(top);
								OPTR.pop();

								//取得前两个表达式
								BTNode* exp1 = EXPT.top();
								EXPT.pop();
								BTNode* exp2 = EXPT.top();
								EXPT.pop();

								//左右子树链接
								root->left = exp2;
								root->right = exp1;

								//表达式压回栈
								EXPT.push(root);

							}
						}
						OPTR.push('+');
						break;
					}
					case '(': {
						OPTR.push('(');
						break;
					}
					case ')': {
						while (OPTR.top()!='(') {
							//说明优先级变小或相等,创建二叉树,表达式栈前两个为其左右子树
							char top = OPTR.top();
							BTNode* root = newNode(top);
							OPTR.pop();

							//取得前两个表达式
							BTNode* exp1 = EXPT.top();
							EXPT.pop();
							BTNode* exp2 = EXPT.top();
							EXPT.pop();

							//左右子树链接
							root->left = exp2;
							root->right = exp1;

							//表达式压回栈
							EXPT.push(root);
						}
						OPTR.pop();//将(出栈
						break;
					}
				}
			}
		}
	}
	//此时将OPTR栈中所有的元素出栈,换称对应的表达式到EXPT中
	while (!OPTR.empty()) {
		//说明优先级变小或相等,创建二叉树,表达式栈前两个为其左右子树
		char top = OPTR.top();
		BTNode* root = newNode(top);
		OPTR.pop();

		//取得前两个表达式
		BTNode* exp1 = EXPT.top();
		EXPT.pop();
		BTNode* exp2 = EXPT.top();
		EXPT.pop();

		//左右子树链接
		root->left = exp2;
		root->right = exp1;

		//表达式压回栈
		EXPT.push(root);
	}
	return EXPT.top();
}

2.2 用二叉树求表达式的值

表达式树的求值【算法步骤】

  1. 设变量lvalue和rvalue分别用以记录表达式树中左子树和右子树的值,初始均为0。
  2. 如果当前节点为叶子(节点为操作数),则返回该节点的数值,否则(节点为运算符)执行以下操作:
    递归计算左子树的值,记为Ivalue;递归计算右子树的值,记为rvalue;
    根据当前节点运算符的类型,将lvalue和rvalue进行相应运算并返回。

代码实现:

double valueBTree(BTNode* root)
{
	if (root->data >= 48 && root->data <= 57)return root->data-48;
	else {
		double lvalue = valueBTree(root->left);
		double rvalue = valueBTree(root->right);
		switch (root->data) {
			case '+': {
				return lvalue + rvalue;
				break;
			}
			case '-': {
				return lvalue - rvalue;
				break;
			}
			case '*': {
				return lvalue * rvalue;
				break;
			}
			case '/': {
				return lvalue / rvalue;
				break;
			}
		}
	}
}

2.3 二叉树求表达式完整测试代码

#include<iostream>
#include<stack>
using namespace std;

typedef char BTDataType;
struct BTNode {
	BTDataType data;
	BTNode* left;
	BTNode* right;
};

BTNode* newNode(BTDataType data) {
	BTNode* root = new BTNode;
	root->data = data;
	root->left = NULL;
	root->right = NULL;
	return root;
}
//将表达式换成对应的二叉树
BTNode* transform(string exp) {
	stack<char>OPTR;//运算符栈
	stack<BTNode*>EXPT;//表达式栈

	for (int i = 0; i < exp.size(); i++) {
		if (exp[i] >= 48 && exp[i] <= 57) {
			BTNode* root = newNode(exp[i]);
			EXPT.push(root);
		}
		else {
			if (OPTR.empty()||exp[i]=='(')OPTR.push(exp[i]);
			else {
				//考虑到的运算有:+-*/()
				switch (exp[i]) {
					case '*': {
						while (!OPTR.empty()) {
							char top = OPTR.top();
							if (top == '+' || top == '-' || top == '(') {
								//说明优先级变大或栈顶为(,直接进栈退出循环在循环外统一入栈
								break;
							}
							else {
								//说明优先级变小或相等,创建二叉树,表达式栈前两个为其左右子树
								char top = OPTR.top();
								BTNode* root = newNode(top);
								OPTR.pop();

								//取得前两个表达式
								BTNode* exp1 = EXPT.top();
								EXPT.pop();
								BTNode* exp2 = EXPT.top();
								EXPT.pop();

								//左右子树链接
								root->left = exp2;
								root->right = exp1;
								
								//表达式压回栈
								EXPT.push(root);

							}
						}
						OPTR.push('*');
						break;
					}
					case '/': {
						while (!OPTR.empty()) {
							char top = OPTR.top();
							if (top == '+' || top == '-' || top == '(') {
								//说明优先级变大或栈顶为(,直接进栈退出循环和switch语句
								break;
							}
							else {
								//说明优先级变小或相等,创建二叉树,表达式栈前两个为其左右子树
								char top = OPTR.top();
								BTNode* root = newNode(top);
								OPTR.pop();

								//取得前两个表达式
								BTNode* exp1 = EXPT.top();
								EXPT.pop();
								BTNode* exp2 = EXPT.top();
								EXPT.pop();

								//左右子树链接
								root->left = exp2;
								root->right = exp1;

								//表达式压回栈
								EXPT.push(root);

							}
						}
						OPTR.push('/');
						break;
					}
					case '-': {
						while (!OPTR.empty()) {
							char top = OPTR.top();
							if (top == '(') {
								//说明优先级变大或栈顶为(,直接进栈退出循环和switch语句
								OPTR.push('-');
								break;
							}
							else {
								//说明优先级变小或相等,创建二叉树,表达式栈前两个为其左右子树
								char top = OPTR.top();
								BTNode* root = newNode(top);
								OPTR.pop();

								//取得前两个表达式
								BTNode* exp1 = EXPT.top();
								EXPT.pop();
								BTNode* exp2 = EXPT.top();
								EXPT.pop();

								//左右子树链接
								root->left = exp2;
								root->right = exp1;

								//表达式压回栈
								EXPT.push(root);

							}
						}
						OPTR.push('-');
						break;
					}
					case '+': {
						while (!OPTR.empty()) {
							char top = OPTR.top();
							if (top == '(') {
								//说明优先级变大或栈顶为(,直接进栈退出循环和switch语句
								break;
							}
							else {
								//说明优先级变小或相等,创建二叉树,表达式栈前两个为其左右子树
								char top = OPTR.top();
								BTNode* root = newNode(top);
								OPTR.pop();

								//取得前两个表达式
								BTNode* exp1 = EXPT.top();
								EXPT.pop();
								BTNode* exp2 = EXPT.top();
								EXPT.pop();

								//左右子树链接
								root->left = exp2;
								root->right = exp1;

								//表达式压回栈
								EXPT.push(root);

							}
						}
						OPTR.push('+');
						break;
					}
					case '(': {
						OPTR.push('(');
						break;
					}
					case ')': {
						while (OPTR.top()!='(') {
							//说明优先级变小或相等,创建二叉树,表达式栈前两个为其左右子树
							char top = OPTR.top();
							BTNode* root = newNode(top);
							OPTR.pop();

							//取得前两个表达式
							BTNode* exp1 = EXPT.top();
							EXPT.pop();
							BTNode* exp2 = EXPT.top();
							EXPT.pop();

							//左右子树链接
							root->left = exp2;
							root->right = exp1;

							//表达式压回栈
							EXPT.push(root);
						}
						OPTR.pop();//将(出栈
						break;
					}
				}
			}
		}
	}
	//此时将OPTR栈中所有的元素出栈,换称对应的表达式到EXPT中
	while (!OPTR.empty()) {
		//说明优先级变小或相等,创建二叉树,表达式栈前两个为其左右子树
		char top = OPTR.top();
		BTNode* root = newNode(top);
		OPTR.pop();

		//取得前两个表达式
		BTNode* exp1 = EXPT.top();
		EXPT.pop();
		BTNode* exp2 = EXPT.top();
		EXPT.pop();

		//左右子树链接
		root->left = exp2;
		root->right = exp1;

		//表达式压回栈
		EXPT.push(root);
	}
	return EXPT.top();
}

double valueBTree(BTNode* root)
{
	if (root->data >= 48 && root->data <= 57)return root->data-48;
	else {
		double lvalue = valueBTree(root->left);
		double rvalue = valueBTree(root->right);
		switch (root->data) {
			case '+': {
				return lvalue + rvalue;
				break;
			}
			case '-': {
				return lvalue - rvalue;
				break;
			}
			case '*': {
				return lvalue * rvalue;
				break;
			}
			case '/': {
				return lvalue / rvalue;
				break;
			}
		}
	}
}
int main() {
	string sample = "(1+2)*3-4/5";
	BTNode* root =transform(sample);
	cout << endl;
	cout << "输入:" << sample << endl;
	cout << "输出" << valueBTree(root) << endl;

	sample = "1+2*3-4/5";
	root = transform(sample);
	cout << endl;
	cout << "输入:" << sample << endl;
	cout << "输出" << valueBTree(root) << endl;
}

image-20221128172659999

  • 17
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值