這里要學的程序主要用來實現一個功能——輸入表達式輸出結果,也就是一個計算器。效果如下:
這個程序主要有兩個步驟:1、把中綴表達式轉換為后綴表達式;2、計算后綴表達式的結果。
首先先明白幾個問題:
1、為什么要轉換為后綴表達式?因為后綴表達式容易實現計算機計算結果。(可以百度一下后綴表達式,又稱逆波蘭式)
2、怎么把中綴表達式轉換為后綴表達式?
3、怎么用后綴表達式輸出結果?
相信如果弄明白了上面幾個問題,有C語言基礎的同學就可以編出這個程序啦。而后面兩個問題都要用到一個數據結構——棧。實際上數據結構只是一種思想,一種思維,是連接人腦與計算機的橋梁(純屬個人杜撰= =)
好,那么我們先學怎么把中綴表達式轉換為后綴表達式。
為了簡化問題,我們不妨設輸入的數字都是一位整數,這樣讀入的時候只需要以字符的方式逐個讀入即可,也就是說讀入的必定是數字或者+-*/,讀到回車視為結束;另外不妨直接將結果打印到屏幕上,也就是說結果不必存儲(這樣我們可以更多地關注算法本身,而非輸入輸出方式的細枝末節)
類似上面的圖片,它的后綴表達式是 9 3 1 - 2 * + 5 2 / + 怎么得到的呢?下面開始講這個轉換的算法:
1、設置一個棧,棧底填一個"@"作為結束符(當然也可以用別的符號,至於為什么要用一會講優先級的時候會說);
2、開始逐個字符讀入;
3、如果讀入的是數字,直接打印printf("%c ",c);(數字后面打印一個空格作為間隔符,要不然沒法看了931-。。。不解釋)
4、如果讀入的是"("直接進棧;
5、如果讀入的是")",說明前面肯定讀入過一個"("找到這個左括號,把兩者之間的符號逐個彈棧(這里要說明的是,括號不必打印,因為后綴表達式沒有括號);
6、如果不是上面的幾種情況,那必定是+-*/中的一個啦,這樣來說就容易多了。本來這里應該說優先級的問題,可是我還是想先說說棧。講到這里,實際上大家應該也看出來了,棧實際上就是一個進棧和出棧的問題,這里也是一樣,對於表達式這里我們可以發現,這個棧是用來存儲符號的,()+-*/,所以我們只需要明白什么時候符號可以進棧,什么時候符號可以出棧就可以啦~上面已經講了兩個了,左括號的時候直接可以進棧,右括號的時候把兩者之間的出棧打印。而對於+-*/,只需要記住一個法則,對於讀入的這個符號,只有它比棧頂符號的優先級高的時候才可以進棧(優先級相同也不能進棧),而它不能進棧,就只能讓棧頂的出棧啦~所以不斷出棧,知道這個符號可以進棧,這個新讀入的符號就算處理完成啦。(優先級函數可以參照后面的程序代碼)
OK,按照上面的算法,掃描完一遍讀入的中綴表達式,就可以在屏幕上輸出后綴表達式啦。下面附上自制代碼(C語言,帶注釋):
1 #include<stdio.h> 2 #include<stdlib.h> 3 #define newp (stype *)malloc(sizeof(stype)) //定義一個申請棧地址的宏 4 typedef struct _stack{ 5 char dat; 6 struct _stack *next; 7 } stype; //建立棧類型 8 int tance(char x) //探測優先級 9 { 10 if(x=='+'||x=='-') return 0; 11 else if (x=='*'||x=='/') return 1; 12 else if (x=='@'||x=='('||x==')') return -1; 13 } 14 int main() 15 { 16 stype *s,*top; //棧指針和棧頂指針 17 char c; 18 s=newp; 19 s->dat='@'; 20 s->next=NULL; 21 top=s; 22 c=getchar(); //此后為讀取中綴表達式的部分,用字符一個一個的讀,直到讀到回車 23 while(c!='\n') 24 { 25 if (c>='0'&&c<='9') //如果讀入數字,直接打印 26 { 27 printf("%c ",c); 28 } 29 else if (c=='(') //如果是左括號,直接進棧 30 { 31 s=newp; 32 s->dat=c; 33 s->next=top; 34 top=s; 35 } 36 else if (c==')') //如果是右括號,匹配左括號,把兩者之間的棧內符號全部彈出 37 { 38 while (top->dat!='(') 39 { 40 s=top; 41 printf("%c ",top->dat); 42 top=top->next; 43 free(s); 44 } 45 s=top; 46 top=top->next; 47 free(s); 48 } 49 else //否則肯定是+-*/了 50 { 51 int a=tance(c); 52 int b=tance(top->dat); //比較該符號和棧頂符號的優先級 53 if (a>b) //如果大於直接壓進去 54 { 55 s=newp; 56 s->dat=c; 57 s->next=top; 58 top=s; 59 } 60 else //否則就把棧頂的符號一直彈出,直到彈到可以壓進去,然后壓進去(也就是說等於也不能壓進去) 61 { 62 while (a<=b) 63 { 64 s=top; 65 printf("%c ",top->dat); 66 top=top->next; 67 free(s); 68 b=tance(top->dat); 69 } 70 s=newp; 71 s->dat=c; 72 s->next=top; 73 top=s; 74 } 75 } 76 c=getchar(); //讀取下一個字符 77 } 78 while (top->dat!='@') //讀完和還不算完,還要把棧內剩余的所有符號挨個彈出 79 { 80 s=top; 81 printf("%c ",top->dat); 82 top=top->next; 83 free(s); 84 } 85 return 0; //后綴表達式輸出完畢 86 }
好,如果你看懂了上面所有的內容,恭喜你,已經學會一半了。那么,現在我們就可以去掉那個只能輸入一位數的大前提,只需要在輸入的時候處理一下,就可以實現任意位數的數,甚至是小數(可以參照最后的程序代碼)。
那么現在我們來完成第二步:已知后綴表達式輸出結果。這個算法要比上面的轉換簡單多了,就是棧的基本操作,只不過這里的棧不是用來存儲字符的,而是用來存儲數字的。
我們不妨還是假設一位數吧。
1、如果讀入的是數字,直接進棧;
2、如果是符號,必然是+-*/中的一個,直需要彈出棧頂的兩個數,運算,然后再把結果進棧。直至掃描完整個后綴表達式,棧頂就是最終結果。
如果看懂了上面,我們可以發現,這個過程直接就可以在轉換表達式的時候順便完成,也就是,如果遇到數字,不打印到屏幕上,而是進棧到數字存儲棧里;如果有出棧的符號,不用打印到屏幕上,而是彈出數字棧的兩個棧頂元素,然后進棧。就OK啦。下面附上輸入中綴表達式輸出結果的代碼(只是將上面的代碼中打印的過程換成了其他操作而已):
1 #include<stdio.h> 2 #include<stdlib.h> 3 4 #define newp (stype *)malloc(sizeof(stype)) //定義一個申請棧地址的宏 5 6 typedef struct _stack{ 7 char dat; 8 struct _stack *next; 9 } stype; //建立棧類型 10 11 int tance(char x) //探測優先級 12 { 13 if(x=='+'||x=='-') return 0; 14 else if (x=='*'||x=='/') return 1; 15 else if (x=='@'||x=='('||x==')') return -1; 16 } 17 18 int main() 19 { 20 int rs=0; 21 stype *s,*top; //棧指針和棧頂指針 22 int calc[50],i=0; 23 char c; 24 s=newp; 25 s->dat='@'; 26 s->next=NULL; 27 top=s; 28 c=getchar(); //此后為讀取中綴表達式的部分,用字符一個一個的讀,直到讀到回車 29 while(c!='\n') 30 { 31 if (c>='0'&&c<='9') //如果讀入數字,直接打印 32 { 33 i++; 34 calc[i]=c-48; 35 } 36 else if (c=='(') //如果是左括號,直接進棧 37 { 38 s=newp; 39 s->dat=c; 40 s->next=top; 41 top=s; 42 } 43 else if (c==')') //如果是右括號,匹配左括號,把兩者之間的棧內符號全部彈出 44 { 45 while (top->dat!='(') 46 { 47 s=top; 48 if (top->dat=='+'){ 49 calc[i-1]=calc[i-1]+calc[i]; 50 i--; 51 } 52 else if (top->dat=='-'){ 53 calc[i-1]=calc[i-1]-calc[i]; 54 i--; 55 } 56 else if (top->dat=='*'){ 57 calc[i-1]=calc[i-1]*calc[i]; 58 i--; 59 } 60 else if (top->dat=='/'){ 61 calc[i-1]=calc[i-1]/calc[i]; 62 i--; 63 } 64 65 top=top->next; 66 free(s); 67 } 68 s=top; 69 top=top->next; 70 free(s); 71 } 72 else //否則肯定是+-*/了 73 { 74 int a=tance(c); 75 int b=tance(top->dat); //比較該符號和棧頂符號的優先級 76 if (a>b) //如果大於直接壓進去 77 { 78 s=newp; 79 s->dat=c; 80 s->next=top; 81 top=s; 82 } 83 else //否則就把棧頂的符號一直彈出,直到彈到可以壓進去,然后壓進去(也就是說等於也不能壓進去) 84 { 85 while (a<=b) 86 { 87 s=top; 88 if (top->dat=='+'){ 89 calc[i-1]=calc[i-1]+calc[i]; 90 i--; 91 } 92 else if (top->dat=='-'){ 93 calc[i-1]=calc[i-1]-calc[i]; 94 i--; 95 } 96 else if (top->dat=='*'){ 97 calc[i-1]=calc[i-1]*calc[i]; 98 i--; 99 } 100 else if (top->dat=='/'){ 101 calc[i-1]=calc[i-1]/calc[i]; 102 i--; 103 } 104 top=top->next; 105 free(s); 106 b=tance(top->dat); 107 } 108 s=newp; 109 s->dat=c; 110 s->next=top; 111 top=s; 112 } 113 } 114 c=getchar(); //讀取下一個字符 115 } 116 while (top->dat!='@') //讀完和還不算完,還要把棧內剩余的所有符號挨個彈出 117 { 118 s=top; 119 if (top->dat=='+'){ 120 calc[i-1]=calc[i-1]+calc[i]; 121 i--; 122 } 123 else if (top->dat=='-'){ 124 calc[i-1]=calc[i-1]-calc[i]; 125 i--; 126 } 127 else if (top->dat=='*'){ 128 calc[i-1]=calc[i-1]*calc[i]; 129 i--; 130 } 131 else if (top->dat=='/'){ 132 calc[i-1]=calc[i-1]/calc[i]; 133 i--; 134 } 135 top=top->next; 136 free(s); 137 } 138 139 140 printf("%d\n",calc[1]); 141 return 0; 142 }
OK,會了一位數的,其他的也就是小case啦,如果想看完整的多位數的代碼,可以參看 棧的應用2——超級計算器(中綴與后綴表達式)C語言