前言:
二叉树实现表达式计算有很多优点,首先它的先序遍历是前缀表达式(波兰表达式),中序遍历是中缀表达式,后序遍历是(逆波兰表达式),最后再进行计算的时候通过不同的遍历可以采用不同的算法。
以下是各个表达式传统做法:
1. 前缀表达式(Prefix Notation)
算法步骤:
- 从右到左遍历表达式。
- 如果遇到一个操作数,将其压入栈中。
- 如果遇到一个操作符,弹出栈顶的两个操作数,进行运算,然后将结果压入栈中。
- 最后栈中的元素即为结果。
2. 中缀表达式(Infix Notation)
算法步骤:
- 使用两个栈,一个用于操作数,另一个用于运算符。
- 遍历表达式:
- 如果是操作数,压入操作数栈;
- 如果是运算符,判断其优先级并进行处理:
- 当运算符栈顶的运算符优先级高于当前运算符时,弹出栈顶运算符并应用操作。
- 将当前运算符压入运算符栈。
- 遍历完成后,依次弹出栈中所有运算符进行计算。
3. 后缀表达式(Postfix Notation)
算法步骤:
- 从左到右遍历表达式。
- 如果遇到操作数,压入栈中。
- 如果遇到运算符,弹出栈顶的两个操作数,进行运算,然后将结果压入栈中。
- 最后栈中的元素即为结果。
具体架构:
而我的有所不同,因为我们已经创建了二叉树,只需要递归计算即可。
算法步骤:
基于递归的表达式求值:
- 如果当前节点是操作数,直接返回值。
- 如果是操作符,根据左右子树递归计算操作数后,执行对应运算。
代码如下:
//计算表达式
operandType CalculateExpression(BiTree tree)
{
if (tree == nullptr) { ERROR = true; return -314; }
if (tree->isOperator) //为运算符
{
operandType a = CalculateExpression(tree->lchild); //递归左子树
operandType b = CalculateExpression(tree->rchild); //递归右子树
switch (tree->Operator)
{
case '+': return a + b; // 加法
case '-': return a - b; // 减法
case '*': return a * b; // 乘法
case '/': if (b == 0) { ERROR = true; return -314; } return a / b; // 除法
default: ERROR = true; return -314; // 错误值
}
}
else //为运算数
{
return tree->Operand;
}
}
计算的问题解决之后,现在考虑二叉树怎么创建,以下是二叉树的节点的信息,代码如下:
// 树存储结构
typedef struct BiTNode {
bool isOperator = true; //是否是运算符,默认为 true
char Operator = '#'; //运算符,默认为 #
int OperatorLevel = 0; //符号等级,默认为 0
operandType Operand = 0; //运算数,默认为 0
struct BiTNode* lchild = nullptr, * rchild = nullptr; // 左右孩子指针,默认为 空指针
} BiTNode, * BiTree;
观察表达式: 1+2*(5-3)-8/4
创建的二叉树及信息如下:
我们观察到,当运算符优先级低的越往上,优先级高的越往下。因此我们读入运算符时要计算他们的符号等级,设计一个函数进行计算,代码如下:
//计算符号优先级
int OperatorLevel(char oper)
{
switch (oper) {
case '+': return 1; // 加法
case '-': return 1; // 减法
case '*': return 2; // 乘法
case '/': return 2; // 除法
case '(': return 3; // 左括号
case ')': return 4; // 右括号
case '.': return 7; // 小数点
case '0': return 9; // 数字0
case '1': return 9; // 数字1
case '2': return 9; // 数字2
case '3': return 9; // 数字3
case '4': return 9; // 数字4
case '5': return 9; // 数字5
case '6': return 9; // 数字6
case '7': return 9; // 数字7
case '8': return 9; // 数字8
case '9': return 9; // 数字9
default: ERROR = true; return -314; // 错误值
}
}数字、小数点、左右括号之后再解释,加减的优先级是相同的,所以都为1,乘除的优先级比加减高且都是相同的,所以都为2。
nums[]数组为表达式数组,规定i=0,为数组下标。我们从左到右遍历一遍表达式,看看具体建树的过程:
表达式: 1+2*(5-3)-8/4
1.刚创建的节点默认符号'#',遍历表达式,开始i==0遇到1,它是运算数,且根树的节点为'#',所以我们将它标识为运算数,运算数的值为1,代码如下:
if (OperatorLevel(nums[i]) == 9) //为运算数
{
if (node->OperatorLevel == 0) //开始的情况
{
node->isOperator = false; //标识为运算数
node->OperatorLevel = 9; //符号等级为9
node->Operand= Number(nums, len, i); //连续多位读取数字
}
}
表达式: 1+2*(5-3)-8/4
2. i==1遇到'+'为运算符,根数的节点是运算数1,我们规定运算数的优先级是最大的,所以它为9,+的运算符等级为1,根树的节点优先级比+的大,因此+要作为1的父节点,将根树的指针指向它,代码如下:
if (OperatorLevel(nums[i]) == 9) //为运算数
{.......}
else //为运算符
{
int currentLevel = OperatorLevel(nums[i]); //计算运算符优先级
//根节点优先级大于等于当前符号优先级,以及根节点为数的情况
if (node->OperatorLevel >= currentLevel)
{
BiTree nodeTemp = new BiTNode; //新建节点
lastnode = nodeTemp; //将最近一次编辑的节点指向新建节点
nodeTemp->isOperator = true; //新建节点标识为运算符
nodeTemp->Operator = nums[i]; //新建节点的符号位为当前遇到的运算符
nodeTemp->OperatorLevel = currentLevel; //新建节点的符号等级为当前遇到的
nodeTemp->lchild = node; //将新建节点的左孩子指向根节点
node = nodeTemp; //根节点指向新建节点
}
}
表达式: 1+2*(5-3)-8/4
3.i==2遇到'2'为运算数,因为根节点不是默认情况,且左子树已满,右子树为空,所以我们将它插入到右子树中,在步骤1的代码补上:
if (OperatorLevel(nums[i]) == 9) //为运算数
{
if (node->OperatorLevel == 0) //开始的情况
{
node->isOperator = false; //标识为运算数
node->OperatorLevel = 9; //符号等级为9
node->Operand = Number(nums, len, i); //连续多位读取数字
}
else
{
BiTree nodeTemp = new BiTNode; //新建节点
nodeTemp->isOperator = false; //将新建节点标识为运算数
nodeTemp->OperatorLevel = 9; //符号等级为9
nodeTemp->Operand = Number(nums, len, i); //连续多位读取数字
if (!node->lchild) //根的左孩子为空
{
node->lchild = nodeTemp; //根节点的左孩子为新建节点
}
else if (!node->rchild) //根的右孩子为空
{
node->rchild = nodeTemp; //根节点的右孩子为新建节点
}
}
}
表达式: 1+2*(5-3)-8/4
4.i==3遇到'*'为运算符,因为*的优先级比+的高,可是根树的左右节点都满了,这时我们就要移动节点,为了不打破它们的计算顺序,我们优先处理右子树,将右子树作为*的左子树,然后将*的作为根树的右子树,在步骤2的代码补上:
if (OperatorLevel(nums[i]) == 9) //为运算数
{.......}
else //为运算符
{
int currentLevel = OperatorLevel(nums[i]); //计算运算符优先级
//根节点优先级大于等于当前符号优先级,以及根节点为数的情况
if (node->OperatorLevel >= currentLevel)
{
BiTree nodeTemp = new BiTNode; //新建节点
lastnode = nodeTemp; //将最近一次编辑的节点指向新建节点
nodeTemp->isOperator = true; //新建节点标识为运算符
nodeTemp->Operator = nums[i]; //新建节点的符号位为当前遇到的运算符
nodeTemp->OperatorLevel = currentLevel; //新建节点的符号等级为当前遇到的
nodeTemp->lchild = node; //将新建节点的左孩子指向根节点
node = nodeTemp; //根节点指向新建节点
}
else //根节点比当前符号优先级低
{
BiTree nodeTemp = new BiTNode; //新建节点
lastnode = nodeTemp; //将最近一次编辑的节点指向新建节点
nodeTemp->isOperator = true; //新建节点标识为运算符
nodeTemp->Operator = nums[i]; //新建节点的符号位为当前遇到的运算符
nodeTemp->OperatorLevel = currentLevel; //新建节点的符号等级为当前遇到的
if (!node->rchild) //根树的右孩子为空
{
node->rchild = nodeTemp; //根树的右孩子为新建节点
}
else if (node->rchild) //根树的右孩子不为空
{
nodeTemp->lchild = node->rchild; //新建节点的左孩子为根树的右孩子
node->rchild = nodeTemp; //根树的右孩子为新建节点
}
else if (!node->lchild) //根树的左孩子为空
{
node->lchild = nodeTemp; //根树的左孩子为新建节点
}
else //根树的左孩子不为空
{
nodeTemp->lchild = node->lchild; //新建节点的左孩子为根树的左孩子
node->lchild = nodeTemp; //根树的左孩子为新建节点
}
}
}
表达式: 1+2*(5-3)-8/4
5.i==4遇到'('为运算符,它会把括号里的表达式或者数作为*的右子树,而*刚好就是我们上一步编辑的节点,因此我们要新建一个指针指向我们上一次编辑的节点,规定新建的指针名为lastnode,因为()里面可以为表达式也可以