解决迷宫回溯

Posted

技术标签:

【中文标题】解决迷宫回溯【英文标题】:Solve a maze backtracking 【发布时间】:2017-10-24 05:25:41 【问题描述】:

我正在尝试使用 C 中的回溯解决迷宫。要解决迷宫,请遵循以下规则:

你从S的位置开始,需要向E走 你只能继续'.'路径 转换所有的 '.'进入“#”,包括 S 和 E

输入由一个 m x n 矩阵组成: 输入示例:

11 11
+-+-+-+-+-+
S.|...|...|
+.+.+.+-+.+
|.|.|.....|
+.+-+-+-+.+
|...|.|...|
+.+.+.+.+-+
|.|...|.|.|
+.+-+.+.+.+
|...|.....E
+-+-+-+-+-+

预期的解决方案:

+-+-+-+-+-+
##|...|...|
+#+.+.+-+.+
|#|.|.....|
+#+-+-+-+.+
|###|.|...|
+.+#+.+.+-+
|.|###|.|.|
+.+-+#+.+.+
|...|######
+-+-+-+-+-+

我非常努力地解决它,但由于某种原因,一旦我到达迷宫中的某个点,我无法继续前进,我的程序就不会返回。它只是朝着它看到的所有方向前进'。'

我的想法是从 S 的位置开始,并在每个递归步骤中使用我们原来的位置。 如果我正在看的位置是“。”,我将从我站立的位置向所有方向走。如果那一点不是我的旧职位。

我还认为,当我回溯到一个十字路口时,我遇到了问题。例如:

+-+-+-+-+-+
##|...|...|
+#+.+.+-+.+
|#|.|.....|
+#+-+-+-+.+
|0##|.|...|
+.+#+.+.+-+
|.|###|.|.|
+.+-+#+.+.+
|..1|######
+-+-+-+-+-+

想象一下,我在位置 0。我从 1 回溯,将 # 改回 '.'。我如何发表声明说你有 2 # 种可能性回去,但你应该停下来?

我的代码:

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

void *safeMalloc(int n) 
    void *p = malloc(n);
    if (p == NULL) 
        printf("Error: malloc(%d) failed. Out of memory?\n", n);
        exit(EXIT_FAILURE);
    
    return p;



char ** readMatrix(int m,int n,int* startI,int* startJ,int* endI,int* endJ)
    char **arr = safeMalloc(m*sizeof(char *));
    int row;
    for (row=0; row < m; row++) 
        arr[row] = safeMalloc(n*sizeof(char));
    
    int i,j;
    for(i=0;i<m;i++)
        for(j=0;j<m;j++)
            scanf(" %c",&arr[i][j]);
            if(arr[i][j]=='S')
                *startI=i;
                *startJ=j;
            
            if(arr[i][j]=='E')
                *endI=i;
                *endJ=j;
            
        
        getchar();
    

    return arr;


void printNumber(char **arr,int m,int n)
    int i,j;
    for(i=0;i<m;i++)
        for(j=0;j<n;j++)
            printf("%c", arr[i][j]);
        
        printf("\n");
    


void findPath(char** arr,int m,int n,int startI,int startJ,int endI,int endJ,int oldI,int oldJ)
    int i=startI,j=startJ;
    int stepsPossible=4;
            //going up
           if(i-1>=0)
                if((arr[i-1][j]=='.') && ((i-1!=oldI) || (j!=oldJ)))
                    arr[i][j]='#';
                    oldI=i;
                    oldJ=j;
                    findPath(arr,m,n,i-1,j,endI,endJ,oldI,oldJ);
                else
                    stepsPossible--;
                
           
           //going right

           if(j+1<n)
                if((arr[i][j+1]=='.') && ((i!= oldI) || (j+1!=oldJ)))
                    arr[i][j]='#';
                    oldI=i;
                    oldJ=j;
                    findPath(arr,m,n,i,j+1,endI,endJ,oldI,oldJ);
                else
                    stepsPossible--;
                
           
           //going left
           if(j-1>=0)
                if((arr[i][j-1]=='.') && ((i!= oldI) || (j-1!=oldJ)))
                    arr[i][j]='#';
                    oldI=i;
                    oldJ=j;
                    findPath(arr,m,n,i,j-1,endI,endJ,oldI,oldJ);
                else
                    stepsPossible--;
                
           

           //going down
            if(i+1<m)
                if((arr[i+1][j]=='.') && ((i+1!= oldI) || (j!=oldJ)))
                    arr[i][j]='#';
                    oldI=i;
                    oldJ=j;
                    findPath(arr,m,n,i+1,j,endI,endJ,oldI,oldJ);
                else
                    stepsPossible--;
                
           
        //if the next block is E then we can stop.
           if((arr[i-1][j]=='E') || (arr[i][j+1]=='E') || (arr[i][j-1]=='E') || (arr[i+1][j]=='E'))
                if(arr[i-1][j]=='E')
                    arr[i-1][j]='#';
                

                if(arr[i][j+1]=='E')
                    arr[i][j+1]='#';
                

                if(arr[i][j-1]=='E')
                    arr[i][j-1]='#';
                

                if(arr[i+1][j]=='E')
                    arr[i+1][j]='#';
                
                return;
            


            if(stepsPossible==0)
                if(arr[i-1][j]=='#')
                    arr[i][j]='.';
                    oldI=i;
                    oldJ=j;
                    findPath(arr,m,n,i-1,j,endI,endJ,oldI,oldJ);
                else
                    return;
                

                if(arr[i][j+1]=='#' )
                    arr[i][j]='.';
                    oldI=i;
                    oldJ=j;
                    findPath(arr,m,n,i,j+1,endI,endJ,oldI,oldJ);
                else
                    return;
                

                if(arr[i][j-1]=='#' )
                    arr[i][j]='.';
                    oldI=i;
                    oldJ=j;
                    findPath(arr,m,n,i,j-1,endI,endJ,oldI,oldJ);
                else
                    return;
                

                if(arr[i+1][j]=='#' )
                    arr[i][j]='.';
                    oldI=i;
                    oldJ=j;
                    findPath(arr,m,n,i+1,j,endI,endJ,oldI,oldJ);
                else
                    return;
                
            



int main()

    int m,n;
    scanf("%d %d",&m,&n);
    int startI,startJ,endI,endJ;
    char** arr;
    arr=readMatrix(m,n,&startI,&startJ,&endI,&endJ);
    findPath(arr,m,n,startI,startJ,endI,endJ,startI,startJ);
    printNumber(arr,m,n);
    return 0;

【问题讨论】:

你用的是什么算法?这听起来像 A* 会很好。 我真的没有搜索要使用的特定算法。我的想法是从 S 开始,如果我看到一个 '.',就往各个方向走。当我没有任何方向可走时,只需搜索“#”,将自己转换回“。”并将您的位置保存为 oldI,oldJ ,然后从您看到“#”的位置再次应用该功能。 无论如何,我不认为我应该使用任何已知的搜索算法,因为我们没有在本练习中看到它们中的任何一个。 除非说明说你不应该这样做,否则我看不出有任何理由不这样做。 如果你想自己制作一个算法,你还没有准备好实际的代码。在实施之前确保算法有效。 【参考方案1】:

正确使用返回值,因为您可以利用它来简化逻辑。在findPath 上为错误情况(需要回溯)和成功情况(到达终点)选择不同的返回值。

现在您可以将# 无条件地设置在该函数的开头,并在最后无条件地重置回. 以用于回溯情况。

也不需要计算可能的方向,只检查某个调用是否返回成功。

也不需要一遍又一遍地编写边界检查,如果您只是在函数开始时检查它们,您可以传递无效坐标而不会出现任何问题。

bool findPath(char** arr, size_t sx, size_t sy, int x, int y) 
    if (x < 0 || x >= sx || y < 0 || y >= sy) return false;
    if (arr[x][y] == 'E') 
        are[x][y] = '#';
        return true;
    
    if (arr[x][y] != '.') return false;
    arr[x][y] = '#';
    bool success = findPath(arr, sx, sy, x-1, y) ||
        findPath(arr, sx, sy, x+1, y) ||
        findPath(arr, sx, sy, x, y-1) ||
        findPath(arr, sx, sy, x, y+1);
    if (!success) arr[x][y] = '.';
    return success;

回溯算法的实现通常都遵循相同的模式:

    尽量拒绝或接受当前的解决方案。 修改当前解决方案。 尝试变体。 如果不成功,请清理修改。

【讨论】:

【参考方案2】:

我建议使用 BFS,因为

BFS 会找到最短的解决方案。 DFS 对某些迷宫的处理非常糟糕。

以下是针对您的案例的 BFS 的简短描述:

    在迷宫中找到“S”并将其添加到队列中 当队列不为空时,检查从队列中获取元素。 用“#”替换元素。如果元素是E,你就完成了。检查元素的邻居(上、下、左、右),如果它们是“.”,则添加到队列中。 如果队列为空且未找到 E,则没有从 S 到 E 的直接路径

【讨论】:

这种情况下最简单的算法,因为迷宫不包含循环,是 DFS,它也能产生理想的结果。 @Ext3h,即使在您描述的条件下,DFS 也远非理想。不正确的早期猜测会大大增加搜索时间。

以上是关于解决迷宫回溯的主要内容,如果未能解决你的问题,请参考以下文章

迷宫求解程序的回溯逻辑错误

关于回溯java的迷宫解析问题

算法——迷宫问题(回溯递归)

迷宫回溯和八皇后问题

从迷宫问题连连看红与黑说回溯算法遍历解空间

详解Java递归(Recursion)通过递归解决迷宫回溯及八皇后问题