回溯法的整体思想是:
当搜索到某个结点发现无法再继续搜索下去时,就让搜索过程回溯(回退)到该节点的前一个结点,继续搜索该节点外的其他尚未搜索的分支;如果发现该结点 无法再搜索下去,就让搜索过程回溯到这个结点的前一结点,继续这样的搜索过程;这样的搜索过程一直进行到搜索到问题的解或者搜索完了全部可搜索分支没有解存在为止。
我们这里写的迷宫就是利用以上思想,按照左上右下的方向进行搜索,当四面都没有通路的时候,就回溯到上一个结点,搜索其他尚未搜索的方向,如果还是没有通路,就继续回溯。直到回到起点或者找到通路的时候方可停止。这里我们是通过栈来实现回溯的,利用了栈的特点,先进后出,当我们发现一个位置四面八方都没有通路的时候,出栈,回到上一个位置。
接下来让我们来实现迷宫:
一、创建一个迷宫
这里我们实现一个有多通路且内部带环的迷宫。(为了方便呢,我用的大都是全局变量)
用一个二维数组来创建一个迷宫,这里我用0表示墙,1表示通路,2表示走过的路,但是在打印迷宫的时候可以选用自己喜欢的字符。
在打印的时候我用"█ "
表示墙," "
表示通路,"☺ "
表示走过的路。
以下是在走迷宫的过程中打印的一个迷宫:
代码如下:
//打印迷宫
void PrintMaze(){
for(int i=0; i<ROWS; i++){
for(int j=0; j<COLS; j++){
if(Maze[i][j] == 0){
printf("█ ");
}
if(Maze[i][j] == 1){
printf(" ");
}
if(Maze[i][j] == 2){
printf("☺ ");
}
}
printf("\n");
}
printf("\n\n");
}
顺便我们可以把迷宫的入口和出口也设定一下:
首先需要一个结构体来保存走迷宫的位置,也就是横纵坐标:
typedef struct Position{
int x;
int y;
} Position;
我们把迷宫入口设置为:
//起始位置
Position start = {5, 2};
出口设成多个,最后一列都为出口:
//判断是否走出迷宫
int IsExit(Position pos){
if(pos.y == COLS-1){
return 1;
}
else{
return 0;
}
}
二、用栈来保存走过的位置
栈的特点是先进后出,十分适合我们的迷宫的回溯,当找到一条通路的时候,进栈,直到四面八方都没有通路的时候,出栈,回到上一个位置并把这个位置还原回去,继续搜索没有搜索过的方向。直到找到出口或者回到原点。
这里我们自己写一个栈,实现栈的声明、初始化、销毁、进栈、出栈、查看栈顶元素、栈中元素的个数、判断栈是否空/满。因为后面走迷宫的时候想要找到一个最短路径,所以我们需要拿一个栈来保存最短路径,这里需要用到栈的拷贝,当最小栈为空或者现在的栈中元素小与最小栈的时候,就把现在的栈拷贝到最小栈,所以一并在这里实现。最后写一个打印栈,可以看看是怎么走出迷宫的。
typedef Position DataType;
#define MAX_SIZE (100)
//栈的声明
typedef struct Stack{
DataType array[MAX_SIZE];
int top;
} Stack;
//初始化
void StackInit(Stack *p){
assert(p);
p->top = 0;
}
//销毁
void StackDestroy(Stack *p){
p->top = 0;
}
//进栈
void StackPush(Stack *p, DataType data){
assert(p->top < MAX_SIZE);
p->array[p->top++] = data;
}
//出栈
void StackPop(Stack *p){
assert(p->top > 0);
p->top--;
}
//查看栈顶元素
DataType StackTop(Stack *p){
return p->array[p->top-1];
}
//返回栈中元素的个数
int StackSize(Stack *p){
return p->top;
}
//判断栈是否为空
int StackIsEmpty(Stack *p){
return p->top <= 0;
}
//判断栈是否满
int StackIsFull(Stack *p){
return p->top >= MAX_SIZE;
}
//拷贝栈
void CopyStack(Stack *dest, Stack *src){
for(int i=0; i<src->top; i++){
dest->array[i] = src->array[i];
}
dest->top = src->top;
}
//打印栈
void PrintStack(Stack *p){
for (int i = 0; i < p->top; i++) {
printf("x = %d, y = %d\n", p->array[i].x, p->array[i].y);
}
}
三、走迷宫
这里我们分装出一个函数判断走到某个位置时是否能通过:
//判断是否能通过
int CanPass(Position pos){
if(Maze[pos.x][pos.y] == 1){
return 1;
}
return 0;
}
走迷宫的主体函数:
利用递归+栈实现回溯,代码很好懂
Stack stack;
Stack min;
void GoMazes(Position pos){
Position now = pos;//记录当前位置
Position next;//记录下一步的位置
StackPush(&stack, now);//当前位置进栈
Maze[now.x][now.y] = 2;//将当前位置改为走过
PrintMaze();//打印迷宫
if(IsExit(now)){
Maze[now.x][now.y] = 1;
if(StackIsEmpty(&min) || StackSize(&stack) < StackSize(&min)){
CopyStack(&min, &stack);
}
printf("成功走出迷宫!\n走了%d步\n",StackSize(&stack));
PrintStack(&stack);
StackPop(&stack);
return;
}
//我是按照 左 上 右 下 的顺序
next.x = now.x;
next.y = now.y-1;
if(CanPass(next)){
PrintMaze();
GoMazes(next);
}
next.x = now.x-1;
next.y = now.y;
if(CanPass(next)){
PrintMaze();
GoMazes(next);
}
next.x = now.x;
next.y = now.y+1;
if(CanPass(next)){
PrintMaze();
GoMazes(next);
}
next.x = now.x+1;
next.y = now.y;
if(CanPass(next)){
PrintMaze();
GoMazes(next);
}
//走到这里的时候四面八方都没有路,只能退回到上一个位置,并将其还原为通路
StackPop(&stack);
Maze[now.x][now.y] = 1;
return;
}
最后献上整体代码:
也就是把上面的板块拼凑在一起
#include <stdio.h>
#include <assert.h>
#define ROWS (6)
#define COLS (6)
typedef struct Position{
int x;
int y;
} Position;
int Maze[ROWS][COLS] = {
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 1 },
{ 0, 0, 1, 0, 1, 0 },
{ 0, 0, 1, 0, 1, 0 },
{ 0, 0, 1, 1, 1, 1 },
{ 0, 0, 1, 0, 0, 0 }
};
typedef Position DataType;
#define MAX_SIZE (100)
typedef struct Stack{
DataType array[MAX_SIZE];
int top;
} Stack;
void StackInit(Stack *p){
assert(p);
p->top = 0;
}
void StackDestroy(Stack *p){
p->top = 0;
}
void StackPush(Stack *p, DataType data){
assert(p->top < MAX_SIZE);
p->array[p->top++] = data;
}
void StackPop(Stack *p){
assert(p->top > 0);
p->top--;
}
DataType StackTop(Stack *p){
return p->array[p->top-1];
}
int StackSize(Stack *p){
return p->top;
}
int StackIsEmpty(Stack *p){
return p->top <= 0;
}
int StackIsFull(Stack *p){
return p->top >= MAX_SIZE;
}
void CopyStack(Stack *dest, Stack *src){
for(int i=0; i<src->top; i++){
dest->array[i] = src->array[i];
}
dest->top = src->top;
}
//打印栈
void PrintStack(Stack *p){
for (int i = 0; i < p->top; i++) {
printf("x = %d, y = %d\n", p->array[i].x, p->array[i].y);
}
}
//打印迷宫
void PrintMaze(){
for(int i=0; i<ROWS; i++){
for(int j=0; j<COLS; j++){
if(Maze[i][j] == 0){
printf("█ ");
}
if(Maze[i][j] == 1){
printf(" ");
}
if(Maze[i][j] == 2){
printf("☺ ");
}
}
printf("\n");
}
printf("\n\n");
}
//起始位置
Position start = {5, 2};
//判断是否走出迷宫
int IsExit(Position pos){
if(pos.y == COLS-1){
return 1;
}
else{
return 0;
}
}
//判断是否能通过
int CanPass(Position pos){
if(Maze[pos.x][pos.y] == 1){
return 1;
}
return 0;
}
Stack stack;
Stack min;
void GoMazes(Position pos){
Position now = pos;//记录当前位置
Position next;//记录下一步的位置
StackPush(&stack, now);
Maze[now.x][now.y] = 2;
PrintMaze();
if(IsExit(now)){
Maze[now.x][now.y] = 1;
if(StackIsEmpty(&min) || StackSize(&stack) < StackSize(&min)){
CopyStack(&min, &stack);
}
printf("成功走出迷宫!\n走了%d步\n",StackSize(&stack));
PrintStack(&stack);
StackPop(&stack);
return;
}
//按照 左 上 右 下 的顺序
next.x = now.x;
next.y = now.y-1;
if(CanPass(next)){
PrintMaze();
GoMazes(next);
}
next.x = now.x-1;
next.y = now.y;
if(CanPass(next)){
PrintMaze();
GoMazes(next);
}
next.x = now.x;
next.y = now.y+1;
if(CanPass(next)){
PrintMaze();
GoMazes(next);
}
next.x = now.x+1;
next.y = now.y;
if(CanPass(next)){
PrintMaze();
GoMazes(next);
}
StackPop(&stack);
Maze[now.x][now.y] = 1;
return;
}
int main(int argc, const char * argv[]) {
PrintMaze();
StackInit(&stack);
StackInit(&min);
GoMazes(start);
printf("最短路径为:%d\n",StackSize(&min));
PrintStack(&min);
return 0;
}
我为了检查过程,用了很多打印函数,这里就看最后走完迷宫的过程:
END