【问题标题】:Determining if a directed graph is unilateral确定有向图是否是单边的
【发布时间】:2013-12-20 01:18:10
【问题描述】:

你将如何确定一个有向图是否是单边的(对于任何一对顶点u,v,至少一个可以从另一个到达)? 我认为您可以运行 DFS 或 BFS 并查看是否可以到达每个顶点。如果不是,则计算转置并从同一顶点执行相同的搜索算法。如果每个顶点都至少达到一个,那么图是单边的吗?

显然,您可以通过仅分析邻接矩阵来在较长的运行时间内完成此操作,但理想情况下我们希望在 O(V+E) 中运行

【问题讨论】:

    标签: algorithm graph directed-graph adjacency-list


    【解决方案1】:

    试试these algorithms。我在大学里学到的是 Floyd-Warshall,但它的表现并不如你所愿。 Johnsons's algorithm 对于稀疏图更快,但仍然是 O(V*E),而不是 O(V+E)。

    【讨论】:

      【解决方案2】:

      找到强连通分量,比如Tarjan's algorithm。 SCC 中的每个节点都可以从任何其他节点到达,因此它们在可以到达和被哪些节点到达方面是等价的。将每个 SCC 折叠成一个顶点,如果原始图是单边的,则生成的 DAG 将是单边的。

      如果 DAG 是一个全序,即只有一个拓扑序,则它是单边的。如果有从 A 到 B 的路径,则 A 必须在 B 之前。如果有从 B 到 A 的路径,则 B 必须在 A 之前。你不能同时拥有两者,因为图现在是无环的。如果 A 和 B 之间没有路径,则它们是无序的,并且该图至少有 2 个拓扑顺序 - 一个是 A 在 B 之前,一个是 B 在 A 之前。

      检查总顺序的一种快速方法是使用卡恩算法执行topological sort,并检查以确保在每次迭代中下一个顶点只有一个选择。

      Tarjan 的 SCC 查找算法、SCC 折叠算法和 Kahn 的拓扑排序算法都在 O(V+E) 时间内运行。

      【讨论】:

        【解决方案3】:

        我不完全确定这是否正确,但我认为检查有向图是否“连接”就足够了(请参阅下面的算法描述以了解我所说的连接的意思)。如果有向图中有多个连通分量,则它不是单边的。

        证明尝试:

        假设有向图有多个连通分量。为简单起见,让连接组件的数量为 2,我们将组件称为 C1C2。从C1 中选择任意顶点v,从C2 中选择任意顶点w。由于它们在不同的连接组件中,所以从vwwv 没有路径,否则C1C2 将在同一个组件中。

        对于另一种情况,假设有向图是连接的。那么对于有向图中的任意 2 个不同的顶点 xy,要么存在从 xy 或从 yx 的路径,否则它们将不在同一个组件中。我知道这里有点手摇,但我并不擅长打样。

        编辑:更简单的算法:

        我确实认为修改后的深度优先搜索 / 广度优先搜索应该可以做到。本质上,我们可以从 any 顶点开始搜索。我们将从第一个顶点到达的所有顶点标记为已访问。然后遍历所有顶点。对于任何未访问的顶点,我们进行 BFS / DFS,并使用列表来跟踪我们遍历过的所有未访问的顶点。如果在 BFS/DFS 期间,我们没有从先前的 BFS/DFS 访问任何先前访问过的顶点,那么我们可以立即得出结论,有向图具有多个连通分量并且不是单边的。否则,我们将搜索过程中获得的列表中的所有顶点标记为已访问,然后继续。

        一旦我们遍历了图中的所有顶点,所有 BFS / DFS 都命中了一些以前访问过的顶点,我们可以得出结论,该图是单边的。

        一些伪代码来说明这一点。我将使用深度优先搜索:

        // a boolean array to keep track if a given vertex is visited
        // initially this is set to false
        boolean[] visited
        // boolean array to keep track of visited vertices for 2nd dfs onwards
        boolean[] visited_two
        
        // used for first dfs
        dfs(vertex) {
          visited[vertex] <- true
          for each edge leading out of vertex {
            dfs(next vertex)
          }
        }
        
        // used for subsequent dfs
        dfs_two(vertex, list_for_storing_vertices) {
          // we use the visited_two array here, because the
          // visited array is used to store previously visited
          // vertices
          visited_two[vertex] <- true
          list_for_storing_vertices.append(vertex)
          // return value. we return true if we encounter
          // a previously visited vertex. otherwise, return false
          return_value <- false
          for each edge leading out of vertex {
            if visited[next vertex]
              return_value <- true
            else if !visited_two[next_vertex]
              r = dfs_two(next vertex, list_for_storing_vertices)
              return_value = return_value || r
          }
          return return_value
        }
        
        // overall algorithm
        // Returns true if the graph is unilateral. false otherwise
        bool digraph_unilateral(graph) {
          // Just pick any vertex. we will pick vertex 0
          set all entries in `visited` array to false
          dfs(0)
          // then loop through all vertices. if they haven't been visited,
          // we perform a dfs
          for each vertex in the digraph {
            if !visited[vertex] {
              // reset visited_two array
              set all entries in `visited_two` array to false
              visited_list <- []
              encountered_previously_visited <- dfs_two(vertex, visited_list)
              if encountered_previously_visited {
                // mark the vertices we encountered as visited
                for each v in visited_list {
                  visited[v] <- true
                }
              } else {
                // we did not encounter any previously visited vertex
                // so all vertices we encounter on this search are in
                // a distinct connected component
                return false
              }
            }
          }
          return true
        }
        

        原始的、更难的算法:

        我们如何计算一个有向图是否是单边的?您可以先运行Tarjan's Strongly Connected Components algorithm 并确定有向图的强连通分量。您将需要稍微修改算法以将强连接组件压缩为超顶点。这并不难做到,只需为同一个强连通分量中的每个顶点分配一个整数标签即可。换句话说,同一个强连通分量中的所有顶点都将被1个超顶点替换。 Tarjan 的 SCC(强连通分量)算法在 O(V+E) 时间运行。

        在上述步骤之后,有向图现在是无环(无环)。这使我们可以继续下一步。

        之后,对上面的结果图执行拓扑排序。这应该给我们一个有向无环图的拓扑排序。此步骤也使用深度优先搜索实现在O(V+E) 时间运行。

        现在我们有了拓扑排序,根据拓扑排序执行广度优先搜索深度优先搜索 >有向无环图在 Tarjan 的 SCC 步骤之后获得。如果连通分量的数量为 1,则原始图是单边的(如果连通分量的数量为 0,它也是单边的,但那是一个空图,因此是微不足道的)。否则,有多个分量,原图不是单边的。这一步也在O(V+E)时间运行。

        因此,整个算法运行在O(V+E)时间。

        结论

        我认为这应该是正确的,但您可能想与其他人验证这种方法。如果您在理解我的方法或实现算法方面有任何困难,请发表评论。

        希望有帮助!

        【讨论】:

        • 我没有看过 Tarjan 的,但我确实喜欢强连接组件的想法!为什么让强连接的组件摆脱循环呢?三个组件之间不可能有一个循环吗?另外,当你对强连通分量进行最终搜索时,你希望每个顶点都是可达的吗?
        • 抱歉,我遇到了一些事情。有向图的强连通分量(从现在起缩写为 SCCC 是一个子图,其中C 中的任何顶点v 可以到达C 中的任何顶点w。在一个 SCC 中至少可以找到 1 个有向循环。通过用超顶点替换 SCC,有向图中将不再有 SCC。否则应该找到一个更大的 SCC 而不是原来的。以您的示例为例,如果三个组件之间存在循环,则 Tarjan 的 SCC 算法会将它们视为单个 SCC,而不是 3 个单独的 SCC
        • 啊,我明白了。谢谢!你帮了大忙!
        • 没问题 =) 如果您有兴趣编写算法代码,我建议您获取一份 this 书籍。它具有用于 tarjan 的 scc、拓扑排序、bfs、dfs 的工作 C++ 代码。这里还有第一版的免费副本:comp.nus.edu.sg/~stevenha/myteaching/competitive_programming/…
        • 我不相信你的简单算法有效。取 3 个顶点图 [(v1,v0), (v2,v0)](即从顶点 v1 和 v2 到顶点 v0 的 2 条边)。该图不是单边的(没有从 v1 到 v2 或 v2 到 v1 的路径),但是您的算法会给出误报:第一个 DFS 将只访问 v0。 v1 中的第二个 DFS 将访问 v0,并且由于已经访问了 v0,因此假设一切正常。然后来自 v2 的第三个 DFS 将访问 v0 并假设一切正常。现在所有顶点都已被访问并且算法报告为真。还是我误解了算法?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-11
        • 1970-01-01
        • 2016-08-17
        • 1970-01-01
        • 2016-05-30
        相关资源
        最近更新 更多