1. 程式人生 > >遞迴與非遞迴實現走迷宮演算法

遞迴與非遞迴實現走迷宮演算法

●問題描述:

  給出一個矩陣,其中0表示通路,1表示牆壁,這樣就形成了一個迷宮,要求編寫演算法求出其中一條路徑。

●遞迴思路:

  編寫一個走迷宮函式,傳入二位陣列的下標,先假設該點位於最終路徑上(將0置為2)再探測周圍四個點是否可以走通(是否為0),如果可以走通則將該點四周能走通的點作為函式引數傳入函式進入遞迴。若四周均不能走通(都不為0時)則將該點置回0表示該點不是最終路徑上的點。

  在此思路中遞迴進入時表示了列舉路徑,當發現此條路徑走到某處再不能走通時就將路徑該點置回0並且遞迴退出(回溯)尋找下一條可走通路徑。

●程式碼:

#include <stdio.h>
#include <stdlib.h>

#define END_I 8
#define END_J 7
#define START_I 0
#define START_J 0

//走迷宮
int VistMaze(int maze[][8], int i, int j)
{
	int end = 0;

	//假設能夠走通
	maze[i][j] = 2;
	
	//如果到達重點則將end置為0表示迷宮已經走結束
	if (i == END_I && j == END_J){
		end = 1;
	}
	//如果迷宮沒有走結束則將搜尋所在位置的右、下、左、上四個方向是否能夠走通
	if (end != 1 && j + 1 <= END_J && maze[i][j + 1] == 0){		//右
		if (VistMaze(maze, i, j + 1) == 1)
			return 1;
	}
	if (end != 1 && i + 1 <= END_I && maze[i + 1][j] == 0){		//下
		if (VistMaze(maze, i + 1, j) == 1)
			return 1;
	}
	if (end != 1 && j - 1 >= START_J && maze[i][j - 1] == 0){	//左
		if (VistMaze(maze, i, j - 1) == 1)
			return 1;
	}
	if (end != 1 && i - 1 >= START_I && maze[i - 1][j] == 0){	//上
		if (VistMaze(maze, i - 1, j) == 1)
			return 1;
	}	//當四周都不通的時候將其置回0
	if (end != 1){
		maze[i][j] = 0;
	}

	return end;
}

int main(void)
{
	    //迷宮
    int i, j;
    int maze[9][8] = {
        {0,0,1,0,0,0,1,0},
        {0,0,1,0,0,0,1,0},
        {0,0,0,0,1,1,0,1},
        {0,1,1,1,0,0,1,0},
        {0,0,0,1,0,0,0,0},
        {0,1,0,0,0,1,0,1},
        {0,1,1,1,1,0,0,1},
        {1,1,0,0,1,1,0,1},
        {1,1,0,0,0,0,0,0}
        };
	//打印出迷宮
	printf("原迷宮:\n");
	for(i = 0; i <= 9; i++)
		printf("-");
	printf("\n");
	for (i = 0; i < 9; i++){
		printf("|");
		for (j = 0; j < 8; j++){
			if (maze[i][j] == 1)
				printf("@");
			else
				printf(" ");
		}
		printf("|\n");
	}
	for(i = 0; i <= 9; i++)
		printf("-");
	printf("\n");

	if (VistMaze(maze, 0, 0) == 0){
		printf("沒有路徑可走\n");
		exit(0);
	}

	//打印出迷宮和路徑
	printf("迷宮和路徑:\n");
	for(i = 0; i <= 9; i++)
		printf("-");
	printf("\n");
	for (i = 0; i < 9; i++){
		printf("|");
		for (j = 0; j < 8; j++){
			if (maze[i][j] == 1)
				printf("@");
			else if (maze[i][j] == 2)
				printf("%%");
			else
				printf(" ");
		}
		printf("|\n");
	}
	for(i = 0; i <= 9; i++)
		printf("-");
	printf("\n");

	return 0;
}

●非遞迴思路:

  此題也可以使用棧來避開遞迴,首先需要開闢一個和迷宮具有同樣幾何形狀的結構體二維陣列來儲存是從哪一個點(座標)到達該點的(該初始化時將所有的座標都置為-1),還需要一個可以儲存座標的棧。每次將能夠走通的點(值為0)都入棧然後在迴圈的開始處將棧頂元素彈出儲存進temp變數(儲存座標的變數)中,訪問temp變數四周的元素是否可以被訪問,如果可以被訪問(值為0)則將對應路徑陣列中的元素(即對應座標)值改為temp變數的值並將該能被訪問的變數入棧,再將迷宮中的0(能訪問)更改為2(已被訪問過)防止重複訪問。最後從路徑陣列中的出口處開始倒序輸出所走路徑即可。

  在此種思想中棧儲存了所有可以走通的點,當一個點的四周都不能夠走通時彈出的該點座標程式並沒有進行任何實質性的處理,所以這就相當於一個“回溯”的過程。而訪問四周的點的過程就是一個列舉的過程。

●程式碼:

#include <stdio.h>
#include <stdlib.h>

#define COUNT_I 9
#define COUNT_J 8
#define START_I 0
#define START_J 0
#define END_I 8
#define END_J 7
#define MAXSIZE 1000

//座標位置結構體
typedef struct local{
	
	int x;
	int y;

}LOCAL;

//棧結構
typedef struct stack{
	
	LOCAL data[MAXSIZE];
	int top;

}STACK;

