棧的應用1——超級計算器(中綴與后綴表達式)C語言


這里要學的程序主要用來實現一個功能——輸入表達式輸出結果,也就是一個計算器。效果如下:

 

這個程序主要有兩個步驟: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語言

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM