数据结构与算法九:经典算法面试题--1.迷宫问题

427 阅读5分钟

这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战

关注我,以下内容持续更新

数据结构与算法(一):时间复杂度和空间复杂度

数据结构与算法(二):桟

数据结构与算法(三):队列

数据结构与算法(四):单链表

数据结构与算法(五):双向链表

数据结构与算法(六):哈希表

数据结构与算法(七):树

数据结构与算法(八):排序算法

数据结构与算法(九):经典算法面试题

迷宫问题

从入口出发,按某一方向向前探索,若能走通(未走过的),即某处可以到达,则到达新点,否则试探下一方向;若该点所有的方向均没有通路,则沿原路返回到前一点,换下一个方向再继续试探,直到所有可能的通路都探索到,或找到一条通路,或无路可走又退回到入口点。退回到的“前一点”正是刚刚才被访问过的,具有“后进先出”的特性,需要用栈保存所能够到达的每一一点的下标及从该点前进的方向。

非递归方式

解题思路

我们规定: 0代表可以走;1代表不通;-1代表已走过。我们用二维数组(邻接矩阵)来表示迷宫,开始设置迷宫的四周一圈为1,即不可走.

在代码中先设置入口为已走过(matex[1][1] = -1),然后把它入栈,遍历时先从入口寻找它的四个方向是否有通路。代码中有两重循环,内层循环中:遍历当前点的前后左右四个方向进行尝试,判断是否可以走(matex[x][y] == 0),如果有一个方向可以走就把当前点入栈(push)并设置当前点已访问(matex[1][1] = -1),如果四个方向都判断完都不能走,说明此路不通,要回溯到上一个走通的格子,也就是跳出内层循环回到外层循环,让当前格子出桟(pop),在判断过程中把当前点入栈的时候,判断当前点是否是迷宫的终点(x == matrix.count-2 && y == matrix.count-2),如果是直接返回true 程序结束,最后桟中保存的是迷宫通路;外层循环判断桟是否为空,不为空才进行内层循环,如果为空直接返回false,即没有通路(没有通路就是无路可走又退回到入口点)

代码示例

-(void)mazeCase{
    //用邻接矩阵表示迷宫: 1代表不通,0代表通
    NSMutableArray<NSMutableArray<NSString*>*>*matrix = [@[

                 [@[@"1",@"1",@"1",@"1",@"1",@"1"] mutableCopy],

                 [@[@"1",@"0",@"0",@"1",@"1",@"1"] mutableCopy],

                 [@[@"1",@"0",@"0",@"0",@"0",@"1"] mutableCopy],

                 [@[@"1",@"0",@"1",@"1",@"1",@"1"] mutableCopy],

                 [@[@"1",@"0",@"0",@"0",@"0",@"1"] mutableCopy],

                 [@[@"1",@"1",@"1",@"1",@"1",@"1"] mutableCopy]]

                mutableCopy];

    printf("打印开始的迷宫\n");
    [self showMatrix:matrix];
    BOOL success = [self maze:matrix];

    if (success) {
        printf("迷宫可以走通\n");
    }
}

-(void)showMatrix:(NSMutableArray<NSMutableArray<NSString*>*>*)matrix{
    for (int i = 0; i<matrix.count; i++) {
        for (int j = 0; j<matrix[i].count; j++) {
            printf("%2d  ",[matrix[i][j] intValue]);
        }
        printf("\n\n");
    }
    printf("本轮打印完毕\n");
}

-(BOOL)maze:(NSMutableArray<NSMutableArray<NSString*>*>*)matrix{

    //用来存储四个方向的 x和y坐标,用CGPoint表示;x代表横向走,y代表纵向走
    //规定方向的顺序:右->下->左->上
    NSArray*ds = @[NSStringFromCGPoint(CGPointMake(0, 1)),NSStringFromCGPoint(CGPointMake(1, 0)),NSStringFromCGPoint(CGPointMake(0, -1)),NSStringFromCGPoint(CGPointMake(-1,0))];

    MazeStack *stack = [MazeStack new];

    int x = 1;
    int y = 1;
    int d = 0;//0代表第一个方向

    matrix[x][y] = @"-1";//设置入口为已访问
    [stack push:@[@(x),@(y),@(d)]];//先把入口入栈,遍历栈顶元素

    NSArray*tmp = @[@(x),@(y),@(d)];//入口是当前格子

    while (![stack isEmpty]) {
        tmp = [stack pop];
        d = 0;//出桟,从第一个方向走//因为每次回溯出桟后都要从第一个方向开始判断,所以要置 0;
        while (d<4) {
            CGPoint dp = CGPointFromString(ds[d]);
            x = [tmp[0] intValue] + dp.x;
            y = [tmp[1] intValue] + dp.y;
            if ([matrix[x][y] intValue] == 0) {//如果通就入栈,继续往下走,继续走第一个方向即d=0;
                matrix[x][y] = @"-1";
                [stack push:@[@(x),@(y),@(d)]];
                tmp = @[@(x),@(y),@(d)];
                d = 0;//如果可以走通,下次继续从第一个方向走
                [self showMatrix:matrix];//打印每一步的迷宫变化
                if (x == matrix.count-2 && y == matrix[0].count-2) {
                    printf("迷宫走通了\n");
                    [stack showStack];//打印的桟有3个元素,前两个是坐标,第三个是方向
                    return true;
                }
            }else{//如果不通就判断下一个方向
                d++;
            }
        }
    }

    printf("迷宫不通\n");
    return NO;
}

