棧:棧及表示式求值與迷宮問題的簡單應用
棧
棧是一種進出受限的線性表。即僅可在一端進出資料,於是具有FILO(first in last out 先進後出)這種特點。
適合於各種需要回退到上一狀態的應用場景。並且通過對進出規則的進一步控制,將優先順序轉化為出現位置的先後順序上。
ADT Stack{
資料物件:同一資料型別的若干資料的集合
結構關係:線性關係
基本運算:
int initStack(Stack **stack) //初始化一個空棧
int isEmpty(Stack *stack) //判斷棧是否為空
int isFull(Stack *stack) //判斷棧是否已滿
int getTop(Stack *stack, DataType *e) //得到棧頂元素儲存在變數 *e中
int pop(Stack *stack, DataType *e) //棧頂元素出棧儲存在 *e變數中
int push(Stack *stack, DataType data) //變數data入棧
}ADT Stack
棧的基本運算的實現
int initStack(Stack **stack) {
*stack = (Stack *)malloc(sizeof(Stack));
(*stack)->top = -1;
if (*stack)
return 1;
return 0;
}
int isEmpty(Stack *stack) {
return stack->top == -1;
}
int isFull(Stack *stack) {
return stack->top == SIZE;
}
int getTop(Stack *stack, double *e) {
if (!isEmpty(stack)) {
*e = stack->data[stack->top];
return 1;
}
return 0;
}
int pop(Stack *stack, double *e) {
if (!isEmpty(stack)) {
*e = stack->data[stack->top--];
return 1;
}
return 0;
}
int push(Stack *stack, double data) {
if (!isFull(stack)) {
stack->top++ ;
stack->data[stack->top] = data;
return 1;
}
return 0;
}
表示式求值
表示式求值主要有兩種思路
1.中綴表示式轉字尾表示式再進行計算 僅使用一個棧
2.使用一個運算元棧,一個運算子棧,邊處理表達式邊計算
根據語言特點,C語言採用第二種方法比較合適
儲存結構的選擇:在鏈棧、順序棧中選擇順序棧
- 首先要分隔輸入的運算元與運算子
- 運算元與運算子按規則進出棧並進行運算
- 從棧底取出結果
分隔運算元與運算子
int process(char res[][21]) {//分隔運算元與操作符
char str[21];
char c;
int i = 0, j = 0;
while ((c = getchar()) != '\n') {
if (isDigit(c)) {//c != '+'&&c != '-'&&c != '*'&&c != '/'&&c != '('&&c != ')'
str[j++] = c;
}
else {
if (j != 0) {
str[j] = 0;
strcpy(res[i++], str);
j = 0;
}
res[i][0] = c;
res[i++][1] = 0;
}
}
str[j] = 0;
strcpy(res[i++], str);
return i;//返回元素個數
}
進出棧規則
初始化兩個棧,一個存放運算子,一個放運算元
讀入表示式分隔運算元與運算子後,按順序處理表達式,是運算元直接進棧,運算子則按照如下規則處理
用judge函式判斷運算子優先順序
規則如下
①左括號直接進棧
②加減優先順序相同,乘除優先順序相同且高於加減,乘方優先順序最高
③若棧空則當前運算子直接入棧,否則與棧頂運算子比較
1.當前運算子優先順序高則入棧
2.棧頂運算子優先順序高則彈出它,並從操作符棧彈出兩個數進行運算(注意後彈出的數在運算子左側),運算結果入運算元棧,返回③
3.遇到右括號,棧頂運算子依次出棧按2的規則運算,直到棧頂運算子為左括號,且將左括號出棧(注意右括號不做任何處理即捨棄)
④待表示式處理完畢,若操作符棧中僅留存一個元素則計算無誤,該元素即為表示式值
//傳入當前運算子與棧頂運算子,按優先順序得到返回值,比較返回值
int judge(char c) {//min值為1
int flag = 0;
switch (c){
case 0:flag = 0; break;
case '(': flag = 0; break;
case '+': flag = 1; break;
case '-': flag = 1; break;
case '*': flag = 2; break;
case '/': flag = 2; break;
case '^': flag = 3; break;
}
return flag;
}
核心流程
int main() {
char res[SIZE][21];
printf("請輸入計算式:\n");
int size = process(res);//size 資料個數
Stack *num, *operator;
if (!initStack(&num) || !initStack(&operator)) { printf("初始化出錯!"); return -1; }
int i, top, now;
for (i = 0; i < size; i++) {
if (isDigit(res[i][0])) {//運算元
push(num, atof(res[i]));
}
else if (res[i][0] == '(') {
push(operator,res[i][0]);
}
else {//運算子
if (res[i][0] != ')') {// 非 )
double tem = 0;
now = judge(res[i][0]);//計算當前運算子優先順序
do {
getTop(operator,&tem);//取得棧頂運算子
top = judge((int)tem);//計算棧頂運算子優先順序
if (now > top)
push(operator,res[i][0]);
else {//彈棧運算 **運算結果入棧
push(num, caculate(num, operator));
}
} while (now <= top);
}
else {// 當前運算子為 )
double tem;
while (getTop(operator,&tem) && ((int)tem != '(')) {//此時棧不可能為空
push(num, caculate(num, operator));
}
pop(operator,&tem);//彈出 (
}
}
}
while (!isEmpty(operator)) {
push(num, caculate(num, operator));
}
if (num->top != 0)
printf("計算出錯!\n");
double result;
getTop(num, &result);
printf("%g", result);
return 0;
}
其中計算過程caculate函式為
double caculate(Stack *num, Stack *operator) {//進行一次彈棧運算 返回結果
char c;
double a, b, re, t;
pop(operator, &t);//彈出一個運算子
pop(num, &b);
pop(num, &a);//彈出兩個運算元
c = (int)t;
switch (c){
case '+':re = a + b; break;
case '-':re = a - b; break;
case '*':re = a * b; break;
case '/':re = a / b; break;
case '^':re = pow(a, b); break;
}
return re;
}
為了方便這裡用了double型別來儲存所有資料
迷宮問題
問題描述:輸入一個矩陣表示迷宮地圖,比如0表示通路,1表示牆壁。告知起點與終點,尋找一條通路。
問題分析:用棧的角度來想的話,這是一個典型的棧的問題,因為一條路走不通,當然要走到上一個路口選另外一邊試試。這就是回溯。棧可以儲存每個路口你的選擇狀態,當某種選擇不合適時,退回到上一狀態,試試另一條路。可以回退到上一狀態,是棧重要的特點。以此類推。
儲存結構:將矩陣對映為二維陣列。以下程式碼中從左至右為Y方向遞增,從上到下以X為方向遞增。從(0,0)開始。
從上到下依次為:
地圖的行和列
地圖矩陣的定義
標記陣列的定義 標記走過的路防止轉圈圈
試探方向的定義
int n, m;
int map[MAX][MAX];
int book[MAX][MAX] = { 0 };
int steps[4][2] = { {0,1}, {1,0}, {0,-1},{-1,0} };// → ↓ ← ↑
座標資料型別定義
typedef struct dataElement {
int x;
int y;
int step;//當前試探到的步數step; 0,1,2,3 // 代表四個方向
}Coord;
演算法的主要過程
int process(Coord begin, Coord end) {
int flag = 1;
push(stack, begin);//設定起點
book[begin.x][begin.y] = 1;
Coord *top = (Coord *)malloc(sizeof(Coord));
*top= begin;//棧頂座標初始化
Coord tem = { 0, 0 ,0 };
while (top->x != end.x||top->y != end.y) {
if (flag)//可走 說明這是一個新的路口 從第一個方向開始嘗試
top->step = 0;
else
top->step++;//回溯後從下一方向繼續嘗試
do {
tem = *top;
tem.x += steps[top->step][X];
tem.y += steps[top->step][Y];
if (0 <= tem.x&&tem.x < n && 0 <= tem.y&&tem.y < m &&
map[tem.x][tem.y] == 0&& book[tem.x][tem.y] ==0){
flag = 1;//可行 //分別判斷是否在地圖內,是否可走,是否走過
}
else {
flag = 0;
top->step++;
}
} while (top->step <= 3 && !flag);//不可行且有餘地時繼續尋找
if (flag) {//可行
push(stack, tem);
book[tem.x][tem.y] = 1;//標記
}
else { //此處無解
pop(stack, &tem);
book[tem.x][tem.y] = 0;//去除標記
if (isEmpty(stack))//無解
return 0;
}
getTop(stack, &top);
}
return 1;
}
結果的簡單展現
標記及列印路徑
void bookTrace() {//標記
for (int i = 0; i <= stack->top; i++) {
map[stack->data[i].x][stack->data[i].y] = 9;
}
}
void print() {
int i, j;
for (i = 0; i < n; i++) {
for (j = 0; j < m; j++) {
switch (map[i][j]){
case 0:printf("○"); break;
case 1:printf("×"); break;
case 9:printf("●"); break;
default:printf("DATA ERROR");exit(0);break;
}
}
printf("\n");
}
}
效果
2018/10/11