1. 程式人生 > >棧:棧及表示式求值與迷宮問題的簡單應用

棧:棧及表示式求值與迷宮問題的簡單應用

棧是一種進出受限線性表。即僅可在一端進出資料,於是具有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