You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化含小球、箭头与障碍物的复杂网格解谜求解器?

网格解谜程序回溯算法问题排查与改进策略

问题概述

开发一款网格解谜程序,需将代表小球的数字移动到'H'单元格并通过箭头连接路径。规则要求小球路径不可交叉,箭头不能落在'X'上但可跳过该障碍物。程序在简单地图上运行正常,但处理复杂地图时输出结果完全不符合预期。

核心规则

  • 小球初始移动步数等于对应数字,后续每次移动步数递减1,每次移动可更换方向
  • 当步数变为0或小球停在'H'(会标记为'F')时,小球无法再移动
  • 路径不可交叉,箭头不能落在'X'单元格上,但移动过程中可跳过'X'障碍物

测试案例

成功案例1

输入地图:

1H

预期输出:

>.

程序输出与预期一致。

成功案例2

输入地图:

2.X
..H
.H1

预期输出:

v..
v..
>.^

程序输出与预期一致。

失败案例

输入地图:

3..H.2
.2..H.
..H..H
.X.2.X
......
3..H..

预期输出:

>>>>..v
.>>>>.v
>>....
^..v..
^..v..
^.....

程序输出:

v....v
v>>v.v
v.....
>>vv..
...v..
>>^...

输出逻辑混乱,完全不符合要求。

代码实现

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>

void    print(int c, int r, const char map[][r])
{
    for (int y = 0; y < c; y++)
    {
        for (int x = 0; x < r; x++)
        {
            if (map[y][x] == '>' || map[y][x] == '<'
                || map[y][x] == '^' || map[y][x] == 'v')
                printf("%c", map[y][x]);
            else
                printf(".");
        }
        printf("\n");
    }
}

bool    constraints(const char c)
{
    return  c == '<' || c == '>' || c == 'F' ||
            c == 'v' || c == '^' || isdigit(c);
}

bool    is_inside(int y, int x, int c, int r)
{
    return  y >= 0 &&
            x >= 0 &&
            y < c &&
            x < r;
}

int    done(bool last_loop, int counter, int y, int x, int c, int r, char map[][r])
{
    if (!is_inside(y, x, c, r))
        return -1;
    if (map[y][x] == 'H') {
        map[y][x] = 'F';
        return 1;
    }
    if (counter == 0) {
        if (last_loop)
        {
            if (map[y][x] == 'H') {
                map[y][x] = 'F';
                return 1;
            }
            return -1;
        }
        return 1;
    }
    return 0;
}

bool    down(bool last_loop, int counter, int y, int x, int c, int r, char map[][r])
{
    int res;
    if ((res = done(last_loop, counter, y, x, c, r, map)) != 0)
        return res == 1;

    if (map[y][x] == 'F')
        return false;
    if (y + 1 < c && !constraints(map[y + 1][x]))
    {
        const char save = map[y][x];
        map[y][x] = 'v';
        if (down(last_loop, counter - 1, y + 1, x, c, r, map))
            return true;
        map[y][x] = save;
    }
    return false;
}

bool    up(bool last_loop, int counter, int y, int x, int c, int r, char map[][r])
{
    int res;
    if ((res = done(last_loop, counter, y, x, c, r, map)) != 0)
        return res == 1;

    if (map[y][x] == 'F')
        return false;
    if (y - 1 >= 0 && !constraints(map[y - 1][x]))
    {
        const char save = map[y][x];
        map[y][x] = '^';
        if (up(last_loop, counter - 1, y - 1, x, c, r, map))
            return true;
        map[y][x] = save;
    }
    return false;
}

bool    right(bool last_loop, int counter, int y, int x, int c, int r, char map[][r])
{
    int res;
    if ((res = done(last_loop, counter, y, x, c, r, map)) != 0)
        return res == 1;

    if (map[y][x] == 'F')
        return false;
    if (x + 1 < r && !constraints(map[y][x + 1]))
    {
        const char save = map[y][x];
        map[y][x] = '>';
        if (right(last_loop, counter - 1, y, x + 1, c, r, map))
            return true;
        map[y][x] = save;
    }
    return false;
}

bool    left(bool last_loop, int counter, int y, int x, int c, int r, char map[][r])
{
    int res;
    if ((res = done(last_loop, counter, y, x, c, r, map)) != 0)
        return res == 1;

    if (map[y][x] == 'F')
        return false;
    if (x - 1 >= 0 && !constraints(map[y][x - 1]))
    {
        const char save = map[y][x];
        map[y][x] = '<';
        if (left(last_loop, counter - 1, y, x - 1, c, r, map))
            return true;
        map[y][x] = save;
    }
    return false;
}

