栈的应用2:中缀转后缀及计算

栈的应用2:中缀转后缀及计算

基本概念

  • 中缀表达式

计算机的本质工作就是做数学运算,比如计算机可以读入字符串:
“9 + (3 - 1) * 5 + 8 / 2”并计算。我们习惯的数学表达式叫做中缀表达式===》符合人类思考习惯

  • 后缀表达式
    波兰科学家在20世纪50年代提出了一种将运算符放在数字后面的后缀表达式。如下的实例更能说清楚这个概念:
5 + 4=> 5 4 +
1 + 2 * 3 => 1 2 3 * + 
8 + ( 31 ) * 5 => 8 3 15 * +  

中缀表达式符合人类的阅读和思维习惯,后缀表达式符合计算机的“运算习惯”

中缀表达式转后缀表达式

遍历中缀表达式中的数字和符号

  • 1.对于数字:直接输出到目标字符串
  • 2.对于符号:
    (1)左括号:直接进栈
    (2)运算符号:与栈顶符号进行优先级比较

    • 若栈顶符号优先级低:行符号直接入栈 (默认栈顶若是左括号,左括号优先级最低)
    • 若栈顶符号优先级不低:将栈顶符号弹出并输出到目标字符串,之后进栈

    (3)右括号:将栈顶符号弹出并输出到目标字符串,直到匹配左括号。右括号是不会入栈的。

    1. 空格:不作处理,跳过
    1. 其他非法字符:表达式错误

遍历结束:将栈中的所有符号弹出并输出到目标字符串,最后目标字符串就是一个后缀表达式。

这里写图片描述

计算后缀表达式

通过栈可以方便的将中缀形式变换为后缀形式。

遍历后缀表达式中的数字和符号

  • 1.对于数字:直接进栈
  • 2.对于符号:

    • 从栈中弹出右操作数
    • 从栈中弹出左操作数
    • 根据符号进行运算
    • 将运算结果压入栈中
  • 3.遍历结束:栈中的唯一数字为计算结果
    这里写图片描述

代码实现“基于链式栈-后缀表达式的四则混合运算计算器”

#include "linkstack.h"


/*计算字符串长度,不含结束符*/
int my_strlen(char *_str)
{
    char *str = _str;
    int len = 0;
    while (*str++ != '\0')
    {
        len++;
    }
    return len;
}


/*计算base的cnt次方*/
double my_pow(double base, int cnt)
{
    double ret = 1.0;
    while (cnt--)
    {
        ret *= base;
    }
    return ret;
}


/*将字符串转换成数字*/
double my_str2num(char *_str)
{
    int i = 0,len = 0;
    double res = 0.0;
    char *str = _str;
    /*计算出小数点前面整数的位数*/
    while (str[i] != '\0' && str[i++] != '.')
    {
        len++;
    }
    i = 0;

    /*计算出整数部分*/
    while (str[i] != '\0' && str[i] != '.')
    {
        res += (str[i++] - '0') * my_pow(10, --len);
    }

    str +=(i+1);//指向第一位小数
    len = 1;//记录小数部分位数
    i = 0;//遍历字符串下标变量

    //将小数部分累加到整数部分
    while (str[i] != '\0' && str[i] != '.')
    {
        res += (str[i++] - '0') * my_pow(0.1, len++);
    }

    /*返回结果*/
    return res;
}

/*判断是否是数字,小数点也看作是数字,是返回1否则返回0*/
int isNumber(char ch)
{
    if ((ch >= '0' && ch <= '9') || (ch == '.'))
    {
        return 1;
    }

    return 0;
}

/*判断是否是左括号,是返回1否则返回0*/
int isLeft(char ch)
{
    if (ch == '(')
    {
        return 1;
    }

    return 0;
}


/*判断是否是右括号,是返回1否则返回0*/
int isRight(char ch)
{
    if (ch == ')')
    {
        return 1;
    }

    return 0;
}


/*将字符串中的第一个数字提取出来放到缓冲区并得到对应的数值*/
#define MAX 1024//缓冲区1024个字节
double my_strtrip_num(char** _str)
{
    int i = 0;
    char* str = *_str;
    char dst[MAX] = { 0 };//初始化缓冲区
    double res = 0;

    /*遍历字符串的数字部分:该字符串必须是数字开头的*/
    while (str[i] != '\0' && isNumber(str[i]))
    {
        dst[i] = str[i];//将数字部分存入缓冲区,后续只处理缓冲区
        i++;
    }
    dst[i] = '\0'; //给缓冲区加上结束符
    res = my_str2num(dst);//将缓冲区得到的字符串转换成数值
    if (str[i] == '>')//如果是负数
    {
        res *= (-1);
    }
    str += i;//调节字符串的起始位置
    *_str = str;

    /*返回字符串的第一个数字*/
    return res;
}


