迷宮-深度優先搜索-打印所有可行路徑

繼上一篇“迷宮-廣度優先搜索-最短路徑並打印該條最短路徑”——https://mp.csdn.net/postedit/103229718,想着如何才能把所有可行路徑打印出來,網上看了些資料都是推薦使用深度優先搜索方法,但是沒看到過完全的實現,因此有了這次自己記錄。

目錄

1.本文例子的迷宮如下:

2.深度優先的基本思路-

3.只考慮一條路徑的實現

  (1) 棧代碼

(2)深度遍歷代碼

(3)運行結果

4.打印所有路徑

(1)從3容易想的是以下2點

(2)代碼

(3)運行結果

5.節點的遍歷方向及節點出棧時恢復該節點的標記

(1)分析

(2)最終思路

(3)對節點已搜索的方向及點已在棧中的狀態如何標記

(4)標記代碼

(5)打印所有路徑的深度搜索代碼

6.源碼鏈接


1.本文例子的迷宮如下:

起點(0,0),需要打印到終點(2,3)的所有可行路徑?

0

0

1

0

0

0

0

0

1

1

1

0

0

0

1

0

 

上篇提到如果是隻要求最短路徑並打印推薦使用廣度優先,求所有路徑使用深度優先比較好。下面是一層層的加深思考

2.深度優先的基本思路-

一條道到黑,走不下去了立即回頭。

 1.起始點入棧,訪問棧頂並尋找下一個合適的點,節點合法就入棧並用DP數組標記,1表示已在棧中(已遍歷過)

 2.繼續1,一直循環直到該點找不到合適的下一個狀態點就出棧返回上一層,直到棧爲空結束遍歷

3.只考慮一條路徑的實現

        用2的思路其實實現一條路徑並打印其實是非常簡單的,但是還是要手寫棧,或者遞歸方式,本人暫時不喜歡用遞歸。

  (1) 棧代碼

/*棧開始*/
typedef struct Stack_
{
	int cap;
	int top;
	int** data; 
}STACK_S;

void Stack_Init(STACK_S* stack,int cap,int size)
{
	int i = 0;
	stack->cap = cap;
	stack->top = -1;
	stack->data = (int**)malloc(sizeof(int*)*cap);
	for(i = 0; i <= cap -1 ;i++)
	{
		stack->data[i] = (int*)malloc(sizeof(int)*size);
		memset(stack->data[i],0,sizeof(int)*size);
	}
}

int Stack_Is_Full(STACK_S* stack)
{
	return (stack->top == stack->cap) ? 1 : 0;
}

int Stack_Is_Empty(STACK_S* stack)
{
	return (stack->top == -1) ? 1 : 0;
}

void Stack_Pushback(STACK_S* stack,int x,int y)
{
	if(Stack_Is_Full(stack) != 1)
	{
		stack->top++;
		stack->data[stack->top][0] = x;
		stack->data[stack->top][1] = y;
	}
}
int* Stack_GetTop(STACK_S* stack)
{
	if(Stack_Is_Empty(stack) != 1)
	{
		return stack->data[stack->top];
	}
	else
		return NULL;
}

void Stack_Pop(STACK_S* stack)
{
	if(Stack_Is_Empty(stack) != 1)
	{
		stack->top--;
	}
}
/******************棧結束***************/

(2)深度遍歷代碼

