- 回溯法:对一个包括有很多个结点,每个结点有若干个搜索分支的问题,把原问题分解为若干个子问题求解的算法;当搜索到某个结点发现无法再继续搜索下去时,就让搜索过程回溯(回退)到该节点的前一个结点,继续搜索该节点外的其他尚未搜索的分支;如果发现该结点无法再搜索下去,就让搜索过程回溯到这个结点的前一结点继续这样的搜索过程;这样的搜索过程一直进行到搜索到问题的解或者搜索完了全部可搜索分支没有解存在为止。
- 首先我们先来定义一张简单的迷宫地图:
图中0表示墙,即无法落脚;1表示可以落脚;之后我们会用2来标记走过的路。
我们默认出口为四个边界上的点。若入口也为边界点,则出口与入口不能重合。
要解决这个问题,我们还是要基于栈的基本操作,依赖栈结构来辅助完成回溯法。而这里的栈可以是内存地址空间中的栈,也可以我们自己手动创建一个栈来实现。这两种方法我们在下面的代码中都会有所实现。
第一种方法:基于地址空间中的栈辅助完成回溯(递归式)
maze.h:
#pragma once #include<stdio.h> #include<stdlib.h> #include<stddef.h> #define MAX_ROW 6 #define MAX_COL 6 typedef struct Maze{ int map[MAX_ROW][MAX_COL]; }Maze; typedef struct Point{ int row; int col; }Point; typedef Point SeqStackType; typedef struct SeqStack{ SeqStackType *data; size_t size; size_t capacity; }SeqStack;
maze.c:
#include"maze.h" int map[MAX_ROW][MAX_COL]={ {0,1,0,0,0,0}, {0,1,1,1,0,0}, {0,1,0,1,1,0}, {0,1,1,0,0,0}, {0,0,1,0,0,0}, {0,0,1,0,0,0} }; void MazeInit(Maze* maze){ if(maze == NULL) return; size_t i = 0; for(;i < MAX_ROW;i++){ size_t j = 0; for(;j < MAX_COL;j++){ maze->map[i][j] = map[i][j]; } } return; } void MazePrint(Maze* maze){ if(maze == NULL) return; size_t i = 0; for(;i < MAX_ROW;i++){ size_t j = 0; for(;j < MAX_COL;j++){ printf("%2d ",maze->map[i][j]); } printf("\n"); } return; } int CanStay(Maze* maze,Point pt){ if(pt.row < 0 || pt.row >= MAX_ROW || pt.col < 0 || pt.col >= MAX_COL){ return 0; } int value = maze->map[pt.row][pt.col]; if(value == 1){ return 1; } return 0; } void Mark(Maze* maze,Point cur){ maze->map[cur.row][cur.col] = 2; } int IsExit(Maze* maze,Point cur,Point entry){ (void)maze; //1.判断当前点是不是入口,若为入口,则不是出口 if(cur.row == entry.row && cur.col == entry.col){ return 0; } //2.如果当前点在地图边界上,说明是出口 if(cur.row == 0 || cur.row == MAX_ROW-1 || cur.col == 0 || cur.col == MAX_COL-1){ return 1; } return 0; } void _GetPath(Maze* maze,Point cur,Point entry){ printf("cur:(%d,%d)\n",cur.row,cur.col); //1.判断当前点能否落脚 if(!CanStay(maze,cur)){ return; } //2.若能落脚,给当前位置做一个标记 Mark(maze,cur); //3.若当前点为出口,说明找到了一条出口,探测结束 if(IsExit(maze,cur,entry)){ printf("找到了一条出路\n"); return; } //4.若当前点不是出口,则按顺时针方向探测四个相邻的点,递归式调用函数自身,递归式更新cur节点 //(每次递归时,cur都是下一次要走的点,这个点能否落脚,交给递归函数作判断) Point up = cur; up.row -= 1; _GetPath(maze,up,entry); Point right = cur; right.col += 1; _GetPath(maze,right,entry); Point down = cur; down.row += 1; _GetPath(maze,down,entry); Point left = cur; left.col -= 1; _GetPath(maze,left,entry); } void GetPath(Maze* maze,Point entry){ if(maze == NULL){ return; } _GetPath(maze,entry,entry); }
test.c:
#include "maze.h" #define PRINT_HEADER printf("\n============%s============\n",__FUNCTION__) void Test1(){ PRINT_HEADER; Maze maze; MazeInit(&maze); Point entry = {0,1}; GetPath(&maze,entry); MazePrint(&maze); } int main(){ Test1(); return 0; }
结果演示:
第二种方法:自己手动创建一个栈,然后基于自己创建的栈实现回溯(非递归式)
maze.h:
#pragma once #include<stdio.h> #include<stdlib.h> #include<stddef.h> #define MAX_ROW 6 #define MAX_COL 6 typedef struct Maze{ int map[MAX_ROW][MAX_COL]; }Maze; typedef struct Point{ int row; int col; }Point; typedef Point SeqStackType; typedef struct SeqStack{ SeqStackType *data; size_t size; size_t capacity; }SeqStack;
maze.c:
#include"maze.h" int map[MAX_ROW][MAX_COL]={ {0,1,0,0,0,0}, {0,1,1,1,0,0}, {0,1,0,1,1,0}, {0,1,1,0,0,0}, {0,0,1,0,0,0}, {0,0,1,0,0,0} }; void MazeInit(Maze* maze){ if(maze == NULL) return; size_t i = 0; for(;i < MAX_ROW;i++){ size_t j = 0; for(;j < MAX_COL;j++){ maze->map[i][j] = map[i][j]; } } return; } void MazePrint(Maze* maze){//打印迷宫 if(maze == NULL) return; size_t i = 0; for(;i < MAX_ROW;i++){ size_t j = 0; for(;j < MAX_COL;j++){ printf("%2d ",maze->map[i][j]); } printf("\n"); } return; } int CanStay(Maze* maze,Point pt){//判断当前点是否能落脚 if(pt.row < 0 || pt.row >= MAX_ROW || pt.col < 0 || pt.col >= MAX_COL){ return 0; } int value = maze->map[pt.row][pt.col]; if(value == 1){ return 1; } return 0; } void Mark(Maze* maze,Point cur){//标记走过的路径 maze->map[cur.row][cur.col] = 2; } int IsExit(Maze* maze,Point cur,Point entry){ (void)maze; //1.判断当前点是不是入口,若为入口,则不是出口 if(cur.row == entry.row && cur.col == entry.col){ return 0; } //2.如果当前点在地图边界上,说明是出口 if(cur.row == 0 || cur.row == MAX_ROW-1 || cur.col == 0 || cur.col == MAX_COL-1){ return 1; } return 0; } void GetPathByLoop(Maze* maze,Point entry){ if(maze == NULL) return; //1.创建一个栈,并且初始化,这个栈保存着走过的路径 SeqStack stack; SeqStackInit(&stack); //2.判定入口点能否落脚,若不能,说明参数非法,则直接返回 if(!CanStay(maze,entry)){ return; } //3.标记入口点,并将入口点入栈 Mark(maze,entry); SeqStackPush(&stack,entry); //4.进入循环,获取到当前栈的栈顶元素 Point cur; while(GetTop(&stack,&cur)){ //5.判定这个点是否为出口,若为出口,直接函数返回 if(IsExit(maze,cur,entry)){ printf("找到了一条路径\n"); return; } //6.按照顺时针方向取相邻点,判断相邻点能否落脚,若能落脚,就标记并入栈,立即进入下一轮循环 Point up = cur; up.row -= 1; if(CanStay(maze,up)){ Mark(maze,up); SeqStackPush(&stack,up); continue; } Point right = cur; right.col += 1; if(CanStay(maze,right)){ Mark(maze,right); SeqStackPush(&stack,right); continue; } Point down = cur; down.row += 1; if(CanStay(maze,down)){ Mark(maze,down); SeqStackPush(&stack,down); continue; } Point left = cur; left.col -= 1; if(CanStay(maze,left)){ Mark(maze,left); SeqStackPush(&stack,left); continue; } //7.若四个相邻点均不能落脚,就出栈当前点,相当于进行回溯 SeqStackPop(&stack); } return; }
test.c:
#include "maze.h" #define PRINT_HEADER printf("\n============%s============\n",__FUNCTION__) void Test2(){ PRINT_HEADER; Maze maze; MazeInit(&maze); Point entry = {0,1}; GetPathByLoop(&maze,entry); MazePrint(&maze); } int main(){ Test2(); return 0; }
结果演示:
至此,我们求解简单迷宫是否存在路径的问题就解决了。