带括号的表达式求值

本来觉得这个问题应该算是很简单的,把自己写的垃圾发上来似乎有些献丑的感觉,但是鉴于此题对于新生来说还算是有点综合性,所以斗胆撰文,程序、思路多有缺漏之处,还请指正


问题描述

输入一个只含整数、+、-、*、/(整除)、括号的表达式,以回车符结束。求其值。
保证括号闭合、不存在其他字符,不存在小数,不存在乘号省略不写。中间可能存在若干空格。

样例1:				输出1
1+(1+2)				4
样例2:				输出2
2*(2+    2)+1		9

算法思路

定义两个栈:运算符栈和运算数栈。分别专门用于存储运算符和运算数。
每次对表达式从左向右扫描,扫描到数字直接提取入栈,对于扫描到的运算符,和运算符栈的栈顶进行比较,若:

  1. 当前扫描到的运算符的优先级大于或等于运算符栈的栈顶运算符,则将当前扫描到的这个运算符加入运算符栈,先不进行计算。
  2. 当前扫描到的运算符的优先级小于运算符栈的栈顶运算符,不断从运算符栈的栈顶取出一个元素和从运算数栈栈顶取出两个元素,这三者进行计算,所得结果压入运算数栈。直到运算符栈的栈顶运算符的优先级不满足本条的条件。
  3. 遇到右括号,一直运算直到遇到左括号。
  4. 若扫描到结尾仍然有运算符和操作数,直接取出一个运算符和两个操作数进行运算,直到清空。
  5. 为了运算规则的统一,我的建议是在输入的式子外面套一整个括号,例如输入的是1+1,你计算的式子要是(1+1),这样如果你是模块化设计,是不需要特判的,逻辑风格统一。

其中,运算符的优先级是:
运算符优先级

我知道你看了一遍还是毫无头绪,没有关系,我们模拟一下:

