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