/*判断是否是操作符*/
int isOperator(char ch)
{
    if (ch == '+' || ch == '-' ||
        ch == '*' || ch == '/' || ch == '%')
    {
        return 1;
    }
    return 0;
}


/*获取给定运算符的优先级*/
int priority(char ch)
{
    int ret = 0;
    switch (ch)
    {
    case '+'://加减优先级为1
    case '-':
        ret = 1;
        break;
    case '*'://乘除取模运算为2
    case '%':
    case '/':
        ret = 2;
        break;
    case '('://栈顶为左括号最低为0
    default:
        ret = 0;
    }

    return ret;//返回优先级
}

/*将中缀表达式转换为后缀表达式*/
int my_transform(char *str, char * dst)
{
    int i = 0, j = 0;//分别是源字符串和目的字符串的下标
    char tmp = '\0';//存放栈顶元素的临时变量
    LinkStack *stack = NULL;//存放操作符的栈

    int flag = 0;//遇到负数的标志
    char pre = '\0';//存放前一个有效字符(非空格)

    /*创建栈*/
    stack = (LinkStack *)LinkStack_Create();
    if (stack == NULL)
    {
        return -1;
    }

    /*遍历字符串*/
    while (str[i] != '\0')
    {
        /*1.数字*/
        if (isNumber(str[i]))
        {
            /*数字直接存入目的字符串*/
            dst[j++] = str[i];
        }
        else if (isLeft(str[i]))//2.左括号
        {
            /*左括号无条件入栈*/
            LinkStack_Push(stack, (void *)&str[i]);
        }
        else if (isRight(str[i]))//3.右括号
        {
            /*右括号涉及到出栈操作,需要保证栈里面有元素*/
            if (LinkStack_Size(stack) > 0){
                if (isNumber(dst[j - 1]))//只要遇到右括号,且栈非空,且目的字符串的前一个字符是数字
                                            //则需要考虑向目的字符串存入数值分隔符
                {
                    if (flag == 1)//如果当前数值是负数(负数的有效字符必定是伴随着右括号结束的)
                    {
                        dst[j++] = '>';//‘>’表示负数
                        flag = 0;//清空负数标志,下一次遇到负数在置位
                    }
                    else{
                        dst[j++] = '|';//‘|’表示非负数
                    }
                }

                /*由于前面已判断栈非空,所以可以读取栈顶元素*/
                tmp = *(char *)LinkStack_Top(stack);
                while (!isLeft(tmp))//直到遇到左括号,
                                    //遇不到左括号则一直弹出栈里面的运算符到目的字符串
                {
                    dst[j++] = tmp;//将栈顶运算符存入目标字符串
                    LinkStack_Pop(stack);//弹出运算符
                    if (LinkStack_Size(stack) <= 0)//如果在遇到左括号之前栈变空了
                                                    //则提示错误,表示缺少左括号
                    {
                        printf("no left\n");
                        return -1;
                    }

                    /*弹出栈顶元素以后还有其他元素,获取栈顶元素进行下一次循环判断是否是左括号*/
                    tmp = *(char *)LinkStack_Top(stack);
                }

                /*在弹出栈之前确保栈里面有元素*/
                if (LinkStack_Size(stack) > 0)
                    LinkStack_Pop(stack);
            }
            else{//如果栈为空却遇到了右括号,表达式错误,缺少左括号
                printf("no left\n");
                return -1;
            }
        }
        else if (isOperator(str[i]))//4.运算符
        {
            /*当前字符是‘-’表示负号,
            且前一个有效字符是左括号,
            则当前字符串部分是一个数字*/
            if (str[i] == '-' && pre == '(')
            {
                flag = 1;//对负数进行标记,
                        //直到负数存储到目标字符串完毕才清除标记
                i++;//如果是负数,则不对当前的'-'进行优先级比较,
                    //直接跳到下一个字符、进行下一次循环
                continue;
            }

            /*如果前一个有效字符也是运算符表示表达式错误*/
            if (isOperator(pre))
            {
                printf("exp error!\n");
                return -1;
            }

            /*遇到运算符也要考虑数值分隔符的存储*/
            if (isNumber(dst[j - 1]) )//如果目标字符串的前一个字符是数字,则先存储一个数值分隔符
                dst[j++] = '|';//这里不可能是负数,因为负数是遇到右括号的时候结束的
                                //如果是负数,遇到运算符之前会先遇到右括号,在右括号部分进行处理

            /*确保栈非空*/
            if (LinkStack_Size(stack) > 0){

                /*读取栈顶元素*/
                tmp = *(char *)LinkStack_Top(stack);

                /*如果当前字符的优先级低于或等于栈顶元素优先级,则会先弹出栈顶元素*/
                while (priority(tmp) >= priority(str[i]))//直到当前字符的优先级高于栈顶元素优先级时,
                                                        //直接将当前字符入栈
                {
                    dst[j++] = tmp;//将栈顶元素存入目标字符串
                    LinkStack_Pop(stack);//弹出栈顶元素
                    if (LinkStack_Size(stack) <= 0)//如果栈变空了则跳出循环,直接将当前运算符入栈
                        break;
                    tmp = *(char *)LinkStack_Top(stack);//非空再次获取栈顶元素进行下次循环
                }
            }

            /*只要遇到运算符,必然会将当前运算符入栈,
            只不过可能在入栈之前会将栈里面的元素弹出来一部分
            如果当前字符优先级比栈顶元素优先级高则直接入栈*/
            LinkStack_Push(stack, (void*)&str[i]);
        }
        else if (str[i] == ' '){//5.遇到空格,忽略直接跳到下一个字符、下一次循环
            i++;
            continue;
        }
        else{//6.除了数字、小数点、运算符和空格以外的字符
            printf("exp error!\n");
            return -1;
        }

        /*只要当前字符不是空格就是有效字符,记录下来;
        以便和下一个有效字符一起决定该部分是否是负数或者表达式是否非法*/
        if (str[i] != ' ')
        {
            pre = str[i];
        }

        /*下标更新*/
        i++;
    }

    /*字符串遍历已结束,将栈里面剩余的所有运算符全部弹出到目的字符串*/
    while (LinkStack_Size(stack) > 0)
    {
        tmp = *(char*)LinkStack_Pop(stack);//弹出
        if (tmp == '(')//如果遍历结束后栈里面剩下左括号,说明表达式有误,缺少右括号
        {
            printf("no right\n");
            return -1;
        }
        if (isNumber(dst[j - 1]))//如果目的字符串的前一个位置是数字,则先存储数字分隔符
        {
            dst[j++] = '|';
        }

        /*将当前运算符存入目的字符串*/
        dst[j++] = tmp;
    }

    /*销毁栈回收内存*/
    LinkStack_Destroy(stack);

    /*转换成功返回0,失败返回-1*/
    return 0;
}


