从零开始写一个数学表达式解析器

从零开始写一个数学表达式解析器


解析并计算数学表达式的方法有多种,下面介绍两种常见的方法:

 1.中缀表达式转后缀表达式 + 后缀表达式求值:
    将中缀表达式转换为后缀表达式
    使用栈结构对后缀表达式进行求值。
    
2. 递归下降解析:
    根据表达式的语法规则,用递归函数解析表达式。
    遍历语法树,执行相应的计算操作得到最终结果。

一、逆波兰表达式(RPN)

逆波兰表达式(Reverse Polish Notation,RPN),也称为后缀表达式,是一种用于表示数学表达式的记法方式。在逆波兰表达式中,运算符位于操作数的后面,因此也被称为后缀表示法。

相比于常见的中缀表示法(如 3 + 4 * 2),逆波兰表达式具有以下特点:

 没有括号:逆波兰表达式不需要括号来确定运算的顺序,因为它通过运算符的位置来明确操作数的顺序。
 
 顺序明确:逆波兰表达式通过将运算符放在操作数的后面,可以明确指定操作的顺序。例如,逆波兰表达式 "3 4 2 * +" 表示的是 3 + (4 * 2)。

逆波兰表达式的计算过程通常使用栈来实现。算法遍历逆波兰表达式中的每个元素,遇到操作数时将其入栈,遇到运算符时从栈中弹出相应数量的操作数进行计算,并将计算结果重新入栈。最终,栈中剩下的元素就是计算结果。

以下是一个示例来说明逆波兰表达式的计算过程:

逆波兰表达式: 3 4 2 * +

    从左到右遍历表达式的每个元素。
    遇到数字 3,入栈。
    遇到数字 4,入栈。
    遇到数字 2,入栈。
    遇到运算符 *,从栈中弹出操作数 2 和 4,计算 4 * 2 的结果 8,将结果入栈。
    遇到运算符 +,从栈中弹出操作数 8 和 3,计算 8 + 3 的结果 11,将结果入栈。
    遍历结束,栈中剩下的元素 11 就是最终的计算结果。

因此,逆波兰表达式 “3 4 2 * +” 的计算结果为 11。
在这里插入图片描述

二、将中缀表达式转化为逆波兰表达式

将中缀表达式转化为逆波兰表达式的常用方法是使用栈来处理。以下是一种常见的算法步骤:

创建一个空栈和一个空的输出队列(或列表)用于存储逆波兰表达式。
从左到右扫描中缀表达式的每个符号(操作数和运算符)。
如果遇到操作数(数字),将其添加到输出队列中。
如果遇到左括号 "(",将其压入栈中。
如果遇到右括号 ")",则弹出栈中的元素,并将弹出的元素添加到输出队列中,直到遇到左括号为止。注意:左右括号不添加到输出队列中。
如果遇到运算符,判断其与栈顶运算符的优先级:
   - 如果栈为空,或栈顶为左括号 "(",则将运算符压入栈中。
      - 否则,将栈顶运算符与当前运算符进行比较:
   - 如果栈顶运算符的优先级大于或等于当前运算符,将栈顶运算符弹出并添加到输出队列中,直到栈顶运算符的优先级小于当前运算符,或栈为空,然后将当前运算符压入栈中。
重复步骤2至步骤6,直到扫描完所有符号。
将栈中剩余的运算符依次弹出并添加到输出队列中。
输出队列中的元素即为转化后的逆波兰表达式。

以下是一个简单的示例,演示了如何将中缀表达式转化为逆波兰表达式:

中缀表达式: 3 + 4 * 2 / (1 - 5)^2

转化为逆波兰表达式: 3 4 2 * 1 5 - 2 ^ / +

在这里插入图片描述

栈的实现

首先,我们定义一个结构体来表示栈:

#define MAX_SIZE 100

typedef struct {
    int data[MAX_SIZE];
    int top;
} Stack;

接下来,我们可以定义一些操作来操作栈的各个功能,比如初始化栈、判断栈是否为空、判断栈是否已满、入栈、出栈等:

void initialize(Stack *stack) {
    stack->top = -1; // 初始化栈顶索引为-1,表示栈为空
}