//初始化棧
STACK *InitStack(void)
{
	STACK *maze;
	maze = (STACK *)malloc(sizeof(STACK));
	maze->top = -1;
	
	return maze;
}

//判棧空
int EmptyStack(STACK *maze)
{
	if (maze->top == -1)
		return 1;
	else
		return 0;
}

//判棧滿
int IsFull(STACK *maze)
{
	if (maze->top == MAXSIZE - 1)
		return 1;
	else
		return 0;

}

//入棧
int PushStack(STACK *maze, LOCAL *x)
{
	if (maze->top <= MAXSIZE - 1){
		maze->data[++maze->top] = *x;
		return 1;
	}
	else{
		printf("棧已滿\n");
		return 0;
	}
}

//出棧
int PopStack(STACK *maze, LOCAL *x)
{
	if (maze->top > -1){
		*x = maze->data[maze->top];
		maze->top--;
		return 1;
	}
	else{
		printf("棧已空\n");
		return 0;
	}
}

//走迷宮函式
int VistMaze(int maze[][COUNT_J], LOCAL path[][COUNT_J])
{
	int i, j;

	//初始化棧
	STACK *stack;
	LOCAL temp;
	stack = InitStack();
	temp.x = 0; temp.y = 0;
	if (maze[START_I][START_J] == 0)
		PushStack(stack, &temp);
	else
		return 0;
	while(!EmptyStack(stack)){
		PopStack(stack, &temp);
		i = temp.x;	j = temp.y;
		maze[i][j] = 2;

		if (i == END_I && j == END_J)
			break;

		//下
		if (i + 1 <= END_I && maze[i + 1][j] == 0){
			maze[i + 1][j] = 2;
			path[i + 1][j].x = i;	path[i + 1][j].y = j;
			temp.x = i + 1;
			temp.y = j;
			PushStack(stack, &temp);
		}
		//右
		if (j + 1 <= END_J && maze[i][j + 1] == 0){
			maze[i][j + 1] = 2;
			path[i][j + 1].x = i;	path[i][j + 1].y = j;
			temp.x = i;
			temp.y = j + 1;
			PushStack(stack, &temp);
		}
		//左
		if (j - 1 >= 0 && maze[i][j - 1] == 0){
			maze[i][j - 1] = 2;
			path[i][j - 1].x = i;	path[i][j - 1].y = j;
			temp.x = i;
			temp.y = j - 1;
			PushStack(stack, &temp);
		}
		//上
		if (i - 1 >= 0 && maze[i - 1][j] == 0){
			maze[i - 1][j] = 2;
			path[i - 1][j].x = i;	path[i - 1][j].y = j;
			temp.x = i - 1;
			temp.y = j;
			PushStack(stack, &temp);
		}
	}
	//如果到達終點而退出的迴圈則將路徑標識出來
	if (i == END_I && j == END_J){
		maze[i][j] = 3;
		while(path[temp.x][temp.y].x != -1){
			temp = path[temp.x][temp.y];
			maze[temp.x][temp.y] = 3;
		}
		
		return 1;
	}
	else{
		return 0;
	}
}



int main(void)
{
	//迷宮
	int i, j;
	int maze[COUNT_I][COUNT_J] = {
			{0,0,1,0,0,0,1,0},
			{0,0,1,0,0,0,1,0},
			{0,0,0,0,1,1,0,1},
			{0,1,1,1,0,0,1,0},
			{0,0,0,1,0,0,0,0},
			{0,1,0,0,0,1,0,1},
			{0,1,1,1,1,0,0,1},
			{1,1,0,0,1,1,0,1},
			{1,1,0,0,0,0,0,0}
		};
	
	//定義路徑陣列,將到(x,y)點的路徑儲存進陣列
	LOCAL path[COUNT_I][COUNT_J];
	for(i = 0; i < COUNT_I; i++){
		for(j = 0; j < COUNT_J; j++){
			path[i][j].x = -1;
			path[i][j].y = -1;
		}
	}

	//打印出迷宮
	printf("原迷宮:\n");
	for(i = 0; i <= COUNT_I; i++)
		printf("-");
	printf("\n");
	for (i = 0; i < COUNT_I; i++){
		printf("|");
		for (j = 0; j < COUNT_J; j++){
			if (maze[i][j] == 1)
				printf("@");
			else
				printf(" ");
		}
		printf("|\n");
	}
	for(i = 0; i <= COUNT_I; i++)
		printf("-");
	printf("\n");

	if (VistMaze(maze, path) == 0){
		printf("沒有路徑可走\n");
		exit(0);
	}

	//打印出迷宮和路徑
	printf("迷宮和路徑:\n");
	for(i = 0; i <= COUNT_I; i++)
		printf("-");
	printf("\n");
	for (i = 0; i < COUNT_I; i++){
		printf("|");
		for (j = 0; j < COUNT_J; j++){
			if (maze[i][j] == 1)
				printf("@");
			else if (maze[i][j] == 3)
				printf("%%");
			else
				printf(" ");
		}
		printf("|\n");
	}
	for(i = 0; i <= COUNT_I; i++)
		printf("-");
	printf("\n");

	return 0;
}

●心得:

  這兩種演算法都離不開列舉與回溯這兩大思想,在以後的學習中要多加理解這兩種思想。在初次接觸時理解不了遞迴的過程,後來在之上慢慢畫模擬計算機的執行過程終於讓遞迴這個過程瞭然於胸。可見在程式設計中驗算是多麼的重要。