我是C ++编程的新手,我正在研究一种迷宫求解算法。我需要使用显式堆栈来跟踪完成的移动,没有递归。
基本上我负责“解算器”算法,我可以查看移动是否可用或阻止并执行此操作,或者我可以撤消移动。这些动作是左,右和前进。已有代码负责绘制和更新迷宫。
我可以使用的只是帮助理解穿越迷宫的基本算法。我已经看了很多不同的程序,但我似乎无法弄明白。我正在解决的迷宫是随机生成的,没有循环。
这是我无法理解的事情:说我有一段直的墙,但也有一个分支从墙上出来。说我决定离开这个分支,但最终导致死路一条。我把我所做的所有动作都推到了堆栈上。基本上,我怎么知道我需要从堆栈中弹出多少动作才能知道我回到了原来的交叉点,所以我可以选择其他分支而不是阻塞分支?
感谢您的帮助!
答案 0 :(得分:5)
这是我无法理解的事情:说我决定离开这个分支,但最终导致死路一条。我把我所做的所有动作都推到了堆栈上。基本上,我怎么知道我需要从堆栈中弹出多少动作才能知道我回到了原来的交叉点,所以我可以选择其他分支而不是阻塞分支?
您需要做的就是始终按预定顺序进行选择。
这些动作是左,右,前进。
如果你总是按照这个顺序做出这些选择,那么当你回溯时,你就会知道你已经做了什么。
每一步您回溯,再次检查这些动作。如果您要撤消正确的选项,您就会知道您已尝试左和正确,但尚未尝试< EM>向前
答案 1 :(得分:2)
首先,从起始位置添加所有可能的移动。然后,只需遵循此算法:
在每次迭代时,尝试从堆栈中弹出一个帧。如果堆栈是空的,那么您已经尝试了所有可能的移动。
现在,查看从堆栈中弹出的位置。这是你目前的职位。
将您弹出的位置中的所有移动添加到堆栈中,这些移动将导致未探测的位置。如果他们中的任何一个是目标位置,那么你就完成了。
其余的将照顾好自己。考虑一下,在纸上尝试一些案例,你会看到。 :)
答案 2 :(得分:1)
你没有很多解决方案:基本上,探索没有循环的迷宫就像在覆盖树上进行深度优先搜索,每个交叉点都是一个节点。
您可以随时构建树,并使用该信息来浏览它,但这将是一个单调乏味的效率不高的。
深度优先搜索的常用方法是将所有节点推送到堆栈上,拉出一个并再次推送,直到达到目标。但是你会堆积很多节点,一旦找到目标节点,你就无法使用堆栈来知道你所遵循的路径,这意味着你需要将这些信息存储在其他地方。
最好保持堆栈解决方案并在堆栈中标记节点,以指示分支的和哪个方向(即哪个子树)已被探索(或留下哪些路径)。如果您始终以相同的顺序进行探索,那么该标记可以只是一个数字:
或更好的枚举。
当找到死角时,只需展开堆栈直到找到其中一个节点,并尝试新的方向。如果已尝试所有方向,换句话说,如果没有方向,请再次展开。
enum Branch {
LEFT,
FORWARD,
RIGHT,
BACKTRACK
};
struct BacktrackException{
};
template <typename MazeMove>
struct StackNode {
MazeMove move;
Branch branch;
StackNode(MazeMove m): move(m), branch(LEFT) {}
MazeMove nextBranch(){
switch(branch){
case LEFT:
if (move.can_left()){
branch = FORWARD;
return move.left();
}
case FORWARD:
if (move.can_forward()){
branch = RIGHT;
return move.forward();
}
case RIGHT:
if (move.can_right()){
branch = BACKTRACK;
return move.right();
}
default:
throw BacktrackException();
}
}
};
上面的代码为堆栈使用的可能的“MazeMove”类提供了一个包装器,它跟踪尝试的方向。 nextBranch方法返回下一个可能的移动,或抛出异常。
优点是你的筹码不会被未经验证的动作破坏。每次到达新位置时都会推送StackNode
,并在其选项全部测试完毕后展开。当你到达迷宫出口时,你的堆栈只包含所需的移动。
答案 3 :(得分:0)
基本上,我怎么知道我需要从堆栈中弹出多少动作才能知道我回到了原来的路口,所以我可以选择其他分支而不是阻塞分支?
你没有 - 你总是回到一个步骤。然后检查所有(剩余的)替代方案,然后再往前走......等等。这称为backtracking。
顺便说一句,无论你是否使用递归,这都是相同的。使用递归只会使堆栈隐式,堆栈处理自动。