给定一个数字 k 和一个图是否有一个 DFS 运行 会给出大于 k 的森林

given a number k and a graph is there a DFS run that will give forest larger then k

有人问我一个我似乎无法解决的问题。

给定一个有向图G=(V,E)和一个自然数k,k>0。

找到一种算法,如果存在 DFS 运行 树的数量 return “是” 在 DFS 森林中是 >= K.

算法应该与图 G 的大小成线性关系。 O(|V| + |E|).

我会解释:

所以对于以下 运行 其中 s 是起始节点,我们将得到 2 棵树:

但是如果我们盯着节点 u 看,我们只会得到一棵树。 所以我需要 return 是的 k = 1 或 2。没有别的。

那我怎么知道树的数量呢?

感谢您的帮助!

问题编辑后:

使用Kosaraju's algorithm在O(V + E)时间内得到强连通分量。这将给出最大 K.

这里的max K与强连通分量的个数相同。

为什么?

现在让我们用反证法来证明。假设对于问题中显示的图形,我们有 4 个强连通分量。假设有可能从某个节点 v 开始得到一个额外的 dfs 树。这将意味着节点 v 在计算强连接组件的数量时未被覆盖,或者在 DFS 期间遗漏了节点。但是,如果我们使用经过充分验证的算法进行 DFS 或找到强连接组件,则这两种情况都是不可能的。因此,我们的假设是错误的。于是,反证法。


编辑前回答问题:

DFS(Vertex v):
    mark v as visited;
    for(Vertex neighbor: Neighbors of v){
        if(!isVisited(neighbor)){
            DFS(neighbor);
        })
    }

count_trees(Graph G): //V vertices and E edges in total
    for(Vertex v: V){
        if(!isVisited(v)){
            DFS(v);
            trees++;
        })
    }
    return trees;

以上步骤不言自明。维护一个顶点是否被访问是微不足道的。

上述方法只是对每个以前没有访问过的节点进行DFS。因此,时间复杂度与 DFS 相同,即 O(|V| + |E|).

想象给定的图实际上是一棵树。然后,如果您在树的根部启动 DFS,您将在一次 DFS 搜索中找到整个图。在另一个极端,如果你在一片叶子中启动一个 DFS,然后在下一片叶子中启动,并且在节点中启动每个新的 DFS,因为你会得到它们bottom-up,那么每个 DFS 只会找到一个节点然后退出(因为 children 已经被之前的 DFS 访问过了)。因此,您可以启动与树中节点数一样多的 DFS 搜索。

如果图形有一些额外的边,但仍然是非循环的,则同样如此。

当图形有循环时,它就变得不同了。在这种情况下,在循环的任何成员中开始的 DFS 将找到该循环中的所有其他成员。所以一个循环永远不会在不同的 DFS 搜索中分裂。这个循环加上与之相交的所有其他循环,是一个 so-called Strongly connected component.

因此,算法必须找到强连接的组件并将它们计为 1 个 DFS,而所有其他不参与任何循环的节点都可以计为一个单独的 DFS(因为您将从那些子树)。

这是一个算法,它使用 DFS(这令人困惑,因为它是一个计算可能的 DFS 的 DFS)来识别周期并相应地更新计数。我对该算法使用了递归,因此当达到所需的 k 时,必须有一些快速回溯:此外,在这种情况下不需要搜索。

所有边只被访问一次,主循环也只访问所有节点一次,所以达到了所需的时间复杂度。

def k_forests(adj, k):
    # pathindex[node] == 0: means node has not been visited
    # pathindex[node] == -1: means node has been visited and all neighbors processed
    # pathindex[node] > 0: means node is at this step in the current DFS path
    pathindex = [0] * len(adj) # none of the nodes has been visited

    def recur(node, depth):
        nonlocal k  # we will decrease this count

        if pathindex[node] > 0: # cycle back
            return pathindex[node]
        if pathindex[node] == -1: # already processed
            return depth
        pathindex[node] = depth # being visited
        cycle = depth + 1 # infinity
        for neighbor in adj[node]:
            cycle = min(cycle, recur(neighbor, depth + 1))
            if k == 0: # success
                return -1 # backtrack completely...
        if cycle >= depth: # no cylce detected or back out of cycle
            k -= 1
            if k == 0:
                return -1 # success
        pathindex[node] = -1 # completely visited and processed
        return cycle

    # main loop over the nodes
    for node in range(len(adj)):
        recur(node, 1)
        if k == 0:
            return "YES"
    return "NO"

这个函数应该用每个节点的邻接表来调用,其中节点由从0开始的序号标识。例如,问题中的图可以表示如下,其中s = 0, t=1、u=2、v=3、w=4、x=5、y=6 和 z=7:

adj = [
    [4, 7],
    [2, 3],
    [3, 1],
    [0, 4],
    [5],
    [7],
    [5],
    [6, 4]
]

print(k_forests(adj, 4)) #  YES
print(k_forests(adj, 5)) #  NO