int isEmpty(Stack *stack) {
    return (stack->top == -1); // 栈为空时返回1,否则返回0
}

int isFull(Stack *stack) {
    return (stack->top == MAX_SIZE - 1); // 栈满时返回1,否则返回0
}

void push(Stack *stack, int value) {
    if (isFull(stack)) {
        printf("Stack overflow\n"); // 栈已满,无法入栈
        return;
    }
    stack->top++; // 栈顶索引加1
    stack->data[stack->top] = value; // 将元素压入栈顶
}

int pop(Stack *stack) {
    if (isEmpty(stack)) {
        printf("Stack underflow\n"); // 栈为空,无法出栈
        return -1; // 返回一个特殊值表示出错
    }
    int value = stack->data[stack->top]; // 取出栈顶元素的值
    stack->top--; // 栈顶索引减1
    return value;
}

解析算法实现

首先需要对字符串中的运算符进行识别与优先级判断,这一步通过isOperator和getPrecedence函数实现,代码如下:

int isOperator(char ch) 
{
    return (ch == '+' || ch == '-' || ch == '*' || ch == '/');
}

int getPrecedence(char ch) 
{
    if (ch == '+' || ch == '-')
        return 1;
    else if (ch == '*' || ch == '/')
        return 2;
    return 0;
}

接下来需要将中缀表达式转化为RPN,定义函数infixToPostfix:

void infixToPostfix(char *infix, char *postfix) //传入两个字符指针,分别对应中缀表达式与RPN
    Stack stack;
    initialize(&stack); //栈初始化
    int i, j;
    char ch;

    for (i = 0, j = 0; infix[i] != '\0'; i++) 
    {
        ch = infix[i];

        if (isalnum(ch)) //判断是否为数字,这里的isanum函数需要先包含<ctype.h>才能使用
        {
            postfix[j++] = ch;//是数字,进入输出队列
        }
         else if (ch == '(') //判断是否是左括号
        {
            push(&stack, ch);//是左括号,压入栈顶
        } 
        else if (ch == ')') //判断是否是右括号
        {
            while (!isEmpty(&stack) && stack.data[stack.top] != '(') 
            {
                postfix[j++] = pop(&stack);//弹出栈中的元素,并将弹出的元素添加到输出队列中,直到遇到左括号为止,注意:左右括号不添加到输出队列中。
            }
            if (!isEmpty(&stack) && stack.data[stack.top] == '(')
             {
                pop(&stack);//弹出栈顶左括号但不添加到输出队列中。
            }
             else 
            {
                printf("Invalid expression\n");//判断表达式是否合法
                return;
            }
        } 
        else if (isOperator(ch)) //判断是否遇到运算符
        {
            while (!isEmpty(&stack) && stack.data[stack.top] != '(' &&getPrecedence(stack.data[stack.top]) >= getPrecedence(ch))    
            {
                postfix[j++] = pop(&stack);//如果栈顶运算符的优先级大于或等于当前运算符,将栈顶运算符弹出并添加到输出队列中,直到栈顶运算符的优先级小于当前运算符
            }
            push(&stack, ch);//然后将当前运算符压入栈中。
        }
 }

    while (!isEmpty(&stack) && stack.data[stack.top] != '(') 
    {
        postfix[j++] = pop(&stack);//将剩余元素从栈中弹出,不包含括号
    }

    postfix[j] = '\0';
}

最后计算RPN的值,定义evaluatePostfix函数:

int evaluatePostfix(char *postfix) {
    Stack stack;
    initialize(&stack);
    int i, operand1, operand2, result;

    for (i = 0; postfix[i] != '\0'; i++) {
        char ch = postfix[i];

        if (isdigit(ch)) {
            push(&stack, ch - '0');
        } else if (isOperator(ch)) {
            operand2 = pop(&stack);
            operand1 = pop(&stack);
            switch (ch) {
                case '+':
                    result = operand1 + operand2;
                    break;
                case '-':
                    result = operand1 - operand2;
                    break;
                case '*':
                    result = operand1 * operand2;
                    break;
                case '/':
                    result = operand1 / operand2;
                    break;
                default:
                    printf("Invalid operator\n");
                    return 0;
            }
            push(&stack, result);
        }
    }

    return pop(&stack);
}