/*打印棧中的路徑**/
void printf_stack_path(STACK_S* stack)
{
	STACK_S stack_temp = {0};
	int* data = NULL;
	Stack_Init(&stack_temp,stack->cap,2);
	//把棧給放到另一個棧
	while(Stack_Is_Empty(stack) != 1)
	{
		data = Stack_GetTop(stack);
		Stack_Pushback(&stack_temp,data[0],data[1]);
		Stack_Pop(stack);
	}
	
	while(Stack_Is_Empty(&stack_temp) != 1)
	{
		data = Stack_GetTop(&stack_temp);
		printf("(%d,%d)->",data[0],data[1]);
		Stack_Pushback(stack,data[0],data[1]);
		Stack_Pop(&stack_temp);
	}
	//退格把最後一個"->"去掉
	printf("\b\b\t\n");	
}
void Maze_Dfs_All_Path(int** maze,int max_x,int max_y,int* start,int* end,STACK_S* stack,int** dp)
{
	int i = 0;
	int next_x = 0,next_y = 0;
	int *cur = 0;
	int pre_x = -1,pre_y = -1;
	//起點入棧並標記
	Stack_Pushback(stack,start[0],start[1]);
	*(((int*)dp)+max_y*start[0]+start[1]) = 1;

	while(Stack_Is_Empty(stack) != 1)
	{
		cur = Stack_GetTop(stack);
		//printf("cur:(%d,%d)\n",cur[0],cur[1]);
		for(i =0; i <= MAX_DIR -1;i++ )
		{
			next_x = cur[0]+s_dirs[i][0];
			next_y = cur[1]+s_dirs[i][1];
			//printf("next:(%d,%d)\n",next_x,next_y);
			if(Point_Is_Boundry(max_x,max_y,next_x,next_y) == 1)//不能是邊界意外
				continue;
			if( *(((int*)maze)+max_y*next_x+next_y) == 1)//不能是牆
				continue;
			if(*(((int*)dp)+max_y*next_x+next_y) == 1)//不能被標記,及已經在棧中了
			{
				//printf("dp is 1 (%d,%d)\n",next_x,next_y);
				continue;
			}
			//printf("now (%d,%d) push back\n",next_x,next_y);
			Stack_Pushback(stack,next_x,next_y);
			*(((int*)dp)+max_y*next_x+next_y) = 1;
			
			if(next_x == end[0] && next_y == end[1])
			{
				printf_stack_path(stack);
				return;
			}
			else
				break;
			
		}
		if(i >= MAX_DIR)
			Stack_Pop(stack);
	}
}

(3)運行結果

 

4.打印所有路徑

(1)從3容易想的是以下2點

a.在找到終點時,不能return而是應該continue

b.continue前應將頂點出棧,並需要設置成非標記狀態並,否則下次遇到終點,終點由於被標記無法入棧

(2)代碼

void Maze_Dfs_All_Path(int** maze,int max_x,int max_y,int* start,int* end,STACK_S* stack,int** dp)
{
	int i = 0;
	int next_x = 0,next_y = 0;
	int *cur = 0;
	int pre_x = -1,pre_y = -1;
	//起點入棧並標記
	Stack_Pushback(stack,start[0],start[1]);
	*(((int*)dp)+max_y*start[0]+start[1]) = 1;

	while(Stack_Is_Empty(stack) != 1)
	{
		cur = Stack_GetTop(stack);
		//printf("cur:(%d,%d)\n",cur[0],cur[1]);
		for(i =0; i <= MAX_DIR -1;i++ )
		{
			next_x = cur[0]+s_dirs[i][0];
			next_y = cur[1]+s_dirs[i][1];
			//printf("next:(%d,%d)\n",next_x,next_y);
			if(Point_Is_Boundry(max_x,max_y,next_x,next_y) == 1)//不能是邊界意外
				continue;
			if( *(((int*)maze)+max_y*next_x+next_y) == 1)//不能是牆
				continue;
			if(*(((int*)dp)+max_y*next_x+next_y) == 1)//不能被標記,及已經在棧中了
			{
				//printf("dp is 1 (%d,%d)\n",next_x,next_y);
				continue;
			}
			//printf("now (%d,%d) push back\n",next_x,next_y);
			Stack_Pushback(stack,next_x,next_y);
			*(((int*)dp)+max_y*next_x+next_y) = 1;
			
			if(next_x == end[0] && next_y == end[1])
			{
				printf_stack_path(stack);
				//終點出棧如果不進行恢復標記的話,下一次就不會進了
				*(((int*)dp)+max_y*end[0]+end[1]) = 0;
				Stack_Pop(stack);
				continue;
			}
			else
				break;	
		}
		if(i >= MAX_DIR)
			Stack_Pop(stack);
	}
}

(3)運行結果

期望結果

實際結果

 

原因分析

可看出其實是少了2條路徑的,打開代碼中那些printf可以看到其實在(0,0)->(0,1) ->(1,1)時,訪問棧頂(1,1)然後按照左、右、上、下的順序,下一節點(1,0)入棧結果發現(1,0)這個節點的四個方向的下一個節點都不合法,導致(1,0)出棧,但是(1,0)已經被DP標記,所以導致在找完前2條路徑後彈棧,知道棧頂爲(0,0)時,開始“左、右、上、下”發現(1,0)已經被標記

5.節點的遍歷方向及節點出棧時恢復該節點所有標記

(1)分析

由4中原因可以看出,當一個節點在棧中被彈出後他的“在棧標記”應該被取消,否則會導致路徑遺漏。但是如果不考慮方向的情況下,(0,0)->(0,1) ->(1,1)->(1,0)當(1,0)被彈出後標記被取消,那麼棧頂(1,1)又會從4個方向去遍歷,從而又將(1,0)入棧,從而死循環。

