Pop(Optr, e); //出栈元素e
while (e != '(') //不为'('时循环
{
postexp[i++] = e; //将e存放到postexp中
Pop(Optr, e); //继续出栈元素e
}
exp++; //继续扫描其他字符
break;
case '+': //判定为加或减号
case '-':
while (!StackEmpty(Optr)) //栈不空循环
{
GetTop(Optr, e); //取栈顶元素e
if (e != '(') //e不是'('
{
postexp[i++] = e; //将e存放到postexp中
Pop(Optr, e); //出栈元素e
}
else //e是'(时退出循环
break;
}
Push(Optr, \*exp); //将'+'或'-'进栈
exp++; //继续扫描其他字符
break;
case '\*': //判定为'\*'或'/'号
case '/':
while (!StackEmpty(Optr)) //栈不空循环
{
GetTop(Optr, e); //取栈顶元素e
if (e == '\*' || e == '/') //将栈顶'\*'或'/'运算符出栈并存放到postexp中
{
postexp[i++] = e; //将e存放到postexp中
Pop(Optr, e); //出栈元素e
}
else //e为非'\*'或'/'运算符时退出循环
break;
}
Push(Optr, \*exp); //将'\*'或'/'进栈
exp++; //继续扫描其他字符
break;
default: //处理数字字符
while (\*exp >= '0' && \*exp <= '9') //判定为数字
{
postexp[i++] = \*exp;
exp++;
}
postexp[i++] = ' '; //用#标识一个数值串结束
}
}
while (!StackEmpty(Optr)) //此时exp扫描完毕,栈不空时循环
{
Pop(Optr, e); //出栈元素e
postexp[i++] = e; //将e存放到postexp中
}
postexp[i] = '\0'; //给postexp表达式添加结束标识
DestroyStack(Optr); //销毁栈
}
>
> 看完这串代码,不用猜,我知道你已经已经想点下浏览器中这个网页的【×】了。但是何妨不听我讲完再说
>
>
>
* 这里我们需要用到顺序的结构实现原理SqStack,代码在最后给出,这里就展示转换的算法部分
* 整体浏览,这串代码处于一个while循环之中,也就是对给出的字符串进行的一个遍历,【!= ‘\0’】在C语言中表示还没到字符串结尾
* 在大层的while循环之下是一个switch()分支判断,判断的就是当前所遍历的exp,在英文中是expression【表达式】,上面讲到的postexp就是postexpression【后缀表达式】,我们的目的就是要去exp中遍历每一个字符,然后将符合条件的字符按照后缀表达式的法则以及堆栈的原理一个个地放入postexp中,然后我们来分别说说每个case的判断
* 首先是左括号【(】,这个直接进栈即可,exp++表示将遍历下一个字符,break表示的跳出当前的case分支,若是不写这一句,则程序会一直往下走,直到下一个break才会跳出
case '(': //判定为左括号 Push(Optr, '('); //左括号进栈 exp++; //继续扫描其他字符 break;
* 接着是右括号【)】,上面说过了,若是碰到右括号,则开始出栈栈顶元素,知道栈顶元素为左括号【(】时,while体中的语句就是将当前出栈的元素放入postexp后缀表达式中,i++表示为下一个位置留出位置,然后Pop出栈即可,循环结束后继续扫描下一个字符
case ')': //判定为右括号 Pop(Optr, e); //出栈元素e while (e != '(') //不为'('时循环 { postexp[i++] = e; //将e存放到postexp中 Pop(Optr, e); //继续出栈元素e } exp++; //继续扫描其他字符 break;
* 然后是【+】【-】,它们的循环条件是栈不为空,首先去获取栈顶元素,若获取的元素不为左括号【(】,则表示子表达式没结束,不停地将栈顶元素放入postexp中,然后出栈。若是碰到了左括号【(】,则跳出当前循环,循环后将当前的【+】【-】入栈,继续扫描下一个字符
case '+': //判定为加或减号 case '-': while (!StackEmpty(Optr)) //栈不空循环 { GetTop(Optr, e); //取栈顶元素e if (e != '(') //e不是'(' { postexp[i++] = e; //将e存放到postexp中 Pop(Optr, e); //出栈元素e } else //e是'(时退出循环 break; } Push(Optr, *exp); //将'+'或'-'进栈 exp++; //继续扫描其他字符 break;
* 其次就是【\*】【/】,同样,也是在栈不空时循环,获取到栈顶元素,若是碰到相同的乘号或除号,则将它们放入postexp中,若是没有,则跳出循环,直接将它们放入栈中即可
case '*': //判定为'*'或'/'号 case '/': while (!StackEmpty(Optr)) //栈不空循环 { GetTop(Optr, e); //取栈顶元素e if (e == '*' || e == '/') //将栈顶'*'或'/'运算符出栈并存放到postexp中 { postexp[i++] = e; //将e存放到postexp中 Pop(Optr, e); //出栈元素e } else //e为非'*'或'/'运算符时退出循环 break; } Push(Optr, *exp); //将'*'或'/'进栈 exp++; //继续扫描其他字符 break;
* 最后就是另外的数字情况的判断,若为数字字符,直接将其放入postexp中即可,然后在每个数字后加一个空格来进行标识
default: //处理数字字符 while (*exp >= '0' && *exp <= '9') //判定为数字 { postexp[i++] = *exp; exp++; } postexp[i++] = ' '; //用空格标识一个数值串结束
* 最后面的这段就是处理最后栈中还剩余的元素,将其依次出栈放入postexp即可,直到栈为空为止,最后别忘了postexp也是一个字符串,既然是字符串最后就要手动加上’\0’,就是尾插法最后要的r - >next = NULL是一个道理
while (!StackEmpty(Optr)) //此时exp扫描完毕,栈不空时循环 { Pop(Optr, e); //出栈元素e postexp[i++] = e; //将e存放到postexp中 } postexp[i] = '\0'; //给postexp表达式添加结束标识 DestroyStack(Optr); //销毁栈
>
> 看完了上面的细致讲解,难道你还想退出吗,不想的话就继续跟着我来吧🚶
>
>
>
## ✒闭隐:如何对后缀表达式进行求值?【⭐⭐⭐】
>
> 将中缀表达式转换为后缀表达式后,只是完成了我们的第一步,也就是有了一个模型了,接下去就要将其细心雕刻直至完美🍁
>
>
>
* 对后缀表达式的求值并不是很难,但是也需要涉及到堆栈FILO的原理,这需要你对Stack非常得熟悉
* 这里的顺序栈数据结构体的数据类型要记得换一下,不可以用char了,前面我们是在遍历字符,所以用char,但是这里是要去求值,但是数字可能会是小数,所以最好声明成double类型,这个不难,只需要改一下各个数据类型即可
### 🌳运算规则
>
> 首先一样,我们要了解一下其运算规则
>
>
>
* 后缀表达式求值的算法是遍历后缀表达式,如果遇到运算数,那么运算数入栈
* 如果遇到运算符,那么**弹出栈里面两个元素,先弹出的是右运算数,后弹出的是左运算数**,计算运算结果,然后将结果入栈。最后遍历到后缀表达式末尾,当结果只有一个元素时,就是答案
double compvalue(char* postexp) //计算后缀表达式的值 { double d, a, b, c, e; SqStack1* Opnd; //定义操作数栈 InitStack1(Opnd); //初始化操作数栈 while (*postexp != '\0') //postexp字符串未扫描完时循环 { switch (*postexp) { case '+': //判定为'+'号 Pop1(Opnd, a); //出栈元素a Pop1(Opnd, b); //出栈元素b c = b + a; //计算c Push1(Opnd, c); //将计算结果c进栈 break; case '-': //判定为'-'号 Pop1(Opnd, a); //出栈元素a Pop1(Opnd, b); //出栈元素b c = b - a; //计算c Push1(Opnd, c); //将计算结果c进栈 break; case '*': //判定为'*'号 Pop1(Opnd, a); //出栈元素a Pop1(Opnd, b); //出栈元素b c = b * a; //计算c Push1(Opnd, c); //将计算结果c进栈 break; case '/': //判定为'/'号 Pop1(Opnd, a); //出栈元素a Pop1(Opnd, b); //出栈元素b if (a != 0) { c = b / a; //计算c Push1(Opnd, c); //将计算结果c进栈 break; } else { printf("\n\t除零错误!\n"); exit(0); //异常退出 } break; default: //处理数字字符 d = 0; //将连续的数字字符转换成对应的数值存放到d中 while (*postexp >= '0' && *postexp <= '9') //判定为数字字符 { d = 10 * d + *postexp - '0'; postexp++; } Push1(Opnd, d); //将数值d进栈
break;
}
postexp++; //继续处理其他字符
}
GetTop1(Opnd, e); //取栈顶元素e
DestroyStack1(Opnd); //销毁栈
return e; //返回e
}
### 🌳关键代码讲解
* 这里主要是讲讲解一下如何去运算,看到【default】分支,当后缀表达式碰到数字字符的时候,这里需要将其转换为数字,也就是 - ‘0’,**因为’0’的ASCLL码值为48**。假设一个数为1,1的ASCLL码值为49,那么49-48也就是1
* 主要还是来解释一下为什么d 要乘10,大家可以看到,这是一个while循环,因为这是一个不断在找数字的过程,若是在遍历到数字字符后又遍历到了一个,那要如何运算呢,\*10就代表着上一个数字要为十位,而当前遍历数字在转换为数字后是为个位,将遍历到的所有数字字符转换后进行一个相加,**最后当遍历到的不是数字字符时,才将此相加完的数放入栈中**,然后退出循环去处理下一个字符
## 👀观镜:结果测试
### 🌳测试结果展示
>
> 首先,就是要在main函数中,传入对应的exp和postexp
>
>
>
* 尤其是对于这个exp,大家千万不要留空格,否则在中缀转后缀的时候就会出现数组越界异常
* 也就是因为这几个空格,我调试了一个下午😄
char exp[] = "(56 - 20)/(4 + 2)"; //错误!!! char exp[] = "(56-20)/(4+2)"; //正确!!!
char exp[] = "5+8*2+(3*9+6)*4"; char postexp[MaxSize]; trans(exp, postexp);
printf("中缀表达式为:%s\n", exp); printf("后缀表达式为:%s\n", postexp); printf("表达式的值为:%g\n", compvalue(postexp));
这就是最终的结果,看后缀表达式,和我们上面通过堆栈原理分析的完全吻合,大家算出来是这样吗😃
>
> 
>
>
>
### 🌳整体代码展示
//求简单表达式的值 #include <stdio.h> #include <stdlib.h> #define MaxSize 100 //--------------------------------------------------------- //--运算符栈基本运算--------------------------------------- //--------------------------------------------------------- typedef struct { char data[MaxSize]; //存放运算符 int top; //栈顶指针 } SqStack; void InitStack(SqStack*& s) //初始化栈 { s = (SqStack*)malloc(sizeof(SqStack)); s->top = -1; } void DestroyStack(SqStack*& s) //销毁栈 { free(s); } bool StackEmpty(SqStack* s) //判断栈是否为空 { return(s->top == -1); } bool Push(SqStack*& s, char e) //进栈元素e { if (s->top == MaxSize - 1) return false; s->top++; s->data[s->top] = e; return true; } bool Pop(SqStack*& s, char& e) //出栈元素e { if (s->top == -1) return false; e = s->data[s->top]; s->top--; return true; } bool GetTop(SqStack* s, char& e) //取栈顶元素e { if (s->top == -1) return false; e = s->data[s->top]; return true; } //---------------------------------------------------------
void trans(char* exp, char postexp[]) //将算术表达式exp转换成后缀表达式postexp { char e; SqStack* Optr; //定义运算符栈 InitStack(Optr); //初始化运算符栈 int i = 0; //i作为postexp的下标 while (*exp != '\0') //exp表达式未扫描完时循环 { switch (*exp) { case '(': //判定为左括号 Push(Optr, '('); //左括号进栈 exp++; //继续扫描其他字符 break; case ')': //判定为右括号 Pop(Optr, e); //出栈元素e while (e != '(') //不为'('时循环 { postexp[i++] = e; //将e存放到postexp中 Pop(Optr, e); //继续出栈元素e } exp++; //继续扫描其他字符 break; case '+': //判定为加或减号 case '-': while (!StackEmpty(Optr)) //栈不空循环 { GetTop(Optr, e); //取栈顶元素e if (e != '(') //e不是'(' { postexp[i++] = e; //将e存放到postexp中 Pop(Optr, e); //出栈元素e } else //e是'(时退出循环 break; } Push(Optr, *exp); //将'+'或'-'进栈 exp++; //继续扫描其他字符 break; case '*': //判定为'*'或'/'号 case '/': while (!StackEmpty(Optr)) //栈不空循环 { GetTop(Optr, e); //取栈顶元素e if (e == '*' || e == '/') //将栈顶'*'或'/'运算符出栈并存放到postexp中 { postexp[i++] = e; //将e存放到postexp中 Pop(Optr, e); //出栈元素e } else //e为非'*'或'/'运算符时退出循环 break; } Push(Optr, *exp); //将'*'或'/'进栈 exp++; //继续扫描其他字符 break; default: //处理数字字符 while (*exp >= '0' && *exp <= '9') //判定为数字 { postexp[i++] = *exp; exp++; } postexp[i++] = ' '; //用空格标识一个数值串结束 } } while (!StackEmpty(Optr)) //此时exp扫描完毕,栈不空时循环 { Pop(Optr, e); //出栈元素e postexp[i++] = e; //将e存放到postexp中 } postexp[i] = '\0'; //给postexp表达式添加结束标识 DestroyStack(Optr); //销毁栈 } //--------------------------------------------------------- //--操作数栈基本运算--------------------------------------- //--------------------------------------------------------- typedef struct { double data[MaxSize]; //存放数值 int top; //栈顶指针 } SqStack1; void InitStack1(SqStack1*& s) //初始化栈 { s = (SqStack1*)malloc(sizeof(SqStack1)); s->top = -1; } void DestroyStack1(SqStack1*& s) //销毁栈 { free(s); } bool StackEmpty1(SqStack1* s) //判断栈是否为空 { return(s->top == -1); } bool Push1(SqStack1*& s, double e) //进栈元素e { if (s->top == MaxSize - 1) return false; s->top++; s->data[s->top] = e; return true; } bool Pop1(SqStack1*& s, double& e) //出栈元素e { if (s->top == -1) return false; e = s->data[s->top]; s->top--; return true; } bool GetTop1(SqStack1* s, double& e) //取栈顶元素e { if (s->top == -1) return false; e = s->data[s->top]; return true; } //---------------------------------------------------------
double compvalue(char* postexp) //计算后缀表达式的值 { double d, a, b, c, e; SqStack1* Opnd; //定义操作数栈 InitStack1(Opnd); //初始化操作数栈 while (*postexp != '\0') //postexp字符串未扫描完时循环 { switch (*postexp) { case '+': //判定为'+'号 Pop1(Opnd, a); //出栈元素a Pop1(Opnd, b); //出栈元素b c = b + a; //计算c Push1(Opnd, c); //将计算结果c进栈 break; case '-': //判定为'-'号 Pop1(Opnd, a); //出栈元素a Pop1(Opnd, b); //出栈元素b c = b - a; //计算c Push1(Opnd, c); //将计算结果c进栈 break; case '*': //判定为'*'号 Pop1(Opnd, a); //出栈元素a Pop1(Opnd, b); //出栈元素b c = b * a; //计算c Push1(Opnd, c); //将计算结果c进栈 break; case '/': //判定为'/'号 Pop1(Opnd, a); //出栈元素a Pop1(Opnd, b); //出栈元素b if (a != 0) { c = b / a; //计算c Push1(Opnd, c); //将计算结果c进栈 break; } else { printf("\n\t除零错误!\n"); exit(0); //异常退出 } break; default: //处理数字字符 d = 0; //将连续的数字字符转换成对应的数值存放到d中 while (*postexp >= '0' && *postexp <= '9') //判定为数字字符 { d = 10 * d + *postexp - '0'; postexp++; } Push1(Opnd, d); //将数值d进栈
break;
}
postexp++; //继续处理其他字符
}
GetTop1(Opnd, e); //取栈顶元素e
DestroyStack1(Opnd); //销毁栈
return e; //返回e
} void test() { char exp[] = "5+8*2+(3*9+6)*4"; char postexp[MaxSize]; trans(exp, postexp);
printf("中缀表达式为:%s\n", exp);
printf("后缀表达式为:%s\n", postexp);
printf("表达式的值为:%g\n", compvalue(postexp));
}
int main(void) { test(); return 0; }
## 🛡开战:实战演练【LeetCode习题深入】
>
> 明白了如何将一个中缀表达式转换为后缀表达式,并且清楚了如何求值。接下来让我们到力扣上去找道题去练练手吧
>
>
>
[原题传送门——逆波兰表达式求值](https://gitee.com/vip204888)
### 🌳题目描述
根据 逆波兰表示法,求表达式的值。
有效的算符包括 +、-、\*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
注意 两个整数之间的除法只保留整数部分。
可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
>
> 输入:tokens = [“2”,“1”,“+”,“3”,“\*”]
> 输出:9
> 解释:该算式转化为常见的中缀算术表达式为:((2 + 1) \* 3) = 9
>
>
>
示例 2:
>
> 输入:tokens = [“4”,“13”,“5”,“/”,“+”]
> 输出:6
> 解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
>
>
>
示例 3:
>
> 输入:tokens = [“10”,“6”,“9”,“3”,“+”,“-11”,“*“,”/“,”*”,“17”,“+”,“5”,“+”]
> 输出:22
> 解释:该算式转化为常见的中缀算术表达式为:
> ((10 \* (6 / ((9 + 3) \* -11))) + 17) + 5
> = ((10 \* (6 / (12 \* -11))) + 17) + 5
> = ((10 \* (6 / -132)) + 17) + 5
> = ((10 \* 0) + 17) + 5
> = (0 + 17) + 5
> = 17 + 5
> = 22
>
>
>
### 🌳思路分析
* 首先对于逆波兰表达式,它其实和二叉树的后续遍历挺像的, 大家可以把运算符作为中间节点,按照后序遍历的规则画出一个二叉树。
* 我们已经了解了它的运算规则,题目已经是一个后缀表达式,所以我们不需要进行繁琐的转换,只要对给出的字符串进行一个遍历,看它是数字还是加减乘除运算符即可
### 🌳动画展示
>
> 温馨提示,微信手机端看不到
>
>
>
逆波兰表达式
### 🌳整体代码展示
class Solution { public: int evalRPN(vector& tokens) { stack st; for(int i = 0;i < tokens.size(); ++i) { if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") { int num1 = st.top(); st.pop(); int num2 = st.top(); st.pop();
if(tokens[i] == "+") st.push(num2 + num1);
if(tokens[i] == "-") st.push(num2 - num1);
if(tokens[i] == "\*") st.push((long long)num2 \* num1);
if(tokens[i] == "/") st.push(num2 / num1);
}
else
{
//若为数字字符,则将其转换为数字,方便后续运算
st.push(stoi(tokens[i]));
}
}
int ret = st.top();
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新