目錄
0? 引言
1? 棧在括號匹配中的應用
2? 棧在表達式求值中的應用
? ? ? ? 2.1 算數表達式
? ? ? ? 2.2 中綴表達式轉后綴表達式
2.3 后綴表達式求值
3? 棧在遞歸中的應用
3.1 棧在函數調用中的作用
3.2 棧在函數調用中的工作原理
4? 總結
0? 引言
????????棧(Stack)是一種非常基本且重要的數據結構,它們在許多計算機科學和軟件工程的應用中都有廣泛的用途。
? ? ? ? 棧:
????????????????①括號匹配;
? ? ? ? ? ? ? ? ②表達式求值;
? ? ? ? ? ? ? ? ③遞歸函數調用。
1? 棧在括號匹配中的應用
? ? ? ? 表達式中有兩種括號:圓括號 ( ) 和 方括號 [ ],嵌套的順序任意,但應為正確的格式。
????????例如:( ( [ ]?[ ] ) ) 為正確格式。
? ? ? ? 但如何用算法實現括號匹配問題?
? ? ? ? 思路如下:
? ? ? ? (1)初始一個空棧;
? ? ? ? (2)順序讀入括號;
? ? ? ? (3)當讀入的為左括號,將繼續讀入括號,直到讀入第一個右括號。那將檢測與之最近的左括號是否與之相匹配,若匹配,則出棧;若不匹配,則退出程序。當程序結束時,棧為空。反之,則表明括號序列的格式不正確。
? ? ? ? 代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h> #define MAX_SIZE 100 // 假設棧的最大大小 typedef struct { char data[MAX_SIZE]; int top;
} Stack; // 初始化棧
void initStack(Stack *s) { s->top = -1;
} // 判斷棧是否為空
bool isEmpty(Stack *s) { return s->top == -1;
} // 入棧
void push(Stack *s, char c) { if (s->top >= MAX_SIZE - 1) { printf("Stack overflow\n"); return; } s->data[++s->top] = c;
} // 出棧
char pop(Stack *s) { if (isEmpty(s)) { printf("Stack underflow\n"); return '#'; // 返回一個無效字符,或可以選擇拋出一個錯誤 } return s->data[s->top--];
} // 檢查兩個括號是否匹配
bool isMatch(char c1, char c2) { if (c1 == '(' && c2 == ')') return true; if (c1 == '[' && c2 == ']') return true; if (c1 == '{' && c2 == '}') return true; return false;
} // 括號匹配函數
bool isBalanced(char *str) { Stack s; initStack(&s); for (int i = 0; str[i] != '\0'; i++) { if (str[i] == '(' || str[i] == '[' || str[i] == '{') { push(&s, str[i]); } else if (str[i] == ')' || str[i] == ']' || str[i] == '}') { if (isEmpty(&s)) { // 棧為空,但遇到了右括號,不匹配 return false; } char topChar = pop(&s); if (!isMatch(topChar, str[i])) { // 棧頂元素與當前右括號不匹配 return false; } } } // 如果棧為空,則所有括號都匹配 return isEmpty(&s);
} int main() { char str[MAX_SIZE]; printf("Enter a string with brackets: "); scanf("%s", str); if (isBalanced(str)) { printf("The brackets are balanced.\n"); } else { printf("The brackets are not balanced.\n"); } return 0;
}
2? 棧在表達式求值中的應用
? ? ? ? 2.1 算數表達式
????????中綴表達式是人們常用的算術表達式,即操作符以中綴形式處于操作數之間。但在計算機中,中綴表達式相較于前綴和后綴表達式來說,更不易被計算機識別。前綴表達式成為波蘭式,后綴表達式又稱逆波蘭式。
? ? ? ? 2.2 中綴表達式轉后綴表達式
? ? ? ? (1)手算方法:
? ? ? ? ①根據運算順序對表達式運算符排號;
? ? ? ? ②根據運算符排號順序,將運算符及兩端的操作數以(左操作數 右操作數 運算符)的順序重新組合。
? ? ? ? 例如:( A + B ) * C + ( D - E ) / F 轉后綴表達式的過程如下:
? ? ? ??(2)算法實現:
? ? ? ? ①初始一個棧;
? ? ? ? ②遇到操作數,直接加入后綴表達式;
? ? ? ? ③遇到界限符,若為左括號直接入棧,若為右括號,則依次彈出棧中的運算符,加入后綴表達式,知道彈出左括號為止。需要注意的是,左括號和右括號直接刪除,不加入后綴表達式。
? ? ? ? ④遇到運算符,則看運算符的優先級,若高于除左括號外的棧頂元素,則直接入棧。反之,則依次彈出棧中優先級高于或等于當前運算符的所有運算符,并加入后綴表達式,直到遇到低于他的優先級的運算符,才入棧。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h> #define MAX_SIZE 100 typedef struct { char data[MAX_SIZE]; int top;
} Stack; // 初始化棧
void initStack(Stack *s) { s->top = -1;
} // 判斷棧是否為空
bool isEmpty(Stack *s) { return s->top == -1;
} // 入棧
bool push(Stack *s, char c) { if (s->top >= MAX_SIZE - 1) { return false; // 棧溢出 } s->data[++s->top] = c; return true;
} // 出棧
char pop(Stack *s) { if (isEmpty(s)) { return '\0'; // 棧空,返回空字符 } return s->data[s->top--];
} // 獲取棧頂元素,但不彈出
char peek(Stack *s) { if (isEmpty(s)) { return '\0'; // 棧空,返回空字符 } return s->data[s->top];
} // 運算符的優先級比較(這里只處理了基本的四則運算)
int precedence(char op) { if (op == '+' || op == '-') { return 1; } if (op == '*' || op == '/') { return 2; } return 0; // 如果不是運算符,返回0
} // 將中綴表達式轉換為后綴表達式
void infixToPostfix(char *infix, char *postfix) { Stack s; initStack(&s); int i = 0, j = 0; while (infix[i] != '\0') { if (infix[i] >= '0' && infix[i] <= '9') { // 如果是操作數,直接添加到后綴表達式中 postfix[j++] = infix[i++]; postfix[j++] = ' '; // 假設操作數都是個位數,用空格分隔 } else if (infix[i] == '(') { // 如果是左括號,直接入棧 push(&s, infix[i++]); } else if (infix[i] == ')') { // 如果是右括號,則彈出棧中元素直到遇到左括號 while (!isEmpty(&s) && peek(&s) != '(') { postfix[j++] = pop(&s); postfix[j++] = ' '; } // 彈出左括號,但不加入后綴表達式 pop(&s); i++; } else { // 如果是運算符 while (!isEmpty(&s) && precedence(peek(&s)) >= precedence(infix[i])) { // 如果棧不為空且棧頂元素優先級高于或等于當前運算符,彈出棧頂元素 postfix[j++] = pop(&s); postfix[j++] = ' '; } // 當前運算符入棧 push(&s, infix[i++]); } } // 彈出棧中剩余的所有運算符 while (!isEmpty(&s)) { postfix[j++] = pop(&s); postfix[j++] = ' '; } // 添加字符串結束符 postfix[j] = '\0';
} int main() { char infix[MAX_SIZE], postfix[MAX_SIZE * 2]; // 后綴表達式可能更長,因此分配更多空間 printf("Enter an infix expression: "); scanf("%s", infix); // 注意:這里不會處理空格和復雜輸入 infixToPostfix(infix, postfix); printf("Postfix expression: %s\n", postfix); return 0;
}
2.3 后綴表達式求值
????????后綴表達式(也稱為逆波蘭表示法或逆波蘭記法)是一種不需要括號來標明運算符的優先級的數學表達式。在這種表示法中,所有的運算符都放在操作數的后面。
????????求值后綴表達式的基本步驟如下:
- 初始化一個棧,用于存儲操作數。
- 從左到右掃描后綴表達式。
- 如果掃描到操作數,則將其壓入棧中。
- 如果掃描到運算符,則從棧中彈出兩個操作數(先彈出的為右操作數,后彈出的為左操作數),將這兩個操作數作為運算符的輸入進行運算,然后將結果壓回棧中。
- 重復步驟2-4,直到后綴表達式掃描完畢。
- 棧中剩下的元素就是表達式的值。
????????示例:
????????后綴表達式:3 4 + 5 *
????????求值過程:
- 掃描到?
3
,壓入棧:[3]
- 掃描到?
4
,壓入棧:[3, 4]
- 掃描到?
+
,彈出?4
?和?3
,計算?3 + 4
?得到?7
,壓入棧:[7]
- 掃描到?
5
,壓入棧:[7, 5]
- 掃描到?
*
,彈出?5
?和?7
,計算?7 * 5
?得到?35
,壓入棧:[35]
- 掃描完畢,棧中元素?
35
?即為表達式的值。
????????下面是實現代碼(以上述示例為例):
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h> #define MAX_STACK_SIZE 100 typedef struct { double data[MAX_STACK_SIZE]; int top;
} Stack; // 初始化棧
void initStack(Stack *s) { s->top = -1;
} // 判斷棧是否為空
int isEmpty(Stack *s) { return s->top == -1;
} // 壓棧操作
void push(Stack *s, double value) { if (s->top >= MAX_STACK_SIZE - 1) { printf("Stack overflow\n"); exit(1); } s->data[++s->top] = value;
} // 彈棧操作
double pop(Stack *s) { if (isEmpty(s)) { printf("Stack underflow\n"); exit(1); } return s->data[s->top--];
} // 求值后綴表達式
double evaluatePostfix(const char *postfix) { Stack s; initStack(&s); const char *token = strtok((char *)postfix, " "); // 假設操作符和操作數之間用空格分隔 while (token != NULL) { if (isdigit(token[0])) { // 如果是操作數 double value = atof(token); push(&s, value); } else { // 如果是運算符 double rightOperand = pop(&s); // 彈出右操作數 double leftOperand = pop(&s); // 彈出左操作數 switch (token[0]) { case '+': push(&s, leftOperand + rightOperand); break; case '-': push(&s, leftOperand - rightOperand); break; case '*': push(&s, leftOperand * rightOperand); break; case '/': if (rightOperand != 0.0) { push(&s, leftOperand / rightOperand); } else { printf("Error: Division by zero\n"); exit(1); } break; default: printf("Error: Unknown operator\n"); exit(1); } } token = strtok(NULL, " "); // 繼續獲取下一個token } if (!isEmpty(&s)) { return pop(&s); // 棧中剩下的元素就是表達式的值 } else { printf("Error: Invalid postfix expression\n"); exit(1); }
} int main() { const char *postfix = "3 4 + 5 *"; double result = evaluatePostfix(postfix); printf("Result: %lf\n", result); return 0;
}
3? 棧在遞歸中的應用
3.1 棧在函數調用中的作用
- 參數傳遞:當調用一個函數時,需要傳遞參數給該函數。這些參數會被壓入棧中,以便函數內部能夠訪問和使用它們。
- 局部變量分配:函數內部定義的局部變量會在棧上分配空間。這些變量的生命周期與函數的執行周期相同,當函數執行完畢后,這些局部變量所占用的棧空間會被自動釋放。
- 保存調用的返回地址:在函數調用時,CPU需要知道函數執行完畢后應該返回到哪個位置繼續執行。這個返回地址會被保存在棧中,以便函數執行完畢后能夠正確地返回到調用它的位置。
- 保存寄存器以供恢復:在函數調用和返回的過程中,CPU的寄存器狀態會發生變化。為了能夠在函數返回后恢復原來的寄存器狀態,棧會保存這些寄存器的值。
3.2 棧在函數調用中的工作原理
- 函數調用:當調用一個函數時,系統首先會創建一個新的棧幀(stack frame)來保存該函數的執行環境。這個棧幀包含了函數的返回地址、參數、局部變量等信息。然后,系統會將當前程序的執行狀態(如返回地址、寄存器狀態等)壓入棧中,以便在函數執行完畢后能夠恢復。
- 函數執行:在函數執行過程中,函數會訪問棧幀中的參數和局部變量,并根據需要進行計算和操作。同時,如果函數內部調用了其他函數,系統也會為這些被調用的函數創建新的棧幀,并將當前函數的執行狀態壓入棧中保存。
- 函數返回:當函數執行完畢或者遇到return語句時,系統會彈出當前函數的棧幀,并根據棧幀中的返回地址返回到調用它的位置繼續執行。在返回之前,系統還會恢復調用該函數時的寄存器狀態。
? ? ? ? 下面將給出一個例子:
? ? ? ? 例如:階乘,大家可以自行調試;
#include <stdio.h>int step(int n){if(n==1)return 1;elsereturn n*step(n-1);
}int main(){int n,s;scanf("%d",&n);s=step(n);printf("%d",s);
}
4? 總結
????????在本文中,我們深入探討了棧這一數據結構及其在各種應用場景中的重要作用。棧作為一種后進先出(LIFO)的數據結構,其獨特的操作方式——壓棧(push)和彈棧(pop),使得它在計算機科學和軟件開發中占據了不可或缺的地位。
????????詳細討論了棧在多個領域中的應用。其中,后綴表達式的求值是一個經典的棧應用示例。在這個問題中,我們利用棧來存儲操作數,并通過操作數的彈出和結果的壓入,實現了表達式的正確計算。這種方法不僅簡化了表達式的處理流程,而且提高了計算效率。
????????此外,棧還在函數調用、遞歸等方面發揮著重要作用。在函數調用中,棧用于存儲局部變量和返回地址,確保函數能夠正確地返回并繼續執行。在遞歸算法中,棧用于保存遞歸調用的中間結果,從而避免重復計算。
????????綜上所述,棧作為一種基本而強大的數據結構,在各個領域都有著廣泛的應用。通過學習和掌握棧的使用方法和應用場景,我們能夠更好地解決實際問題,提高編程效率。