因此彈棧取消“在棧標記”引出的新問題可以通過“方向標記”來避免進入死循環,即(1,1)向左遍歷到(1,0)可入棧時,(1,1)節點的左遍歷方向應該被標記。

(2)最終思路

     a.根幾點入棧,從四個方向中未被標記的方向搜尋合法的下一個狀態(節點),如果合法,棧頂節點方向需要標記,下一節點的“在棧標記”被標記

     b.繼續訪問棧頂,重複上一步驟,如果棧頂節點沒有合法的下一個節點,則棧頂出棧,並將棧頂的“在棧標記取消”,【其實在棧標記是爲了下一節點成爲棧頂元素時不會反向遍歷】,出棧取消可以避免路徑遺漏;當然“方向標記”也要取消,否則即使該節點下次重新入棧成爲棧頂,也不會起作用,因爲方向標記全是已標記

     c.期間如果下一個狀態是終點,則調用打印路徑函數打印,之後終點也當一般棧頂處理,出棧並取消標記

(3)對節點已搜索的方向及點已在棧中的狀態如何標記

本代碼中用DP數組的元素的bit0-3分表標識該節點的左右上下四個方向標記,bit4標識“在棧標記”

(4)標記代碼

//判斷DP的某個bit是否爲1,比如查在棧標記IS_DP_BITN_TRUE(DP[0][1],4)
#define IS_DP_BITN_TRUE(value,bitn) ((value) >> (bitn)) & 0x1 
//設置DP的某個bit爲1或者0
#define SET_DP_BITN(value,bitn,flag) \
	if((flag) == 1)\
	{\
		(value) = ((value) | (0x1 << bitn));\
	}\
	else\
		(value) = ((value) & (~(0x1<<bitn)))
//清除節點的所有標記
#define CLEAR_DP_ALL(value) (value) = 0x0

(5)打印所有路徑的深度搜索代碼

tips:取消printf的註釋可以顯示節點進出站棧過程

void Maze_Dfs_All_Path(int** maze,int max_x,int max_y,int* start,int* end,STACK_S* stack,int** dp)
{
	int i = 0;
	int next_x = 0,next_y = 0;
	int *cur = 0;
	int pre_x = -1,pre_y = -1;
	//起點入棧
	Stack_Pushback(stack,start[0],start[1]);
	SET_DP_BITN(*(((int*)dp)+max_y*start[0]+start[0]),4,1);
	
	while(Stack_Is_Empty(stack) != 1)
	{
		cur = Stack_GetTop(stack);
		//printf("cur:(%d,%d)\n",cur[0],cur[1]);
		for(i =0; i <= MAX_DIR -1;i++ )
		{
			next_x = cur[0]+s_dirs[i][0];
			next_y = cur[1]+s_dirs[i][1];
			//printf("next:(%d,%d)\n",next_x,next_y);
			if(Point_Is_Boundry(max_x,max_y,next_x,next_y) == 1)
			{
				//printf("Boundry:(%d,%d)\n",next_x,next_y);
				continue;
			}
			if( *(((int*)maze)+max_y*next_x+next_y) == 1)
			{
				//printf("maze is 1 (%d,%d)\n",next_x,next_y);
				continue;
			}
			//如果下一個節點已經標記,則不合法繼續尋找下一個方向
			if(IS_DP_BITN_TRUE(*(((int*)dp)+max_y*next_x+next_y),4))
			{
				//printf("dp is 1 (%d,%d)\n",next_x,next_y);
				continue;
			}
			//如果本節點方向已經被遍歷則不繼續
			if(IS_DP_BITN_TRUE(*(((int*)dp)+max_y*cur[0]+cur[1]),i))
			{
				//printf("(%d,%d) 的 %d 方向已經遍歷\n",cur[0],cur[1],i);
				continue;
			}
			//printf("now (%d,%d) push back\n",next_x,next_y);
			Stack_Pushback(stack,next_x,next_y);
			//設置下一節點爲已經訪問,以及本節點的方向
			SET_DP_BITN(*(((int*)dp)+max_y*next_x+next_y),4,1);
			SET_DP_BITN(*(((int*)dp)+max_y*cur[0]+cur[1]),i,1);
			if(next_x == end[0] && next_y == end[1])
			{
				printf_stack_path(stack);
				//終點出棧如果不進行恢復標記的話,下一次就不會進了
				Stack_Pop(stack);
				CLEAR_DP_ALL(*(((int*)dp)+max_y*end[0]+end[1]));
				continue;
			}
			else
				break;	
		}
		if(i >= MAX_DIR)
		{
			CLEAR_DP_ALL(*(((int*)dp)+max_y*cur[0]+cur[1]));
			//printf("(%d,%d) pop and clear all dp bit,dp %d\n",cur[0],cur[1],*(((int*)dp)+max_y*cur[0]+cur[1]));
			Stack_Pop(stack);
		}
	}
}

