1.我们平时进行运算的表达式如下所示:
9 + ( 3 - 1 ) * 5
这种运算和逻辑思维方式十分符合我们的思维方式,但是并不符合计算机的运算方式,所以我们要有一种符合计算机运算方式的方法。我们平时所看见的运算表达式称为中缀表达式(可以简单的理解为运算符号都位于一个表达式的中间)。
2.一位IT的大牛提出一种将运算符号放在表达式后面的方法,这样更有利于计算机进行计算,我们称之为后缀表达式。
假如我们的计算式为:
9 + ( 3 - 1 ) * 5
那么使用后缀表达式的方法就是:
931-5*+
3.假如我们要完成一个计算器,那么我们首先要按照正常的中缀表达式的方式来完成输入,然后计算机再按照后缀表达式来完成计算,这里面就涉及了如何将中缀表达式转换为后缀表达式的问题。
4.解决方法:
我们首先要遍历整个字符串,如果遇见的数字,那么就直接输出,如果遇见符号,那么首先判定符号的优先级是否高于栈顶元素的优先级,如果高于栈顶元素的优先级,那么将符号压入栈,如果符号的优先级低于栈顶元素的优先级,那么将栈顶元素弹出。如果遇见左括号,那么直接将左括号压进栈,如果遇见右括号,那么将栈顶元素弹出并输出,直至找到了与之匹配的左括号。当遍历结束的时候我们将所有的栈中的元素都弹出。
5.基本算法框架
1.程序的具体实现主要采用链式栈的数据结构,同时链式栈是通过复用单向链表来实现的,这些在点击打开链接这篇博文中都有讲解。所以算法实现的主要部分都是在主函数中实现的,也就是我所谓的上层函数。
2.首先采用在主函数调用其他函数的方式来实现整个程序的运行
int main(int argc, char *argv[]) { houzhui("9 + ( 3 - 1 ) * 5"); return 0; }
在主函数中调用houzhui函数,我们传递给houzhui函数的是一个字符串,等到真正的进入houzhui函数的饿时候我们的字符串也就变成了一个字符数组的首地址。
3.houzhui函数的主要实现
(1)首先要建栈,我们采用的仍然是链式栈,链式栈的实现方式是复用单向链表
LinkStack* stack = LinkStack_Create();
(2)因为我们要不断的扫描整个字符数组,所以采用while大循环,直至'\0',整个字符数组的结束。
while(a[i] != '\0')
(3)根据算法,我们首先判定被扫描的字符是否为数字,如果是数字,那么就直接输出。
if (ifnumber(a[i]) == 1) { output(a[i]); }
在这里我们从main()函数中传递进来的字符串首地址已经被转为数组首地址从而进行数组操作了。
(4)第二个判定主要判定是否为左括号,如果是左括号,那么直接将左括号压入栈。
else if (isleft(a[i]) == 1) { LinkStack_Push(stack, (void*)(int)(a[i])); }
在这个压入栈的函数中,我们压入栈的应该是地址,但是这里的类型转化我没有搞清除,首先我们的a[i]应该是char类型的,那么通过(int)把char类型强制类型转化为int类型,然后再强制类型转化为(void*)类型压入栈,也就是程序的主要目的是要将一个整形的指针压入栈,看Pop函数我们就可以知道,最后弹出的仍然是char类型,而且这个char类型也是经过强制类型转换的。但是为什么要以int类型压入栈中呢?
(5)第三个判定的主要作用是判定符号是否为符号,如果为符号的话就要判定符号的优先级是否小于栈顶元素的优先级,如果小于栈顶元素的优先级,那么就弹出栈顶元素,否则将这个符号压入栈。
else if (fuhao(a[i]) == 1) { while (youxian(a[i]) <= youxian((char)(int)LinkStack_Top(stack))) { output((char)(int)LinkStack_Pop(stack)); } LinkStack_Push(stack, (void*)(int)(a[i])); }
通过调用这里的output函数,可以发现我们要将栈顶元素输出,将栈顶元素输出首先要经过一此int类型的强制转换,然后再强制类型转换为char类型。
(6)接着判定是否为右括号,如果为右括号,那么就要将栈顶元素输出,直至我们栈顶元素为(为止。
else if (isright(a[i]) == 1) { while ((char)(int)LinkStack_Top(stack) != '(') { output((char)(int)LinkStack_Pop(stack)); } LinkStack_Pop(stack); }
(7)最后程序结束的标志是程序运行到数组中的'\0'
while( (LinkStack_Size(stack) > 0) && (a[i] == '\0') ) { output((char)(int)LinkStack_Pop(stack)); }
(8)程序的实现过程中还有若干个其他的子函数,输出函数output();判定是否为数字的函数ifnumber();判断是否为左括号的函数isleft();判断是否为右括号的函数isright();判断是优先级的函数youxian();
1.程序实现所复用的链式栈的详细代码请看点击打开http://blog.csdn.net/cheyuxuan/article/details/9852779和http://blog.csdn.net/cheyuxuan/article/details/10349867部分,这里粘的是主函数的实现部分。
2.主函数实现部分代码
#include <stdio.h> #include <stdlib.h> #include "1.h" /******************************************************************************* *函数名:ifnumber *参数:char a 传进来的数组元素 *返回值:int类型 如果是数字,那么返回1,不是返回0 *功能:判断传进来的字符是否是数字 *******************************************************************************/ int ifnumber(char a) { int ret = 0; ret = (a >= '0') && (a <= '9'); return ret; } /******************************************************************************* *函数名:fuhao *参数:char a 传进来的数组元素 *返回值:int类型 如果是符号,那么返回1,不是返回0 *功能:判断传进来的字符是否是符号 *******************************************************************************/ int fuhao(char a) { int ret = 0; ret = (a == '+') || (a == '-') || (a == '*') || (a == '/'); return ret; } /******************************************************************************* *函数名:isleft *参数:char a 传进来的数组元素 *返回值:int类型 如果是左括号,那么返回1,不是返回0 *功能:判断传进来的字符是否是左括号 *******************************************************************************/ int isleft(char a) { int ret = 0; ret = (a == '('); return ret; } /******************************************************************************* *函数名:isright *参数:char a 传进来的数组元素 *返回值:int类型 如果是右括号,那么返回1,不是返回0 *功能:判断传进来的字符是否是右括号 *******************************************************************************/ int isright(char a) { int ret = 0; ret = (a == ')'); return ret; } /******************************************************************************* *函数名:kongge *参数:char a 传进来的数组元素 *返回值:int类型 如果是空格,那么返回1,不是返回0 *功能:判断传进来的字符是否是空格 *******************************************************************************/ int kongge(char a) { int ret = 0; ret = (a == ' '); return ret; } /******************************************************************************* *函数名:youxian *参数:char a 传进来的数组元素 *返回值:int类型 定义了三种不同的优先级 *功能:将几种符号的优先级进行划分 *******************************************************************************/ int youxian(char a) { int ret = 0; if (a == '+') { ret = 2; } else if (a == '-') { ret = 2; } else if (a == '*' ) { ret = 3; } else if (a == '/') { ret = 3; } else { ret = 1; } return ret; } /******************************************************************************* *函数名:output *参数:char a 传进来的数组元素 *返回值:int类型 成功返回1 *功能:打印 *******************************************************************************/ int output(char a) { printf ("%c\n", a); return 1; } /******************************************************************************* *函数名:houzhui *参数:char a 传进来的数组元素 *返回值:int类型 成功返回1 *功能:中缀转后缀的算法主要实现部分 *******************************************************************************/ int houzhui(char *a) { int ret = 0; int i = 0; LinkStack* stack = LinkStack_Create(); while(a[i] != '\0') { if (ifnumber(a[i]) == 1) { output(a[i]); } else if (isleft(a[i]) == 1) { LinkStack_Push(stack, (void*)(int)(a[i])); } else if (fuhao(a[i]) == 1) { while (youxian(a[i]) <= youxian((char)(int)LinkStack_Top(stack))) { output((char)(int)LinkStack_Pop(stack)); } LinkStack_Push(stack, (void*)(int)(a[i])); } else if (isright(a[i]) == 1) { while ((char)(int)LinkStack_Top(stack) != '(') { output((char)(int)LinkStack_Pop(stack)); } LinkStack_Pop(stack); } else if (kongge(a[i]) == 1) { output(a[i]); } else { printf ("输入有误\n"); } i++; } while( (LinkStack_Size(stack) > 0) && (a[i] == '\0') ) { output((char)(int)LinkStack_Pop(stack)); } LinkStack_Destroy(stack); return ret; } int main(int argc, char *argv[]) { houzhui("9 + ( 3 - 1 ) * 5"); return 0; }
1.我们通过将平时我们习惯的中缀表达式转换为了计算机更加方便的计算,那么如何让计算机通过后缀表达式来完成四则运算呢?
2.解决方法:
遍历整个后缀表达式数组,如果遇见数字,那么让数字进栈。如果遇见符号,那么先从栈中弹出右操作数,再从栈中弹出左操作数,根据符号进行运算,将运算结果压入栈中,依次循环。整个四则运算结束的标志是:栈中的唯一数字为运算结果。
3.算法框架如下所示:
4.首先采用在主函数中调用其他函数的方式来实现整个程序的运行。
int main() { calcu("123*+"); return 0; }
在主函数中向calcu函数传递的是字符串,在calcu函数中接收的是一个字符串数组的首地址。
5.calcu函数
(1)我们要遍历整个字符串数组,所以这里采用while大循环,直到遍历到'\0',整个遍历结束。
while (a[i] != '\0')
(2)首先进行的判定是判定字符串数组中是否为数字,如果是数字的话,那么将元素压入栈。
if (ifnum(a[i]) == 1) { LinkStack_Push(stack, (void*)(int)zhuanhuan(a[i])); }
压入栈的函数中,我们首先调用了zhuanhuan()函数,这个zhuanhuan函数的作用是将字符数组转换为整型数组,然后向栈中压入这个整型数组的首地址。
转换函数的代码如下所示:
int zhuanhuan(char a) { int ret = 0; ret = a - '0'; return ret; }
(3)第二个判定是判定数组中的元素是否为符号,如果为符号,我们依次弹出栈中的两个元素,并对符号进行判定然后依次进行运算,最后再将运算结果压入栈中。
else if (iffuhao(a[i]) == 1) { c = (int)LinkStack_Pop(stack); d = (int)LinkStack_Pop(stack); switch (a[i]) { case '+': e = d + c; break; case '-': e = d - c; break; case '*': e = d * c; break; case '/': e = d / c; break; default: break; } LinkStack_Push(stack, (void*)(int)e); }
弹出栈的元素我们强制转为int类型,然后在压入栈的时候仍然是以整型数转换为void*类型进行压入栈中。
(4)程序运行结束的标志是:栈中还有一个元素并且我们的遍历操作已经进行到字符'\0'
if (LinkStack_Size(stack) == 1 && a[i] == '\0') { printf ("%d\n", LinkStack_Top(stack)); }
(5)程序中还涉及到了其他的子函数,判定是否为数字的函数ifnum();判定是否为符号的函数iffuhao();
1.程序实现所复用的链式栈的详细代码请看点击打开链接和点击打开链接部分,这里粘的是主函数的实现部分。
2.主函数的具体代码实现如下所示:
#include <stdio.h> #include <stdlib.h> #include "LinkStack.h" /******************************************************************************* *函数名:ifnum *参数:char a 传进来的数组元素 *返回值:int类型 如果是数字,那么返回1,不是返回0 *功能:判断传进来的字符是否是数字 *******************************************************************************/ int ifnum(char a) { int ret = 0; ret = (a >= '0') && (a <= '9'); return ret; } /******************************************************************************* *函数名:iffuhao *参数:char a 传进来的数组元素 *返回值:int类型 如果是符号,那么返回1,不是返回0 *功能:判断传进来的字符是否是字符 *******************************************************************************/ int iffuhao (char a) { int ret = 0; ret = (a == '+' || a == '-' || a == '*' || a == '/'); return ret; } /******************************************************************************* *函数名:zhuanhuan *参数:char a 传进来的数组元素 *返回值:int类型 返回把字符转换为数字的值 *功能:将字符常量转化为数字常量 *******************************************************************************/ int zhuanhuan(char a) { int ret = 0; ret = a - '0'; return ret; } /******************************************************************************* *函数名:calcu *参数:char *a 传进来的字符串的首地址 *返回值:int类型 成功返回1,不成功返回0 *功能:后缀表达式的算法实现的主要部分 *******************************************************************************/ int calcu(char *a) { int i = 0; int ret = 0; int c = 0; int d = 0; int e = 0; LinkStack *stack = LinkStack_Create(); while (a[i] != '\0') { if (ifnum(a[i]) == 1) { LinkStack_Push(stack, (void*)(int)zhuanhuan(a[i])); } else if (iffuhao(a[i]) == 1) { c = (int)LinkStack_Pop(stack); d = (int)LinkStack_Pop(stack); switch (a[i]) { case '+': e = d + c; break; case '-': e = d - c; break; case '*': e = d * c; break; case '/': e = d / c; break; default: break; } LinkStack_Push(stack, (void*)(int)e); } else { printf ("输入有误\n"); break; } i++; } if (LinkStack_Size(stack) == 1 && a[i] == '\0') { printf ("%d\n", LinkStack_Top(stack)); } else { printf ("发生错误\n"); } return ret; } int main() { calcu("123*+"); return 0; }
1.从中缀表达式转换为后缀表达式和后缀表达式的代码都已经实现,两个程序相结合转换为一个真正的可以进行四则运算是最后的一步,由于最近的事情较多,同时传递参数部分已经实现,所以这里暂时给自己留一个作业,真正的计算器还没有实现。
2.在中缀转后缀和后缀运算的函数中,涉及到很多类型转换,这里还有部分疑问。