main函数:

int main() {
    char infix[MAX_SIZE];
    char postfix[MAX_SIZE];
    printf("Enter an infix expression: ");
    fgets(infix, sizeof(infix), stdin);

    infixToPostfix(infix, postfix);
    printf("Postfix expression: %s\n", postfix);

    int result = evaluatePostfix(postfix);
    printf("Result: %d\n", result);

    return 0;
}

添加对浮点数的支持

以上程序基于整数运算,不支持浮点数,在进行整数除法时结果将丢弃小数部分

为了支持浮点数,需要对字符串中的小数进行识别,首先定义getUserInput()接收用户写入的表达式:

char* getUserInput() 
{  #define MAX_SIZE 2000
    static char input[MAX_SIZE];

    printf("输入表达式:");
    fgets(input, MAX_SIZE, stdin);

    return input;
}

定义isDecimal(),用于判断一个字符串是否为小数

bool isDecimal(const char* str) 
{
    char* endptr;
    strtod(str, &endptr);

    // 检查转换后的字符串是否为空,且遍历完所有字符
    if (*endptr == '\0' && endptr != str) {
        return true;
    }

    return false;
}

接下来对用户输入的表达式进行处理,注意到此时表达式中既有double型数据(小数)也有char型数据(括号和运算符),我们需要更该数据的储存方式以实现在一个数组中同时存放char和double型数据。这里我们用结构体数组的方式来实现

typedef struct 
{
    double doubleValue;//存放小数的值
    char charValue;//存放符号
    bool isNum;//判断当前结构体存放的数据类型
} DataItem;

DataItem infix[MAX_SIZE];//存放中缀表达式的结构体数组

定义input2infix函数将输入的字符串转换为中缀表达式

DataItem* input2infix(char *input)
{
    char temp[MAX_SIZE] = ""; // 临时字符串,用于构建数字
    DataItem infix[MAX_SIZE];
    DataItem Postfix[MAX_SIZE];
    double number;
    int i = 0;
    int index = 0;
    while (input[i] != '\0') {
        if (isdigit(input[i]) || input[i] == '.') {
            // 将数字字符或小数点添加到临时字符串中
            strncat(temp, &input[i], 1);
        }
        else 
        {
            // 当遇到非数字字符时,检查临时字符串是否为小数
            if (isDecimal(temp)) 
            {
                number = atof(temp);
                infix[index].doubleValue = number;
                infix[index].isNum = true;
                index++;
            }
            // 重置临时字符串
            temp[0] = '\0';
            if (input[i]=='(')
            {
                infix[index].charValue = input[i];
                infix[index].isNum = false;
                index++;
            }
            if (input[i] == ')')
            {
                infix[index].charValue = input[i];
                infix[index].isNum = false;
                index++;
            }
            if (isOperator(input[i]))
            {
                switch (input[i]) {
                case '+':
                    infix[index].charValue = input[i];
                    infix[index].isNum = false;
                    index++;
                    break;
                case '-':
                    infix[index].charValue = input[i];
                    infix[index].isNum = false;
                    index++;
                    break;
                case '*':
                    infix[index].charValue = input[i];
                    infix[index].isNum = false;
                    index++;
                    break;
                case '/':
                    infix[index].charValue = input[i];
                    infix[index].isNum = false;
                    index++;
                    break;
                    default:
                    //printf("Invalid operator\n");
                    return 0;
                }
            }
        }
        i++;
    }
    infix[index].charValue = '\0';
    infix[index].isNum = false;
    
    return infix;
    }

在主函数中调用 DataItem* infix= input2infix(getUserInput());就可以将输入的表达式转换为含浮点数中缀表达式了
在这里插入图片描述

然后照猫画虎,把中缀表达式转化为RPN。别忘了我们更改了数据的储存方式,所以对于栈的实现与操作也需要做相应的修改:

typedef struct 
{
    DataItem data[MAX_SIZE];//这里将data类型改为前面定义的DataItem结构体
    int top;
} Stack;
//由于压入栈中的数据有char和double类型,所以要有两种对应的压栈操作
void pushChar(Stack* stack, char value)
{
    if (isFull(stack)) {
        printf("Stack overflow\n");
        return;
    }
    stack->top++;
    stack->data[stack->top].charValue = value;
    stack->data[stack->top].isNum = false;
}

