DFS(深度优先搜索) 总是需要重置 visited 的状态吗?

2023-07-12,,

问题来自 P1902 刺杀大使,在最初的实现中 DFS 中一段代码如下:

visited[x2][y2] = true;
flag = dfs(v, x2, y2);
visited[x2][y2] = false;
if (flag) { break; }

从格点([x2][y2]) 出发进行搜索,搜索开始前,会设置 visited[x2][y2] = true;搜索结束后,会重新设置 visited[x2][y2] = false。在没有解的情况下,算法会搜索二维数组中所有可能的路径(四个方向),这个路径被称为自回避行走(SAW, Self-Avoiding Walks)。目前还没对任意尺寸二维数组的 SAW 路径的通项公式,但是可以知道 SAW 路径个数和二维数组的尺寸是非线性关系。所以在没有解的情况下,使用上面的算法会导致 TLE(搜索的路径数远远多于数组的元素个数)。

为了解决超时问题,我们可以在搜索结束后,不再重置 visited[x2][y2],也就是说在整个搜索过程中,对于任意的一个格点(lattice),我们至多访问一次。此时,算法的最坏复杂度为 O(n*m)

证明

关于该算法正确性的一个简短证明:

命题:对于搜索失败的格点(即,从该格点出发),无需再次访问,即使再次访问搜索结果也不会发生变化。

我们使用反证法来证明。对于搜索失败的格点 A,可能会存在一条成功的路径。那么之前对格点 A 的搜索会失败,是因为这条成功路径上的某个格点 B 已经被访问过,导致这条路径不通。

如果这条成功路径上的这个格点 B 之前已经被访问过,但是算法没有中止,就代表从格点 B 出发的路径也存在某个格点 C 已经被访问过。重复这个步骤,我们可以得到,终点 D 也应该之前被访问过,这当然是矛盾的。

从而我们可知,若从某一格点出发搜索失败,则表示并不存在从该格点出发的成功路径,不然程序早就在访问这个格点之前就已经中止了。

奇怪的代码

在查看题目题解的时候,我看到这样的一段代码,

vis[x][y] = 1;
dfs(x, y);
vis[x][y] = 0;

照例说这个重置 vis 状态的解法,会导致超时问题。但是我提交后发现,这个解法并未超时。这让我思考了很久,明明是和我相同的写法,为什么上面的代码不会超时呢?直到我看到代码作者把变量 xy 定义在了函数外,我才明白了原因——搜索开始前的 x, y 和搜索结束后的 x, y 的值并不相同,在递归的过程中,xy 作为全局变量并没有暂存下来,所以最后重置的 vis[x][y] 和搜索前的不是同一个。这也就是我们看到这个代码歪打正着地 AC 的原因了。

参考资料

自回避行走(Self-Avoiding Walks)问题(简介)
Number of nonintersecting (or self-avoiding) rook paths joining opposite corners of an n X n grid.
(组合学)从九宫格的左下角到右上角有多少不同的路径?
Self-Avoiding Walk
How Many Paths from A to B?
Number of paths from A to B with no direction constraints

DFS(深度优先搜索) 总是需要重置 visited 的状态吗?的相关教程结束。

《DFS(深度优先搜索) 总是需要重置 visited 的状态吗?.doc》

下载本文的Word格式文档,以方便收藏与打印。