6.源碼鏈接

PS:長度只需要在打印時統計棧中節點個數其實就可以,文中代碼這部分未做

1.只求一條路徑並打印長度:https://pan.baidu.com/s/1ZgL2bPIv6koNfoChBdJkwQ

2.打印所有路徑:https://pan.baidu.com/s/1064x3hyQ23scaAho7e9Cbw

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

/*大概的思路是:
1.找到一個合適的點(非邊界,非標記)就入棧,訪問棧頂,直到該棧頂找不到下一個點纔出棧,返回上一層
2.入棧時需要進行標記,當找到終點時同樣入棧,並調用打印函數,把這個終點出棧並恢復標記即可即可,繼續搜索

其實需要考慮如下場景
0 0 0
0 0 0 
1 0 0
路線(0,0)->(0,1)->(1,1)->(1,0)時,此時會對棧頂(1,0)進行4個方向的遍歷,發現往左不行,往右已經被標記,往上也被標記,往下不行,

此時出棧,但是沒有恢復比標記,當一直出棧到(0,0)發現往下元素本來合法的但是因爲被標記導致(0,0)->(1,0)->(1,1)路線被堵頂,

所以需要增加如下方式:即對於一個當前的頂點,如果他的四個方向都沒有遍歷的話*/

#define MAZE_MAX_X 4
#define MAZE_MAX_Y 4

#define MAX_DIR 4
#define DIM 2

#define IS_DP_BITN_TRUE(value,bitn) ((value) >> (bitn)) & 0x1 

#define SET_DP_BITN(value,bitn,flag) \
	if((flag) == 1)\
	{\
		(value) = ((value) | (0x1 << bitn));\
	}\
	else\
		(value) = ((value) & (~(0x1<<bitn)))

#define CLEAR_DP_ALL(value) (value) = 0x0
	


	/*方向的模擬,座標原點在左上方,向左的話其實是列減1*/
	//左,右,上,下
static const int s_dirs[MAX_DIR][DIM] = {{0,-1},{0,1},{-1,0},{1,0}};


/*棧開始*/
typedef struct Stack_
{
	int cap;
	int top;
	int** data; 
}STACK_S;

void Stack_Init(STACK_S* stack,int cap,int size)
{
	int i = 0;
	stack->cap = cap;
	stack->top = -1;
	stack->data = (int**)malloc(sizeof(int*)*cap);
	for(i = 0; i <= cap -1 ;i++)
	{
		stack->data[i] = (int*)malloc(sizeof(int)*size);
		memset(stack->data[i],0,sizeof(int)*size);
	}
}

int Stack_Is_Full(STACK_S* stack)
{
	return (stack->top == stack->cap) ? 1 : 0;
}

int Stack_Is_Empty(STACK_S* stack)
{
	return (stack->top == -1) ? 1 : 0;
}

void Stack_Pushback(STACK_S* stack,int x,int y)
{
	if(Stack_Is_Full(stack) != 1)
	{
		stack->top++;
		stack->data[stack->top][0] = x;
		stack->data[stack->top][1] = y;
	}
}
int* Stack_GetTop(STACK_S* stack)
{
	if(Stack_Is_Empty(stack) != 1)
	{
		return stack->data[stack->top];
	}
	else
		return NULL;
}

void Stack_Pop(STACK_S* stack)
{
	if(Stack_Is_Empty(stack) != 1)
	{
		stack->top--;
	}
}
/******************棧結束***************/


int Point_Is_Boundry(int max_x,int max_y,int x,int y)
{
	if(x < 0 || x >= max_x || y < 0 || y >= max_y)
		return 1;
	return 0;
}