input: 2+2+2*(2*(2+2*2))
从左向右扫描:
操作对象 操作	运算符栈		操作数栈				解说
2 		入栈  	NULL		2
+ 		入栈	+			2
2 		入栈	+			2	2
+ 		入栈	+	+		2	2
2 		入栈	+	+		2	2	2
* 		入栈	+ + *		2	2	2			虽然优先级比+高,但是其右边的数不知道,所以先不能算
(		入栈	+ + * (		2	2	2			左括号必然是最小的优先级,因为碰到它你必然有一个运算符需要等括号算完再算,所以谁都不能比它先算
2		入栈	+ + * (		2	2	2	2
*		入栈	+ + * ( *	2	2	2	2		同样,右边的数不知道,不能算
(		入栈	+ + * ( * ( 2	2	2	2
2		入栈	+ + * ( * ( 2	2	2	2	2
+		入栈	+ + * ( * (+2	2	2	2	2
2		入栈	+ + * ( * (+2	2	2	2	2	2
*		入栈	++*(*(+*	2	2	2	2	2	2
2		入栈	++*(*(+*	2	2	2	2	2	2	2
)		计算	++*(*(+		2	2	2	2	2	4
		计算	++*(*(		2	2	2	2	6
		清除左括号 ++*(*		2	2	2	2	6
)		计算    ++*(		2	2	2	12
		清除左括号 ++*		2	2	2	12
##      扫描到结尾,开始清空运算符栈
				++			2	2	24
				+			2	26
				NULL		28

好了,如果你跟着这个看了一遍,想必是大概了解了算法,我们直接看代码。写的很垃圾,因为一开始不是准备发博客的,就是写着自己用。注释很多这里就没什么说的了。
需要注意的就是,括号的处理。你是扫到右括号了,

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

// 定义两个栈,oper是运算符栈,num是数字栈
struct Stack
{
    
    
    char oper[1024];
    int num[1024];

    // 两个栈的栈顶指针
    // 事先约定:指向当前的栈顶元素,如果你想要入栈一个元素,先++再入栈
    int p_oper; //point to the top of stack, if you want to push sth in stack, please increase first.
    int p_num;  //point to the top of stack, if you want to push sth in stack, please increase first.
};

// 可以打印每一步的栈内信息,建议根据其输出仔细理解
void debug_function(struct Stack *stack, char ch, int i, int n)
{
    
    
    printf("\n%d:\n%d  number stack =   ", n, stack->p_num);
    for (int i = 0; i<=stack->p_num;i++)
        printf("%d ", *(stack->num+i));
    printf("\n%d  operator stack =   ", stack->p_oper);
    for (int i = 0; i<=stack->p_oper;i++)
        printf("%c ", *(stack->oper+i));
    printf("\nnow string[%d] is %c\n", i, ch);
    printf("\n\n");
}

// 返回运算符的级别
char get_oper_level(char a)
{
    
    
    if (a == ')')
        return 4;
    if ((a == '*') || (a == '/'))
        return 3;
    if ((a == '+') || (a == '-'))
        return 2;
    if (a == '(')
        return 1;
}

//return TRUE if a > b, a should be a operator in stack
//work_mode:
//In mode 0, can get a>b?; in mode 1, can get delete_bracket_flag;In mode 2, can reset delete_bracket_flag;
// 如果a>b,返回true,其中a是操作数栈顶元素
// 具有三种工作模式:0可以比较操作符优先级,1是获取是否需要清空括号,2是重置清除括号标志位
char oper_cmp(char a, char b, char work_mode)
{
    
    
    // 这个标志位是为了表明,当前我们扫描式子的指针是不是在括号里面
    static char in_brackets_flag = 0;
    // 这个标志位是为了表明,是否需要一直清空
    static char delete_bracket_flag = 0;

    if (work_mode == 1)
        return delete_bracket_flag;

    if (work_mode == 2){
    
    
        delete_bracket_flag = 0;
        return 6;
    }


    if (b == '(')
        return 0;

    //match the bracket, change mode
    if ((a == '(' && b == ')') && (in_brackets_flag == 1)){
    
    
        in_brackets_flag = 0;
        delete_bracket_flag = 1;    //remain work function should remove 
        return 0;
    }

    //now in brackets, always calacute
    if (b == ')' && (in_brackets_flag == 1))
        return 1;

    //find right bracket, change mode
    if (b == ')' && (in_brackets_flag == 0)){
    
    
        in_brackets_flag = 1;
        return 1;
    }

    if (get_oper_level(a) > get_oper_level(b))
        return 1;
    return 0;
}

void calc_ans(int* temp_a, int* temp_b, int* ans, char oper)
{
    
    
    switch (oper)
    {
    
    
    case '+':
        *ans = *temp_a + *temp_b;
        printf("calc: %d + %d = %d\n", *temp_a, *temp_b, *ans);
        break;
    case '-':
        *ans = *temp_a - *temp_b;
        printf("calc: %d - %d = %d\n", *temp_a, *temp_b, *ans);
        break;
    case '*':
        *ans = *temp_a * *temp_b;
        printf("calc: %d * %d = %d\n", *temp_a, *temp_b, *ans);
        break;
    case '/':
        *ans = *temp_a / *temp_b;
        printf("calc: %d / %d = %d\n", *temp_a, *temp_b, *ans);
        break;
    default:
        break;
    }
}

void work(int* len, char *string, struct Stack *stack)
{
    
    
    int i = 0;  //the pointer of the string 字符串指针
    while (i <= (*len)){
    
    
        //if string[i] is number
        // 如果当前所指的是一个数字,那么先把这个数字提取出来
        if ((*(string + i) >= '0') && (*(string + i) <= '9')){
    
    

            //start to get this number
            // 开始提取数字
            int temp = 0;
            // 直到扫描到数字结尾,即下一个运算符,非数字
            while ((*(string + i) >= '0') && (*(string + i) <= '9')){
    
    
                // 这是基操了,自己看
                temp = temp + ((*(string + i)) - 48);
                temp = temp * 10;
                i++;
            }
            temp = temp / 10;

            // 加入操作数栈
            stack->p_num++;    //increase p of number stack
            *(stack->num + stack->p_num) = temp;    //push this number in stack
            printf("Get number = %d\n", temp);
        }

        debug_function(stack, *(string + i), i, 1); //打印调试信息
        //if string[i] is operator
        // 如果当前是运算符
        if (*(string+i) == '(' || *(string+i) == ')' || *(string+i) == '+' || 
         *(string+i) == '-' || *(string+i) == '*' || *(string+i) == '/'){
    
    
            
            printf("now operator = %c\n", *(string + i));
            //keep calacute until this operator's level is bigger than operator which is on top of the stack.
            //ONLY promise the level of top of stack is smaller than now.
            // 一直运算,直到当前运算符的优先级大于栈顶运算符。
            while(oper_cmp(*(stack->oper + stack->p_oper), *(string+i), 0) == 1){
    
    
                printf("start calcuate!!\n");
                //really bigger, it's time to calcuate
                //确实优先级高,那么计算,取两个操作数
                int temp_b = *(stack->num + stack->p_num);
                stack->p_num--;
                int temp_a = *(stack->num + stack->p_num);
                stack->p_num--;
                int ans = 0;

                calc_ans(&temp_a, &temp_b, &ans, *(stack->oper + stack->p_oper));
                stack->p_num++;
                *(stack->num + stack->p_num) = ans;

                stack->p_oper--;//pop the top of the operator stack 弹出栈顶运算符
                debug_function(stack, *(string + i), i, 2);
            }

            //now calc had done, we should check if there still a left bracket left
            //如果出了上面那个while循环,只能保证当前运算符的优先级大于栈顶运算符,但是可能还剩一个左括号
            //需要查询现在是不是在括号里面,则调用模式1
            if (oper_cmp('s', 'j', 1) == 1){
    
    
                stack->p_oper--;    //delete the left bracket   删除栈顶的左括号
                oper_cmp(' ', ' ', 2);  //tell function the left bracket had been removed   重置需要删除左括号的标记位
                printf("removed matched left bracket\n");
            }

            // 右括号直接入栈
            if (*(string+i) != ')'){
    
    
                printf("push in '%c' in stack\n", *(string + i));
                stack->p_oper++;    //increase p of operator stack
                *(stack->oper + stack->p_oper) = *(string + i);    //push this operator in stack
            }

            //all calc had done, write the output answer!
            // 运算符栈的指针指向-1代表计算完毕,输出答案
            if (stack->p_oper == -1){
    
    
                printf("ans = %d\n", *(stack->num + stack->p_num));
                break;
            }

            i++;
        }
         debug_function(stack, *(string + i), i, 3);
    }
    printf("len = %d %s\n", (*len), string);
}

//自己的读整行的函数
void my_readline(char *string, int* len)
{
    
    
    char ch;
    while (ch = getchar()){
    
    
        if (ch == '\n') break;
        if (ch == ' ') continue;
        (*len)++;
        *string = ch;
        string++;
    }
    *string = ')';
    return;
}

// 连续输入多个运算式需要清空栈、输入缓冲区等等,重置指针位置,不要忘了。
void flash(char *string, struct Stack *stack, int* len)
{
    
    
    (*len) = 0;

    memset(stack->oper, '\0', 1024);
    memset(stack->num, 0, sizeof(stack->num));
    stack->p_num = 0;
    stack->p_oper = 0;
    *(stack->oper + stack->p_oper) = '(';
    memset(string, '\0', 1024);
    // 清空输入缓冲区
    fflush(stdin);
    return;
}

int main()
{
    
    
    char *string = (char*)malloc(1024);
    struct Stack stack;
    int len = 0;
    flash(string , &stack, &len);
    
    while(1)
    {
    
    
        my_readline(string, &len);
        work(&len, string, &stack);
        flash(string, &stack, &len);
    }

    return 0;
}

//一开始是在ubuntu虚拟机里面写的,没装中文输入法所以有很多英文注释。

// Author: Harbin Engineering University - Information and Communication Collage - Shi JiaHao.
// If you copy or download my program, please remain these declare before or at the end of your program.

猜你喜欢

转载自blog.csdn.net/m0_51194302/article/details/127364941