回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。(深度优先遍历,能走则走,不能走则退)
迷宫问题__牛客网 (nowcoder.com)https://www.nowcoder.com/questionTerminal/cf24906056f4488c9ddb132f317e03bc
定义一个二维数组 N*M ,如 5 × 5 数组下所示:
int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};要求
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的路线。入口点为[0,0], 既第一格是可以走的路。
输出
(0,0) (1,0) (2,0) (2,1) (2,2) (2,3) (2,4) (3,4) (4,4)
首先0为通路,1为墙。深度优先遍历,在迷宫里有上下左右四个方向。按照一定的顺序走(例:先走上,再走下,再走左,最后走右)。因为0为通路,先走下的话,把下又当作起点,那么它的上边还是0,继续走的话就又回去了,所以把它走过的路做个标记,就可以不用下去又折回去了。
按照一定的顺序去走,难免会走到死胡同,即后边有标记,剩下三个方向都是墙,那么这条路肯定是走不通的,所以就原路返回,采用递归的方法,即回溯。返回到有其他通路的坐标,就继续往下走,如果再走到死胡同,就又返回,找新通路。这样最终一定会找出一条到终点通路。
这里要把坐标输出, 这里考虑用栈的数据结构存储,先走的先进,后走的后进。当遇到死胡同时,就往回退,这个时候就把退的坐标出栈。那么以此下去,栈里存的肯定是最终通路的坐标。
这里要求把坐标从入口,打印到出口,但是栈里后进的是出口坐标,所以就需要再借助一个栈来倒一下数据,再输出数据。
#include
#include
#include
typedef struct pointer
{
int row;
int col;
}pointer;
typedef pointer StaDateType;
typedef struct stack
{
StaDateType* arr;
int top;
int capicity;
}Stack;
void StactInit(Stack* ps);
void StackPush(Stack* ps, StaDateType date);
void StackPop(Stack* ps);
int StackSize(Stack* ps);
bool StackEmpty(Stack* ps);
void StackDestroy(Stack* ps);
StaDateType StackTop(Stack* ps);
void StactInit(Stack* ps)
{
assert(ps);
ps->arr = (StaDateType*)malloc(4 * sizeof(StaDateType));
if (ps->arr != NULL)
{
ps->capicity = 4;
ps->top = 0;
}
}
void StackPush(Stack* ps, StaDateType date)
{
assert(ps);
if (ps->top == ps->capicity)
{
StaDateType* pa = (StaDateType*)realloc(ps->arr, ps->capicity * 2 * sizeof(StaDateType));
if (pa != NULL)
{
ps->arr = pa;
ps->capicity *= 2;
}
}
ps->arr[ps->top] = date;
ps->top++;
}
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
StaDateType StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->arr[ps->top - 1];
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->arr);
ps->top = 0;
ps->arr = NULL;
}
bool IsPass(int** maze, int N, int M, pointer pos)
{
if (pos.row >= 0 && pos.row < N &&
pos.col >= 0 && pos.col < M &&
maze[pos.row][pos.col] == 0)
{
return true;
}
else
{
return false;
}
}
Stack path;//全局变量的path
void PrintPath()
{
//倒数据
Stack rpath;
StactInit(&rpath);
while (!StackEmpty(&path))
{
StackPush(&rpath, StackTop(&path));
StackPop(&path);
}
//打印数据
while (!StackEmpty(&rpath))
{
pointer top = StackTop(&rpath);
printf("(%d,%d)\n", top.row, top.col);
StackPop(&rpath);
}
StackDestroy(&rpath);
}
bool GetMazePath(int** maze, int N, int M, pointer cur)
{
StackPush(&path, cur);
//找到出口
if (cur.row == N - 1 && cur.col == M - 1)
{
return true;
}
pointer next;
maze[cur.row][cur.col] = 2;//标记走过的路
//上
next = cur;
next.row -= 1;
if (IsPass(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
{
return true;//一但找到终点通路就不在找别的路径
}
}
//下
next = cur;
next.row += 1;
if (IsPass(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
{
return true;
}
}
//左
next = cur;
next.col -= 1;
if (IsPass(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
{
return true;
}
}
//右
next = cur;
next.col += 1;
if (IsPass(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
{
return true;
}
}
//死胡同
StackPop(&path);
return false;
}
int main()
{
int N = 0;
int M = 0;
//开辟二维数组
while (scanf("%d%d", &N, &M) != EOF)
{
int** maze = (int**)malloc(sizeof(int*) * N);
for (int i = 0; i < N; i++)
{
maze[i] = (int*)malloc(sizeof(int) * M);
}
//输入二维数组数据
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
scanf("%d", &maze[i][j]);
}
}
StactInit(&path);
//迷宫入口
pointer entry = { 0,0 };
if (GetMazePath(maze, N, M, entry))
{
PrintPath();//打印数据
}
else
{
printf("没有到终点的通路");
}
StackDestroy(&path);
//释放二维数组
for (int i = 0; i < N; i++)
{
free(maze[i]);
}
free(maze);
maze = NULL;
}
return 0;
}
回溯算法的核心思想就就是递归时往回返的一个过程,那么对递归的理解就相当重要,可以尝试画一下递归的展开图,对于理解递归的过程会有很大帮助。