C++二叉树实现四则运算表达式计算(支持无限叠加括号,负数,小数,多位数字)

前言:

二叉树实现表达式计算有很多优点,首先它的先序遍历是前缀表达式(波兰表达式),中序遍历是中缀表达式,后序遍历是(逆波兰表达式),最后再进行计算的时候通过不同的遍历可以采用不同的算法。

以下是各个表达式传统做法:

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,因为()里面可以为表达式也可以

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值