void pushDouble(Stack* stack, double value)
{
    if (isFull(stack)) {
        printf("Stack overflow\n");
        return;
    }
    stack->top++;
    stack->data[stack->top].doubleValue = value;
    stack->data[stack->top].isNum = true;
}

double popDouble(Stack* stack) {
    if (isEmpty(stack)) {
        printf("Stack underflow\n");
        return '\0';
    }
    double value = stack->data[stack->top].doubleValue;
    stack->top--;
    return value;
}

char popChar(Stack* stack) {
    if (isEmpty(stack)) {
        printf("Stack underflow\n");
        return '\0';
    }
    char value = stack->data[stack->top].charValue;
    stack->top--;
    return value;
}

最后修改infixToPostfix函数和evaluatePostfix函数就大功告成了,修改比较简单,注意更改数据类型并增添对char和double类型的判断逻辑即可,这里我直接贴代码了。

void infixToPostfix(DataItem* infix, DataItem* postfix)
{
    Stack stack;
    initialize(&stack);
    int i, j;


    for (i = 0, j = 0; !((infix[i].isNum == false) && (infix[i].charValue == '\0')); i++)
    {


        if (infix[i].isNum == true)//遇到数字,添加到输出队列
        {
            postfix[j].doubleValue = infix[i].doubleValue;
            postfix[j].isNum = infix[i].isNum;
            j++;
        }

        else if ((infix[i].isNum == false) && (infix[i].charValue == '(')) //压入栈中
        {
            pushChar(&stack, infix[i].charValue);
        }

        else if ((infix[i].isNum == false) && (infix[i].charValue == ')'))
        {
            while ((!isEmpty(&stack)) && (!((stack.data[stack.top].isNum == false) && (stack.data[stack.top].charValue== '('))))
            {
                if (infix[i].isNum)
                {
                    postfix[j].doubleValue = popDouble(&stack);
                    postfix[j].isNum = infix[i].isNum;
                    j++;
                }
                else
                {
                    postfix[j].charValue = popChar(&stack);
                    postfix[j].isNum = false;
                    j++;
                }

            }

            if ((!isEmpty(&stack)) && ((stack.data[stack.top].isNum == false) && (stack.data[stack.top].charValue == '(')))
            {
                popChar(&stack);
            }
            else {
                printf("Invalid expression\n");
                return;
            }


        }
        else if ((infix[i].isNum == false) && (isOperator(infix[i].charValue)))
        {
            while (!isEmpty(&stack) && stack.data[stack.top].charValue != '(' &&
                getPrecedence(stack.data[stack.top].charValue) >= getPrecedence(infix[i].charValue))
            {
                postfix[j].charValue = popChar(&stack);
                postfix[j].isNum = false;
                j++;
            }
            pushChar(&stack, infix[i].charValue);
        }
    }

    while (!isEmpty(&stack) && stack.data[stack.top].charValue != '(') {
        postfix[j].charValue = popChar(&stack);
        postfix[j].isNum = false;
        j++;
    }

    postfix[j].charValue = '\0';
    postfix[j].isNum = false;
}

main函数


int main() 
{
    double reslut;
    DataItem Postfix[MAX_SIZE];
    DataItem* infix= input2infix(getUserInput());
    printf("Infix:");
    for (int k = 0; !((infix[k].isNum==false)&&(infix[k].charValue=='\0')); k++)
    {
        if (infix[k].isNum)
            printf("%.2f", infix[k].doubleValue);
        else
        {
            printf("%c", infix[k].charValue);
        }
    }
    printf("\n");
    infixToPostfix(infix, Postfix);
    reslut = evaluatePostfix(Postfix);
    printf("The reslut: %.2f", reslut);
    return 0;
}

测试用例

((3 + 4) * (5 - 2) + 7) / (8 * (6 - 1))   
((2.5 + 3.2) * (4.7 - 1.8)) / ((5.3 + 2.1) - (6.4 * 0.5)) + (9.6 - 10.3) * 11.2


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谷天楽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值