void move_in_grid(int counter, int y, int x, int c, int r, char map[][r])
{
    for (int i = counter; i > 0; i--)
    {
        if (y + i < c)
        {
            if (down(i == 1, i, y, x, c, r, map)) {
                y += i;
                continue;
            }
        }
        if (y - i >= 0)
        {
            if (up(i == 1, i, y, x, c, r, map)) {
                y -= i;
                continue;
            }
        }
        if (x + i < r)
        {
            if (right(i == 1, i, y, x, c, r, map)) {
                x += i;
                continue;
            }
        }
        if (x - i >= 0)
        {
            if (left(i == 1, i, y, x, c, r, map)) {
                x -= i;
                continue;
            }
        }
    }
}

void start_from_digit(int c, int r, char map[][r])
{
    for (int y = 0; y < c; y++)
    {
        for (int x = 0; x < r; x++)
        {
            if (isdigit(map[y][x]))
                move_in_grid(map[y][x] - '0', y, x, c, r, map);
        }
    }
}

int main (int argc, char **argv, char **envp) {
    int columns, rows;
    printf ("For 6x5 grid, enter 65 and so on\n");
    scanf ("%d%d", &rows, &columns);
    char map [columns] [rows];
    for (int i = 0; i < columns; i++) {
        scanf("%s", map[i]);
    }
    start_from_digit (columns, rows, map);
    print (columns, rows, map);
    return EXIT_SUCCESS;
}

问题排查点

  1. 回溯逻辑的方向选择缺陷
    move_in_grid函数按固定顺序尝试下/上/右/左方向,且一旦某个方向成功就直接进入下一步数循环,完全跳过其他方向的尝试。这种“贪心”式的选择会导致程序陷入局部错误路径,无法回溯尝试其他可能的方向组合,而复杂地图需要多个小球的路径相互配合,单一方向选择必然出错。

  2. 障碍物'X'处理缺失
    当前constraints函数未将'X'纳入检查范围,导致程序可能将箭头画在'X'单元格上,违反规则。同时,移动逻辑未区分“跳过X”和“落在X上”的场景,完全忽略了障碍物的存在。

  3. 步数处理逻辑错误
    move_in_grid的循环逻辑是从初始步数到1依次尝试不同步数的移动,而非按照规则要求的“初始步数→步数递减1→直到0或到达H”的连续移动流程。这完全误解了规则中的步数递减机制。

  4. 回溯状态恢复不完整
    当小球到达'H'并标记为'F'后,回溯过程中未将'F'恢复为'H',导致该单元格被永久占用,后续小球无法使用,破坏了地图的初始状态约束。

  5. 小球处理顺序无回溯机制
    start_from_digit按行优先顺序逐个处理小球,每个小球的路径一旦确定就无法被后续小球的路径修正,完全没有考虑小球之间的路径相互影响,而复杂地图需要全局的路径组合回溯。

改进与调试策略

改进策略

  1. 重构全局回溯机制
    放弃逐个处理小球的方式,改为将所有未完成的小球状态(位置、剩余步数)纳入回溯状态。通过递归尝试所有可能的移动方向和步数,记录当前地图状态,失败则回滚所有修改(包括箭头和'H'→'F'的标记)。

  2. 修正障碍物处理逻辑

    • 更新constraints函数,添加c == 'X',确保箭头不会落在'X'上;
    • 移动时检查每一步的目标单元格,允许路径中间经过'X'(即跳过),但箭头位置和终点不能是'X'。
  3. 修复步数递减逻辑
    重新实现move_in_grid:当前剩余步数为n时,先完成n步的移动(可换方向),到达新位置后剩余步数变为n-1,继续处理直到步数为0或到达'H',而非循环尝试不同步数。

  4. 完善回溯状态恢复
    在递归回溯时,不仅要恢复箭头字符,还要将被标记为'F'的'H'单元格恢复为原始状态,确保地图回到尝试该路径前的状态。

  5. 调整方向尝试逻辑
    遍历所有可能的方向进行尝试,而非一旦某个方向成功就跳过其他方向。即使某个方向暂时成功,也要尝试其他方向以找到全局最优的路径组合。

调试策略

  1. 添加关键步骤日志
    在方向选择、状态修改、回溯操作等关键节点添加日志,输出当前小球位置、剩余步数、地图状态,跟踪错误路径的产生过程。

  2. 分步模拟复杂地图
    手动模拟复杂地图的正确路径,逐步对比程序的每一步选择,定位第一个偏离正确路径的操作,分析原因。

  3. 编写单元测试
    针对单个小球的移动逻辑编写单元测试,验证步数递减、方向切换、障碍物处理、到达'H'等场景的正确性。

  4. 边界条件测试
    测试小球刚好到达'H'、步数为0、路径经过'X'等边界情况,确保逻辑符合规则要求。

内容的提问来源于stack exchange,提问作者Tristan Debas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 15:55:55