代码解读

① 规定: 0代表可以走;1代表不通;-1代表已走过。同时规定方向的顺序:右->下->左->上

② ds按顺序存储四个方向的x和y坐标,用CGPoint表示;x代表横向走,y代表纵向走;

③ 桟中存放的事数组,数组中有3个元素,前两个是x,y坐标,第三个是方向

④ 开始从入口开始走,把入口入栈并设置入口已访问,第一轮循环入口就是当前格子,循环中不断判断4个方向中是否有方向可走,如果都不可走,说明此路不通,出桟,回溯到走上一个格子,这时要重新从第一个方向开始走,循环此操作.

⑤ x == matrix.count-2 && y == matrix[0].count-2 就是最后的终点坐标

最初的迷宫

最初的迷宫.png

最后打印的走成功的迷宫

截屏2021-08-17 17.39.26.png

递归方式

-(BOOL)maze:(NSMutableArray<NSMutableArray<NSString*>*>*)matrix{
    //用来存储四个方向的 x和y坐标,用CGPoint表示;x代表横向走,y代表纵向走
    //规定方向的顺序:右->下->左->上
    NSArray*ds = @[NSStringFromCGPoint(CGPointMake(0, 1)),NSStringFromCGPoint(CGPointMake(1, 0)),NSStringFromCGPoint(CGPointMake(0, -1)),NSStringFromCGPoint(CGPointMake(-1,0))];
    //传入(1,1)从起点开始
    return [self setWay:matrix x:1 y:1 ds:ds];
}

-(BOOL)setWay:(NSMutableArray<NSMutableArray<NSString*>*>*)matrix x:(int)x y:(int)y ds:(NSArray*)ds{
    if (x == matrix.count-2 && y == matrix[0].count-2) {//如果能走到终点,直接返回YES;(终点肯定是可以走的,不用判断是否等于0)
        matrix[x][y] = @"-1";//为了保证路径(matrix)的完整,加上这一句
        return YES;
    }

    if (x<0 || x>matrix.count || y<0 || y>matrix[0].count) {
        return NO;
    }

    //0代表可以走;1代表不通;-1代表已走过。
    if ([matrix[x][y] intValue] == 0) {
        matrix[x][y] = @"-1";
        for (int i = 0; i<ds.count; i++) {
            CGPoint dp = CGPointFromString(ds[i]);
            int tmpX = x + dp.x;
            int tmpY = y + dp.y;
            if ([self setWay:matrix x:tmpX y:tmpY ds:ds]) {
                return YES;
            }
        }
        return NO;
    }
    return NO;
}
-(void)mazeCase{

    //用邻接矩阵表示迷宫: 1代表不通,0代表通

    NSMutableArray<NSMutableArray<NSString*>*>*matrix = [@[                 [@[@"1",@"1",@"1",@"1",@"1",@"1"] mutableCopy],

                 [@[@"1",@"0",@"0",@"1",@"1",@"1"] mutableCopy],

                 [@[@"1",@"0",@"0",@"0",@"0",@"1"] mutableCopy],

                 [@[@"1",@"0",@"1",@"1",@"1",@"1"] mutableCopy],

                 [@[@"1",@"0",@"0",@"0",@"0",@"1"] mutableCopy],

                 [@[@"1",@"1",@"1",@"1",@"1",@"1"] mutableCopy]]

                mutableCopy];

    
    printf("打印开始的迷宫\n");
    [self showMatrix:matrix];

    //可以选择递归方式或者非递归方式
    BOOL success = [self maze:matrix];

    if (success) {
        printf("迷宫可以走通\n");
    }else{
        printf("迷宫不可以走通\n");
    }

    printf("打印最后的迷宫\n");
    [self showMatrix:matrix];
}

如果觉得我写的不错,请点个赞 关注我 您的支持是我更文最大的动力!