您以前听说过广度优先搜索 (BFS) 吗?
基本上,您只需将初始区域(在您的示例中为“a”)放入一个空队列 Q
您需要的第二个数据结构是一个布尔数组,其中包含与您的地区一样多的元素,在本例中为 9。它有助于记住我们已经检查过哪些地区。我们称它为 V(代表“已访问”)。它需要进行如下初始化:所有元素都等于假,除了与初始方块对应的元素。对于所有区域 t,我们有 V[t] = false,但 V[a] = true 因为“a”已经在队列中。
您需要的第三个也是最后一个数据结构是一个数组来存储父节点(即我们来自哪个节点)。它还具有与您的领土一样多的元素。我们称它为 P(表示“父”),每个元素最初都指向自己,即对于 P 中的所有 t,设置 P[t] = t。
那么,就很简单了:
while Q is not empty:
t = front element in the queue (remove it also from Q)
if t = f we can break from while loop //because we have found the goal
for all neighbors s of t for which V[s] = false:
add s into the back of Q //we have never explored the territory s yet as V[s] = false
set V[s] = true //we do not have to visit s again in the future
//we found s because there is a connection from t to s
//therefore, we need to remember that in s we are coming from the node t
//to do this we simply set the parent of s to t:
P[s] = t
您现在如何阅读解决方案?
只需检查 f 的父级,然后检查 that 的父级,然后检查 that 的父级,依此类推,直到找到开头。一旦找到一个以自身为父元素的元素(请记住,我们最初让它们指向自身),您就会知道开始是什么,或者您也可以将其与 a 进行比较。
基本上,你只需要一个空列表 L,将 f 添加到其中,然后
while f != a:
f = P[f]
add f into L
请注意,如果不存在路径,这显然会失败,因为 f 永远不会等于 a。
因此,这个:
while f != P[f]:
f = P[f]
add f into L
好一点。它利用了一个事实,即最初所有领土都指向 P 中的自己。
如果你用上面的例子在纸上尝试这个,那么你最终会得到
L = [f, e, b, a]
如果你只是把这个列表倒过来,那么你就得到了你想要的。
我不懂 C#,所以我没有费心使用 C# 语法。我假设你知道用整数索引你的地区然后使用数组来访问它们是最简单的。
您会很快意识到为什么会这样。它被称为广度优先搜索,因为您首先只考虑区域“a”的邻居,到它们的最短路径(只有一条边)并且只有在处理完所有这些之后,更远的区域才会出现在队列中(从现在开始只有2条边)等等。这就是为什么我们为这个任务使用队列而不是堆栈之类的东西。
此外,这在区域和边的数量上是线性的,因为您只需要(最多)查看每个区域和边一次(尽管来自两个方向的边)。
我给你的算法与https://en.wikipedia.org/wiki/Breadth-first_search基本相同,只是添加了P数据结构来跟踪你从哪里来,以便能够弄清楚所走的路径。