/*打印棧中的路徑**/
void printf_stack_path(STACK_S* stack)
{
	STACK_S stack_temp = {0};
	int* data = NULL;
	Stack_Init(&stack_temp,stack->cap,2);
	//把棧給放到另一個棧
	while(Stack_Is_Empty(stack) != 1)
	{
		data = Stack_GetTop(stack);
		Stack_Pushback(&stack_temp,data[0],data[1]);
		Stack_Pop(stack);
	}
	while(Stack_Is_Empty(&stack_temp) != 1)
	{
		data = Stack_GetTop(&stack_temp);
		printf("(%d,%d)->",data[0],data[1]);
		Stack_Pushback(stack,data[0],data[1]);
		Stack_Pop(&stack_temp);
	}
	//退格把最後一個"->"去掉
	printf("\b\b\t\n");	

}

	void Maze_Dfs_All_Path(int** maze,int max_x,int max_y,int* start,int* end,STACK_S* stack,int** dp)
	{
		int i = 0;
		int next_x = 0,next_y = 0;
		int *cur = 0;
		int pre_x = -1,pre_y = -1;
		//起點入棧
		Stack_Pushback(stack,start[0],start[1]);
		SET_DP_BITN(*(((int*)dp)+max_y*start[0]+start[0]),4,1);
		
		while(Stack_Is_Empty(stack) != 1)
		{
			cur = Stack_GetTop(stack);
			//printf("cur:(%d,%d)\n",cur[0],cur[1]);
			for(i =0; i <= MAX_DIR -1;i++ )
			{
				next_x = cur[0]+s_dirs[i][0];
				next_y = cur[1]+s_dirs[i][1];
				//printf("next:(%d,%d)\n",next_x,next_y);
				if(Point_Is_Boundry(max_x,max_y,next_x,next_y) == 1)
				{
					//printf("Boundry:(%d,%d)\n",next_x,next_y);
					continue;
				}
				if( *(((int*)maze)+max_y*next_x+next_y) == 1)
				{
					//printf("maze is 1 (%d,%d)\n",next_x,next_y);
					continue;
				}
				//如果下一個節點已經標記,則不合法繼續尋找下一個方向
				if(IS_DP_BITN_TRUE(*(((int*)dp)+max_y*next_x+next_y),4))
				{
					//printf("dp is 1 (%d,%d)\n",next_x,next_y);
					continue;
				}
				//如果本節點方向已經被遍歷則不繼續
				if(IS_DP_BITN_TRUE(*(((int*)dp)+max_y*cur[0]+cur[1]),i))
				{
					//printf("(%d,%d) 的 %d 方向已經遍歷\n",cur[0],cur[1],i);
					continue;
				}
				//printf("now (%d,%d) push back\n",next_x,next_y);
				Stack_Pushback(stack,next_x,next_y);
				//設置下一節點爲已經訪問,以及本節點的方向
				SET_DP_BITN(*(((int*)dp)+max_y*next_x+next_y),4,1);
				SET_DP_BITN(*(((int*)dp)+max_y*cur[0]+cur[1]),i,1);
				if(next_x == end[0] && next_y == end[1])
				{
					printf_stack_path(stack);
					//終點出棧如果不進行恢復標記的話,下一次就不會進了
					Stack_Pop(stack);
					CLEAR_DP_ALL(*(((int*)dp)+max_y*end[0]+end[1]));
					continue;
				}
				else
					break;	
			}
			if(i >= MAX_DIR)
			{
				CLEAR_DP_ALL(*(((int*)dp)+max_y*cur[0]+cur[1]));
				//printf("(%d,%d) pop and clear all dp bit,dp %d\n",cur[0],cur[1],*(((int*)dp)+max_y*cur[0]+cur[1]));
				Stack_Pop(stack);
			}
		}
	}

int main()
{
	int len = 0, i =0,j = 0;
	//bit0:1:2:3分別表示左右前後的標誌位,bit4表示是否標記被訪問
	int dp[MAZE_MAX_X][MAZE_MAX_Y] = {0};
	int maze[MAZE_MAX_X][MAZE_MAX_Y] = {
		{0,0,1,0},
		{0,0,0,0},
		{1,1,0,0},
		{0,0,1,0},
	};
	//起點、終點
	int start[2] = {0,0};
	int end[2] = {2,3};
	STACK_S stack = {0};
	Stack_Init(&stack,100,2);
	memset((int*)dp,0,sizeof(int)*MAZE_MAX_X*MAZE_MAX_Y);
	Maze_Dfs_All_Path((int**)maze,MAZE_MAX_X,MAZE_MAX_Y,start,end,&stack,(int**)dp);
	return 0;
}

 

發佈了13 篇原創文章 · 獲贊 5 · 訪問量 2101
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章