DFS(深度优先搜索) 总是需要重置 visited 的状态吗?
问题来自 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
状态的解法,会导致超时问题。但是我提交后发现,这个解法并未超时。这让我思考了很久,明明是和我相同的写法,为什么上面的代码不会超时呢?直到我看到代码作者把变量 x
和 y
定义在了函数外,我才明白了原因——搜索开始前的 x
, y
和搜索结束后的 x
, y
的值并不相同,在递归的过程中,x
,y
作为全局变量并没有暂存下来,所以最后重置的 vis[x][y]
和搜索前的不是同一个。这也就是我们看到这个代码歪打正着地 AC 的原因了。
参考资料