棧的應用:四則運算實現
注:我們這里使用鏈棧來實現,當然前面的順序棧同樣可以實現,而且更加容易理解。這里我們使用鏈棧來練習
(一)預備知識
前綴、中綴、后綴表達式(逆波蘭表達式)
union聯合體使用詳解
前綴表達式、中綴表達式、后綴表達式都是四則運算的表達方式,用以四則運算表達式求值
中綴表達式
中綴表達式就是常見的運算表達式,如(3+4)×5-6
前綴表達式
前綴表達式又稱波蘭式,前綴表達式的運算符位於操作數之前
比如:- × + 3 4 5 6
后綴表達式(逆波蘭表達式)<這是我們使用的>
后綴表達式又稱逆波蘭表達式,與前綴表達式相似,只是運算符位於操作數之后
比如:3 4 + 5 × 6 -
(二)中綴轉后綴
例如,將中綴表達式“1+((2+3)×4)-5”轉換為后綴表達式的過程如下:
掃描到的元素 | s2(棧底->棧頂) | s1 (棧底->棧頂) | 說明 |
---|---|---|---|
1 | 1 | 空 | 數字,直接入棧 |
+ | 1 | + | s1為空,運算符直接入棧 |
( | 1 | + ( | 左括號,直接入棧 |
( | 1 | + ( ( | 同上 |
2 | 1 2 | + ( ( | 數字 |
+ | 1 2 | + ( ( + | s1棧頂為左括號,運算符直接入棧 |
3 | 1 2 3 | + ( ( + | 數字 |
) | 1 2 3 + | + ( | 右括號,彈出運算符直至遇到左括號 |
× | 1 2 3 + | + ( × | s1棧頂為左括號,運算符直接入棧 |
4 | 1 2 3 + 4 | + ( × | 數字 |
) | 1 2 3 + 4 × | + | 右括號,彈出運算符直至遇到左括號 |
- | 1 2 3 + 4 × + | - | -與+優先級相同,因此彈出+,再壓入- |
5 | 1 2 3 + 4 × + 5 | - | 數字 |
到達最右端 | 1 2 3 + 4 × + 5 - | 空 | s1中剩余的運算符 |
因此結果為“1 2 3 + 4 × + 5 -”
步驟:
- 初始化兩個棧:運算符棧s1和儲存中間結果的棧s2;
- 從左至右掃描中綴表達式;
- 遇到操作數時,將其壓s2;
- 遇到運算符時,比較其與s1棧頂運算符的優先級:
- 如果s1為空,或棧頂運算符為左括號“(”,則直接將此運算符入棧;
- 否則,若優先級比棧頂運算符的高,也將運算符壓入s1(注意轉換為前綴表達式時是優先級較高或相同,而這里則不包括相同的情況);
- 否則,將s1棧頂的運算符彈出並壓入到s2中,再次轉到(4-1)與s1中新的棧頂運算符相比較;
- 遇到括號時:
- 如果是左括號“(”,則直接壓入s1;
- 如果是右括號“)”,則依次彈出s1棧頂的運算符,並壓入s2,直到遇到左括號為止,此時將這一對括號丟棄;
- 重復步驟2至5,直到表達式的最右邊;
- 將s1中剩余的運算符依次彈出並壓入s2;
- 依次彈出s2中的元素並輸出,結果的逆序即為中綴表達式對應的后綴表達式(轉換為前綴表達式時不用逆序)
(三)計算后綴表達式結果
從左至右掃描表達式,遇到數字時,將數字壓入堆棧,遇到運算符時,彈出棧頂的兩個數,用運算符對它們做相應的計算(次頂元素 op 棧頂元素),並將結果入棧;重復上述過程直到表達式最右端,最后運算得出的值即為表達式的結果
例如后綴表達式“3 4 + 5 × 6 -”:
- 從左至右掃描,將3和4壓入堆棧;
- 遇到+運算符,因此彈出4和3(4為棧頂元素,3為次頂元素,注意與前綴表達式做比較),計算出3+4的值,得7,再將7入棧;
- 將5入棧;
- 接下來是×運算符,因此彈出5和7,計算出7×5=35,將35入棧;
- 將6入棧;
- 最后是-運算符,計算出35-6的值,即29,由此得出最終結果。
(四)代碼實現
//棧中數據類型 typedef struct Data { int flag; //1為字符,2為浮點數 union { double num; char sign; }number; }data;

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define STACK_INIT_SIZE 100 //定義棧的初始大小 #define STACK_INCR_SIZE 10 //定義棧的增長大小 #define MAXSIZE 100 //中綴表達式的長度 typedef struct Data { int flag; //1為字符,2為浮點數 union { double num; char sign; }number; }data; typedef data ElemType; typedef int Status; typedef struct { ElemType *base; //棧底指針 ElemType *top; //棧頂指針 int stackSize; //最大容量,這是可修改的 }sqStack; //四個基礎操作 Status InitStack(sqStack *s); //初始化操作,建立一個空棧 Status ClearStack(sqStack *s); //將棧清空 Status StackEmpty(sqStack s); //若棧存在,返回true,否則返回false int StackLength(sqStack s); //返回棧S的元素個數 Status GetTop(sqStack s, ElemType *e); //若是棧存在且非空,用e返回S的棧頂元素 Status Push(sqStack *s, ElemType e); // 若是棧存在,則插入新的元素e到棧S中並成為棧頂元素 Status Pop(sqStack *s, ElemType *e); //若是棧存在且非空,刪除棧頂元素,並用e返回其值 Status DestroyStack(sqStack *s); //若是棧存在,則銷毀他 void PrintStack(sqStack s); //打印所有數據,測試用,會依次將數據出棧 //初始化操作,建立一個空棧 Status InitStack(sqStack *s) { s->base = (ElemType *)malloc(STACK_INIT_SIZE*sizeof(ElemType)); if (!s->base) return ERROR; s->top = s->base; //最開始,棧頂就是棧底 s->stackSize = STACK_INIT_SIZE; return OK; } //將棧清空,將棧頂指針移動到棧底即可,容量大小不要修改,數據不需要清空,數據入棧會覆蓋 Status ClearStack(sqStack *s) { if (s == NULL) return ERROR; s->top = s->base; return OK; } //若棧存在,返回true,否則返回false Status StackEmpty(sqStack s) { if (s.base == s.top) return TRUE; return FALSE; } //返回棧S的元素個數 int StackLength(sqStack s) { int length = s.top - s.base; //指針之間運算,是按照其中數據大小字節來算的 return length; } //若是棧存在且非空,用e返回S的棧頂元素,注意:只是獲取棧頂數據,不出棧 Status GetTop(sqStack s, ElemType *e) { if (!e || StackEmpty(s) || !s.base) return ERROR; *e = *(s.top - 1); return OK; } //入棧操作:若是棧存在,則插入新的元素e到棧S中並成為棧頂元素 Status Push(sqStack *s, ElemType e) { ElemType* newStack; if (!s->base) return ERROR; if (s->top-s->base>=s->stackSize) //棧滿,需要再分配 { newStack = (ElemType *)realloc(s->base, (s->stackSize + STACK_INCR_SIZE)*sizeof(ElemType)); //重新分配大小 if (!newStack) //若是分配失敗,會返回NULL { free(s->base); exit(0); //分配失敗,直接退出 } s->base = newStack; //分配后需要將棧頂指針進行移動到新的位置 s->top = s->base + s->stackSize; } *(s->top) = e; s->top++; return OK; } //若是棧存在且非空,刪除棧頂元素(只需要將棧頂指針下移即可),並用e返回其值 Status Pop(sqStack *s, ElemType *e) { if (!s->base || !e || StackEmpty(*s)) return ERROR; *e = *(--s->top); return OK; } //若是棧存在,則銷毀他(直接將棧底指針釋放即可,置為空) Status DestroyStack(sqStack *s) { if (!s->base) //若是棧存在 { s->stackSize = 0; free(s->base); s->base = s->top = NULL; } return OK; } //打印數據 void PrintStack(sqStack s) { data d; int len = StackLength(s); for (int i = 0; i < len;i++) { Pop(&s, &d); if (d.flag == 1) printf("%c", d.number.sign); else printf("%lf", d.number.num); } }
//四則運算需要的函數 Status MatchBrack(char* str); //匹配括號是否正確 Status RotateStack(sqStack* s); //將棧中的數據翻轉 Status GetMidStack(sqStack *s, char* str); //獲取中綴表達式,將字符串轉換到棧中 Status GetBackStack(sqStack *s); //獲取后綴表達式 Status GetBackValue(sqStack *s, double* val); //獲取后綴表達式計算出來的結果
//括號匹配 Status MatchBrack(char* str) { char *cur = str; sqStack BrkSK; data d; char ch; Status sta = OK;; if (!str || !InitStack(&BrkSK)) return ERROR; while (*cur!='\0') { if (*cur=='('||*cur=='['||*cur=='{') { d.flag = 1; d.number.sign = *cur; Push(&BrkSK,d); } else if (*cur == ')' || *cur == ']' || *cur == '}' ) { if (!GetTop(BrkSK, &d)) { sta = ERROR; break; } ch = d.number.sign; if ((*cur == ')'&&ch == '(') || (*cur == ']'&&ch == '[') || (*cur == '}'&&ch == '{')) Pop(&BrkSK, &d); else { sta = ERROR; break; } } cur++; } DestroyStack(&BrkSK); return sta; } //獲取中綴表達式 Status GetMidStack(sqStack *s, char* str) { char* cur, *start, *end; cur = start = end = str; char fnum[10] = { 0 }; data d; if (!s || !str) return ERROR; while (*cur != '\0') { if (!isdigit(*cur) && *cur != '.') { if (cur > str&&isdigit(*(cur - 1))) { end = cur; memset(fnum, 0, 10); memcpy(fnum, start, (end - start)); d.flag = 2; d.number.num = atof(fnum); Push(s, d); } d.flag = 1; d.number.sign = *cur; if (isdigit(*(cur + 1))) start = cur + 1; Push(s, d); } cur++; } if (start > end) //最后以數字結尾,需要再進行判斷 { end = cur; memset(fnum, 0, 10); memcpy(fnum, start, (end - start)); d.flag = 2; d.number.num = atof(fnum); Push(s, d); } return OK; } //將棧中的數據翻轉 Status RotateStack(sqStack* s) { sqStack tempSk, *freeSk; if (!InitStack(&tempSk) || !s) return ERROR; data d; int length = StackLength(*s); for (int i = 0; i < length; i++) { Pop(s, &d); Push(&tempSk, d); } freeSk = s; *s = tempSk; DestroyStack(freeSk); } //獲取后綴表達式 Status GetBackStack(sqStack *s) { sqStack OperaSk, ResSk, *tempSk; OperaSk.base = ResSk.base = NULL; data d, top; char pc; //存儲單個運算符 int length = StackLength(*s); if (!s) return ERROR; if (!InitStack(&OperaSk) || !InitStack(&ResSk)) { DestroyStack(&OperaSk); DestroyStack(&ResSk); return ERROR; } for (int i = 0; i < length; i++) { Pop(s, &d); if (d.flag == 2) Push(&ResSk, d); if (d.flag == 1) { if (GetTop(OperaSk, &top)) pc = top.number.sign; if (d.number.sign == ')') { //將'('之上的運算符全部彈出到ResSk while (pc != '(') { Pop(&OperaSk, &d); Push(&ResSk, d); GetTop(OperaSk, &top); pc = top.number.sign; } Pop(&OperaSk, &d); //將(一起彈出 continue; } if (StackEmpty(OperaSk) || pc == '(' || d.number.sign == '(') //若是OperaSk為空或者棧頂為(或者獲取的運算符為(,我們直接將這個運算符壓棧 Push(&OperaSk, d); else if ((pc == '-' || pc == '+') && (d.number.sign == '*' || d.number.sign == '/')) //若是棧頂的優先級低,也壓棧,但是要先將棧頂的 Push(&OperaSk, d); else if (pc == d.number.sign || (pc == '-'&&d.number.sign == '+') || (pc == '+'&&d.number.sign == '-') || (pc == '*'&&d.number.sign == '/') || (pc == '/'&&d.number.sign == '*')) //當棧頂優先級和當前運算符一致,先彈出到ResSk,在進行壓棧 { Pop(&OperaSk, &top); Push(&ResSk, top); Push(&OperaSk, d); } else //當獲取的運算符優先級低於棧頂優先級,先將棧頂運算符移棧到ResSk,然后再將當前運算符與下一次進行比較,壓棧到OperaSK棧 { Pop(&OperaSk, &top); //獲取OperaSK棧頂數據 Push(&ResSk, top); //將數據放入ResSK棧 Push(s, d); //由於當前數據沒有完成入棧,我們將它放回原來棧中,再次進行比較 length++; } } } while (!StackEmpty(OperaSk)) //將運算符棧中的剩余的數據全部移到結果棧 { Pop(&OperaSk, &d); Push(&ResSk, d); } tempSk = s; *s = ResSk; DestroyStack(tempSk); DestroyStack(&OperaSk); return OK; } //獲取后綴表達式計算出來的結果 Status GetBackValue(sqStack *s, double* val) { sqStack OpValSk; //獲取運算結果的棧 int length = StackLength(*s); double op1, op2, value = 0; data d, top; if (!s || !InitStack(&OpValSk)) return ERROR; for (int i = 0; i < length; i++) { Pop(s, &d); if (d.flag == 2) //數字 Push(&OpValSk, d); else //運算符,取兩個數運算后,放回棧中 { Pop(&OpValSk, &top); op2 = top.number.num; Pop(&OpValSk, &top); op1 = top.number.num; switch (d.number.sign) { case '+': value = op1 + op2; break; case '-': value = op1 - op2; break; case '*': value = op1 * op2; break; case '/': value = op1 / op2; break; default: break; } d.flag = 2; d.number.num = value; Push(&OpValSk, d); } } *val = value; DestroyStack(&OpValSk); return OK; }
int main() { sqStack sk; double value; ElemType e; char str[MAXSIZE] = { 0 }; sk.base = sk.top = NULL; //用於判斷是否存在 //初始化空棧 InitStack(&sk); //接收輸入的中綴表達式 printf("please enter expression<no space>:"); scanf("%s", str); //先進行括號匹配 if (!MatchBrack(str)) return 0; //將字符串中的中綴表達式分割,轉換為中綴表達式存放在棧中'1+2+3'--->3.0 + 2.0 + 1.0每一個都是一個棧空間數據 GetMidStack(&sk, str); //將上面的中綴表達式翻轉,變為正常的 RotateStack(&sk); //下面將中綴表達式轉后綴表達式 GetBackStack(&sk); //將上面的后綴表達式翻轉,變為正常的 RotateStack(&sk); //獲取后綴表達式結果 GetBackValue(&sk, &value); //PrintStack(sk); printf("Operation values:%lf", value); //PrintStack(sk); DestroyStack(&sk); system("pause"); return 0; }
(五)結果演示
(六)反思
實現了小數運算,但是不足的是對方法不熟悉,導致耗費太多時間