我们日常生活中 , 处处离不开计算 ,简单的买菜 , 去超市购物等 , 都需要计算消费的金额 , 我们一次性买了很多种类的菜 , 同一种饮料买了好几瓶 . 如果用手算的话 , 我们当然不可能笨到一个算式一个算式的去计算结果 . 我们一般会列一个混合运算的式子 , 然后再计算.
这时候 ,如果让一个一年级的小学生来算的话 ,他会感到一头雾水 , 到底先算哪一个 ,因为他现在只学了加减法 ,只会逐步计算.
我们学过算术优先级的同学 , 当然知道 , 我们要先算乘除, 再算加减 , 然后括号里的要优先运算 .
但是 , 计算机就是那个只会按顺序计算的小朋友 , 他只会计算两个数的运算,我们怎样给他赋予智慧呢? 就是怎样帮助他算出正确答案呢?
答案是: 既然他只会计算两个数 ,我们就只给他两个数 ,然后这两个数要进行什么运算 , 我们再按照符号的优先级的排序 , 告诉小朋友 ,这样他总会计算出最终结果的.
接下来 , 就是我们如何告诉小朋友 , 该算哪两个数字 , 这两个数字的运算符号是什么.
我们朝着这个目标设计算法:
现在第一步的问题是:
我们已经有了一个混合运算的表达式 , 如何把他转化为固定的运算 ,我们只用依次去取元素,然后运算就可以了
要想这样做 , 就需要先对混合运算的表达式做手脚了 ,我们先认识一下后缀表达式:
例子:
Exp = a × b + ( c - d / e) × f
后缀式
a b × c d e / - f × +
我们的后缀表达式 , 不改变操作数之间的相对次序 ,然后一次只运算两个数 ,
运算符的相对次序不同 ,并且后缀表达式已经考虑了运算法的优先级 ,去掉了括号, 只有操作数和运算符号
我们现在就用人脑来模拟一遍后缀式的运算过程
从开始开始遍历
a b × c d e / - f × +
取到 a 记下来
取到 b 记下来
看到 乘号 ,因为已经有两个数,就直接运算 a 乘 b 的结果得到 记作 x1 (抹掉运算过的算式)
X1 c d e / - f × +
接着看到 c 记下来
取到 d 记下来
取到 e 记下来
看到 除号 ,就近运算 d / e 得出结果 d/e (抹掉 d e) , 记作 X2
X1 c X2 - f × +
看到 减号 , 就近取 c 和 X2 , 计算 c - X2 ,记下结果 X3
X1 X3 f × +
看到 f 记下来
看到 × ,就近取 X3 乘以 f 得出结果记作 X4,
X1 X4 +
接着看到 + ,就近取两个数 , X1 + X4 ,记作 X5
X5
接着往下没有元素了,输出最后结果
我们的目标就是解决问题 , 我们假如现在已经设计出了 把一个混合运算式子转化为 后缀式
现在我们如何实现算法来实现上述 ,我们的人脑运算的过程呢?
下面给出构思:
我们用后缀式来运算的时候 , 其运算顺序完全符合原式优先级运算顺序 .
遍历后缀式的时候 , 我们从第一个字符开始遍历 ,遇到数字 ,就直接存放起来 , 遇到符号的话, 如果我们存放的够两个数 ,就直接就近取出两个数进行运算 ,结果再存起来
每次遇到符号 ,运算的总是就近的两个数,然后存储到起来
所以现在我们从上述描述中 , 提取出来 ,有规范性的信息
●遍历后缀式
●遇到数字记下来
●遇到符号,计算就近的两个数,然后接着存起来
●直到后缀式遍历完
●输出结果
承载数据的数据结构:
我们遇到符号,就直接就近取出两个数字进行运算 ,然后再插入
后进的元素 , 就是就近的元素 ,所以这很符合我们的栈的数据结构
所以说 , 我们遇到元素,直接插入栈 , 遇到符号 ,就直接从栈中取出两个元素运算完,然后再压入栈中,直到我们把后缀式遍历完 , 弹出栈中元素 ,就算出结果了.
接下来,我们就对构造这个运算,构建大致的框架 ,
我们对后缀表达式 postexp 求值
while(从 postexp 读取字符 ch ,ch != '\0') { 若 ch 为数字 ,将后续的所有数字构成一个整数 存放到数值栈st 中 若ch 为 "+", 则从数值栈 st 中退栈两个运算数 ,相加后进栈 st 中; 若ch 为 "-" , 则从数值栈st 中退栈两个运算数, 相减后进栈 st 中; 若 ch 为"*" , 则从数值栈 st 中退栈两个运算数 ,相乘后进栈 st 中; //若除数为零,则提示相应的错误信息 若 ch 为 "/", 则从数值栈 st 中退栈两个运算数 ,相除后进栈 st 中; } 若字符串 postexp 扫描完毕 ,则数值栈op中的栈顶元素就是表达式的值
以上后缀表达式求值的过程
下面进行算法实现,后缀表达式的求值:
(混合表达式给出 , 后缀表达式如何求? 我们下一讲细说, 现在的目标是先解决求值问题,我们要知道 ,有这一种混合表达式 ,可以让我们对混合运算实现求值 ,至于如何得出后缀式 ,我们下一讲根据优先级,依次程序化构思)
我们遍历后缀式 ,存字符 ,就近取两个数进行运算 ,需要用到栈的特性 ,后进先出 ,所以我们先构建栈,
来存储运算字符
传入 后缀表达式的字符串数组
float compvalue(char exp[]) {
//构建我们存储数值的栈 ,我们承载数据是用数组,然后我们定义的最大长度是多少呢?
这取决于我们的后缀式 ,我们为了一劳永逸,直接定义成 固定的长度 保证够用,MaxSize是后缀式的长度
struct { //定义数组长度 ,MaxSize为后缀式的元素个数 float data[MaxSize]; //定义栈顶 int top; }st;
接下来,定义栈顶 ,初始栈顶在 -1
st.top = -1;
定义遍历 后缀字符串的指针
char ch = exp[t];
定义遍历后缀字符串的计数器
int t = 0;
因为后缀式里面存放的都是字符 ,例如 23 +45 ,我们依次取 2 和 3 ,直到遇到 + ,才发现这是一个合并的整数 ,所以我们定义将数字字符转换成数值的变量
float d;
// 下面开始处理 exp 字符串
//从字符串开始开始遍历,直到遇到字符串结尾标志'\0' while (ch !='\0') { //判断遇到的字符类型 switch(ch) { //当遇到的是加号时,就从栈顶取出两个数字进行运算,结果入栈 case'+': //两个数字运算变成一个数,当然栈顶减一,变成 top-1 //我们在这里巧妙的利用栈指针,来运算,栈顶的数字是后入栈的,所以在运算符右边 st.data[st.top-1] = st.data[st.top-1]+st.data[st.top]; st.top--; //方便一会给 ch赋值 t++; //跳出switch,继续遍历下一个字符 break; //下面几个运算符同理 case'-': st.data[st.top-1] = st.data[st.top-1] - st.data[st.top] st.top--; //方便一会给 ch赋值 t++; break; case'*': st.data[st.top-1] = st.data[st.top-1]*st.data[st.top]; st.top--; t++; break; case'/': st.data[st.top-1] = st.data[st.top-1]/st.data[st.top]; st.top--; //方便一会给 ch赋值 t++; break; //以上都不是,则判断是否遇到数字字符 default: d = 0; //我们将数字字符的元素转换为数值存放到d中 //那我们怎样转换成数字呢 ,这些都是Ascll码. 例如 23 ,我们先读到2 ,然后ch 接着遍历,又读到3, 所以我们就知道,我们要把两个联合起来 //首先,我们可以给第一个数字乘以10 ,然后再加上后来的数字就可以了 //但是现在我们的主要问题是,字符如何转换为int类型,我们可以利用 //Ascll码是有序的,用字符减去'0'的Ascll码,之间的差值就是整形,正好等于字符的int类型 while(ch>='0' && ch<='9') { d = 10*d+ch-'0'; //ch = exp[t] 是数字,通过了检查,接着判断下一个字符 t++; ch = exp[t]; //直到遇到运算符,就跳出了循环,此时数字字符也串联起来了 //注意:此时跳出的时候 ch = exp[t]指向的是'#' } //因为跳出的时候,后缀式每个数值 后面是 '#' ,所以我们再次指向下一个字符 t++; //我们既然遇到了数值数据, 就把联合整理后的 d 入栈 //栈顶指针先上移 st.top++; //往栈顶指针赋值 st.data[st.top] = d; } //此时跳出 switch是要继续将 ch=exp[t] 里的字符进行对比 //进行遍历下一个字符,上面跳出后,已经对数组坐标进行了,处理,赋值对比即可 ch = exp[t]; }
//返回结果
return st.data[st.top]; }
设计求解程序
#include <stdio.h>
#include <stdlib.h>
#define MaxOp 100
#define MaxSize 100
int main()
{
char exp[]="(56-12)/(4+2)";
//定义存储后缀式的数组
char postexp[MaxSize];
//把算术式转换成后缀式
trans(exp,postexp);
printf("中缀式:%s\n",exp);
printf("后缀表达式:%s\n",postexp);
printf("表达式的值:%g\n",compvalue(postexp));
return 0;
}
完整代码如下:
#include <stdio.h>
#include <stdlib.h>
#define MaxOp 100
#define MaxSize 100
struct //设定运算符优先级
{
char ch; //运算符
int pri; //优先级
}
lpri[]= {{'=',0},{'(',1},{'*',5},{'/',5},{'+',3},{'-',3},{')',6}},
rpri[]= {{'=',0},{'(',6},{'*',4},{'/',4},{'+',2},{'-',2},{')',1}};
int leftpri(char op) //求左运算符op的优先级
{
int i;
for (i=0; i<MaxOp; i++)
if (lpri[i].ch==op)
{
return lpri[i].pri;
}
return 0;
}
int rightpri(char op) //求右运算符op的优先级
{
int i;
for (i=0; i<MaxOp; i++)
if (rpri[i].ch==op)
return rpri[i].pri;
return 0;
}
bool InOp(char ch) //判断ch是否为运算符
{
if (ch=='(' || ch==')' || ch=='+' || ch=='-'
|| ch=='*' || ch=='/')
return true;
else
return false;
}
int Precede(char op1,char op2) //op1和op2运算符优先级的比较结果
{
if (leftpri(op1)==rightpri(op2))
return 0;
else if (leftpri(op1)<rightpri(op2))
return -1;
else
return 1;
}
void trans(char *exp,char postexp[])
//将算术表达式exp转换成后缀表达式postexp
{
struct
{
char data[MaxSize]; //存放运算符
int top; //栈指针
} op; //定义运算符栈
int i=0; //i作为postexp的下标
op.top=-1;
op.top++; //将'='进栈
op.data[op.top]='=';
while (*exp!='\0') //exp表达式未扫描完时循环
{
if (!InOp(*exp)) //为数字字符的情况
{
while (*exp>='0' && *exp<='9') //判定为数字
{
postexp[i++]=*exp;
exp++;
}
postexp[i++]='#'; //用#标识一个数值串结束
}
else //为运算符的情况
switch(Precede(op.data[op.top],*exp))
{
case -1: //栈顶运算符的优先级低:进栈
op.top++;
op.data[op.top]=*exp;
exp++; //继续扫描其他字符
break;
case 0: //只有括号满足这种情况
op.top--; //将(退栈
exp++; //继续扫描其他字符
break;
case 1: //退栈并输出到postexp中
postexp[i++]=op.data[op.top];
op.top--;
break;
}
} //while (*exp!='\0')
while (op.data[op.top]!='=')
//此时exp扫描完毕,退栈到'='为止
{
postexp[i++]=op.data[op.top];
op.top--;
}
postexp[i]='\0'; //给postexp表达式添加结束标识
}
float compvalue(char exp[]) //计算后缀表达式的值
{
struct
{
float data[MaxSize]; //存放数值
int top; //栈指针
} st; //定义数值栈
float d;
char ch;
int t=0; //t作为exp的下标
st.top=-1;
ch=exp[t];
while (ch!='\0') //exp字符串未扫描完时循环
{
switch (ch)
{
case'+':
st.data[st.top-1]=st.data[st.top-1]+st.data[st.top];
st.top--;
t++;
break;
case '-':
st.data[st.top-1]=st.data[st.top-1]-st.data[st.top];
st.top--;
t++;
break;
case '*':
st.data[st.top-1]=st.data[st.top-1]*st.data[st.top];
st.top--;
t++;
break;
case '/':
if (st.data[st.top]!=0)
{
st.data[st.top-1]=st.data[st.top-1]/st.data[st.top];
}
else
{
printf("\n\t除零错误!\n");
exit(0); //异常退出
}
st.top--;
t++;
break;
default:
d=0; //将数字字符转换成数值存放到d中
while (ch>='0' && ch<='9') //为数字字符
{
d=10*d+ch-'0';
t++;
ch=exp[t];
}
t++;
st.top++;
st.data[st.top]=d;
}
ch=exp[t];
}
return st.data[st.top];
}
int main()
{
char exp[]="(56-20)/(4+2)-33+9*32"; //可将exp改为键盘输入
char postexp[MaxSize];
trans(exp,postexp);
printf("中缀表达式:%s\n",exp);
printf("后缀表达式:%s\n",postexp);
printf("表达式的值:%g\n",compvalue(postexp));
return 0;
}