/*计算出表达式的值*/
double express(double left,double right,char op)
{
    double ret = 0.0;

    switch (op)//判断何种运算
    {
    case '+':
        ret = left + right;
        break;
    case '-':
        ret = left - right;
        break;

    case '*':
        ret = left * right;
        break;
    case '/':
        ret = left / right;
        break;
    default:
        break;
    }
    return ret;//返回结果
}


/*计算后缀表达式*/
int calculate(char * _str,double * res)
{
    char * str = _str;//辅助指针变量,防止改变原指针
    LinkStack* stack = NULL;//用来存放数值的栈
    double tmp = 0.0;//存放局部计算结果的临时变量
    double * ptr_tmp = NULL;//用来指向每一个栈业务节点的内存空间
                            //因为栈的实现模型是栈里面的每一个元素实质是一个链表的业务节点
                            //然后链表节点包含了栈的业务节点,
                            //这个ptr_tmp指针指向的就是栈的业务节点
                            //同时意味着,这种模型的栈,针对每一个栈节点要实现分配内存,
                            //不能全部使用同一个临时变量存放栈节点的数据
                            //每进一个元素就为栈节点分配一次内存
                            //每弹出一个元素就回收一次内存
    double left = 0.0, right = 0.0;//用来存放局部表达式的左值和右值


    /*创建栈存放数值*/
    stack = LinkStack_Create();
    if (stack == NULL)//合法性检测
    {
        printf("create error!\n");
        return -1;
    }


    /*遍历字符串---后缀表达式*/
    while (*str != '\0')
    {
        if (isNumber(*str))//如果是数字直接入栈--先转换成具体数字再入栈,在字符串是ASCII码
        {
            ptr_tmp = (double*)malloc(sizeof(double));//入栈的每一个元素需要事先分配内存--
                                                        //入栈入的只是数据对象的首地址
            *ptr_tmp = my_strtrip_num(&str);//一旦检测到是一个数字字符,
                                            //就开始将字符串中的第一个数字提取出来,形如:"123","1"
                                            //并存入上述分配的内存空间
            LinkStack_Push(stack, (void *)ptr_tmp);//将得到的数字入栈--
                                                    //实际上就是将数字所在内存的首地址存入栈的业务节点
#ifdef DEBUG//调试信息
            printf("top:%lf\n", *(double*)LinkStack_Top(stack));
            printf("tmp: %lf\tstring : %s\n", *ptr_tmp, str);
#endif // DEBUG
        }
        else if (isOperator(*str))//如果是运算符
        {
            if (LinkStack_Size(stack) > 0)//确保栈非空
            {
                ptr_tmp = (double *)LinkStack_Pop(stack);//弹出栈顶元素--右操作数
                if (ptr_tmp != NULL){
                    right = *ptr_tmp;//取出实际的数值赋给临时辅助变量
                    free(ptr_tmp);//回收内存
                }

                if (LinkStack_Size(stack) > 0){//确保栈非空
                    ptr_tmp = (double *)LinkStack_Pop(stack);//弹出栈顶元素--左操作数
                    if (ptr_tmp != NULL){
                        left = *ptr_tmp;//取出实际的数值赋给临时辅助变量
                        free(ptr_tmp);//回收内存
                    }
                }
                else{//栈里面没有数字就遇到运算符--表达式错误
                    printf("---exp error!---\n");
                    return -1;
                }
                ptr_tmp = (double*)malloc(sizeof(double));//因为需要把局部表达式的结果压栈
                                                        //所以就需要为这个结果分配内存,当他弹出的时候会回收内存
                tmp = express(left, right, *str);//计算局部表达式的结果
                *ptr_tmp = tmp;//将结果存入上述分配的内存
#ifdef DEBUG//调试信息
                printf("left: %lf\tright: %lf\ttmp: %lf\n", left, right, tmp);

#endif
                /*将局部结果入栈*/
                LinkStack_Push(stack, (void *)ptr_tmp);
            }
        }
        else//如果不是数字和运算符表达式错误
        {
            printf("---exp error!---\n");
            return -1;
        }
        str++;//字符串指针调节,这里是个小技巧处理后缀表达式中的数值分隔符
            //前面提取出数字以后,指针指向了数值分隔符'|'或者'>',此时再++
            //就指向了下一个有效字符数字或者运算符
            //如果是遇到运算符,执行这里的++就是很普通的指向运算符后面的字符
            //由前面中缀转后缀的算法知道:运算符后面不会出现数值分隔符,
            //所以一定是有效字符(数字或者运算符)
    }


    /*栈里面只剩下一个数字且字符串遍历结束*/
    if ((LinkStack_Size(stack) == 1) && (*str == '\0'))
    {
        ptr_tmp = (double*)LinkStack_Pop(stack);//弹出栈顶元素--得到的是数据对象的首地址
        *res = *ptr_tmp;//取出实际的值
        free(ptr_tmp);//回收内存
    }
    ptr_tmp = NULL;//避免野指针
    LinkStack_Destroy(stack);
    //计算成功返回0
    return 0;
}

double My_Cal(char *src,char *dst,double * res)
{
    my_transform(src, dst);
    calculate(dst,res);
    printf("res : %lf\n", *res);
    return *res;
}
int main()
{
    char test[MAX] = { 0 };
    char src[MAX] = {0};
    double res = 0;
    int i = 0, tmp = 0;
    printf("%.3lf\n", my_pow(5.5, 3));

    printf("%d\n", my_strlen("ndjknjk"));

    printf("%.3lf\n", my_str2num("125.679"));


    my_transform("(2+5)/78*6", test);
    printf("%s\n", test);

    calculate(test, &res);
    printf("%.3lf\n", res);

    printf("Please input expression:\n");
    memset(test, 0, MAX);
    memset(src, 0, MAX);
    while ((tmp = getchar()) != EOF && tmp != '\r' && tmp != '\n')
    {
        if (!(isNumber(tmp) || isOperator(tmp) || isLeft(tmp) || isRight(tmp) || tmp == ' '))
        {
            printf("input error!\n");
            return -1;
        }

        src[i++] = tmp;
    }
    src[i] = '\0';
    My_Cal(src, test, &res);

    system("pause");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值