繁体   English   中英

给定一个数字 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 运行 DFS 森林中的树数 >= K,则找到一个返回“YES”的算法。

该算法在图 G 的大小上应该是线性的。O(|V| + |E|)。

我会解释:

因此对于以下运行,其中 s 是起始节点,我们将获得 2 棵树: 在此处输入图像描述

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

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

谢谢您的帮助!

在对问题进行编辑后:

使用Kosaraju 算法在 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,就像你让它们自下而上一样,那么每个 DFS 将只找到一个节点,然后退出(因为之前的 DFS 已经访问过孩子)。 因此,您可以启动与树中的节点一样多的 DFS 搜索。

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

当图形有循环时,它会变得不同。 在这种情况下,从循环的任何成员开始的 DFS 将找到该循环中的所有其他成员。 因此,一个循环永远不会被不同的 DFS 搜索分割。 这个循环,加上与之相交的所有其他循环,就是所谓的强连通分量

因此,算法必须找到强连接的组件并将其计为 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"

调用这个function应该每个节点都有一个邻接表,其中节点是用一个序号标识的,从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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM