如何检查有向图是否为非循环图?以及如何调用算法?我将不胜感激。
如何检查有向图是否为非循环图?以及如何调用算法?我将不胜感激。
Answers:
做一个简单的深度优先搜索是不是足够好找到一个循环。可以在不存在周期的情况下在DFS中多次访问节点。根据开始的位置,您可能也不会访问整个图形。
您可以按以下方式检查图的连接组件中的周期。查找仅具有向外边缘的节点。如果没有这样的节点,则存在一个周期。在该节点上启动DFS。遍历每个边时,请检查边是否指向堆栈中已存在的节点。这表明存在循环。如果找不到这样的边缘,则该连接的组件中没有循环。
正如Rutger Prins指出的那样,如果图形未连接,则需要在每个连接的组件上重复搜索。
作为参考,Tarjan的强连接组件算法密切相关。它还将帮助您找到周期,而不仅仅是报告周期是否存在。
书中的引理22.11 Introduction to Algorithms
(第二版)指出:
当且仅当深度优先搜索G不产生后边缘时,有向图G是非循环的
ShuggyCoUk提供的解决方案是不完整的,因为它可能不会检查所有节点。
def isDAG(nodes V):
while there is an unvisited node v in V:
bool cycleFound = dfs(v)
if cyclefound:
return false
return true
时间复杂度为O(n + m)或O(n ^ 2)
m = O(n^2)
因为完整的图形具有精确的m=n^2
边缘。就是这样O(n+m) = O(n + n^2) = O(n^2)
。
我知道这是一个老话题,但是对于未来的搜索者来说,这是我创建的C#实现(没有声称它是最有效的!)。它旨在使用一个简单的整数来标识每个节点。您可以根据需要装饰,只要您的节点对象哈希值正确且相等即可。
对于非常深的图,这可能会产生很高的开销,因为它会在每个深度节点上创建一个哈希集(它们会在宽度上被破坏)。
输入要从中搜索的节点,然后输入到该节点的路径。
在检查任何给定节点以下的周期时,只需将该节点与一个空哈希集一起传递
private bool FindCycle(int node, HashSet<int> path)
{
if (path.Contains(node))
return true;
var extendedPath = new HashSet<int>(path) {node};
foreach (var child in GetChildren(node))
{
if (FindCycle(child, extendedPath))
return true;
}
return false;
}
这是一个快速代码,用于查找图形是否具有循环:
func isCyclic(G : Dictionary<Int,Array<Int>>,root : Int , var visited : Array<Bool>,var breadCrumb : Array<Bool>)-> Bool
{
if(breadCrumb[root] == true)
{
return true;
}
if(visited[root] == true)
{
return false;
}
visited[root] = true;
breadCrumb[root] = true;
if(G[root] != nil)
{
for child : Int in G[root]!
{
if(isCyclic(G,root : child,visited : visited,breadCrumb : breadCrumb))
{
return true;
}
}
}
breadCrumb[root] = false;
return false;
}
let G = [0:[1,2,3],1:[4,5,6],2:[3,7,6],3:[5,7,8],5:[2]];
var visited = [false,false,false,false,false,false,false,false,false];
var breadCrumb = [false,false,false,false,false,false,false,false,false];
var isthereCycles = isCyclic(G,root : 0, visited : visited, breadCrumb : breadCrumb)
这个想法是这样的:一个普通的dfs算法,具有一个数组来跟踪访问的节点,还有一个额外的数组,该数组用作通向当前节点的节点的标记,因此,无论何时我们为一个节点执行dfs我们将其在标记数组中的对应项设置为true,以便在遇到一个已经访问过的节点时,检查其在标记数组中的对应项是否为true,如果为true,则检查其自身的节点之一(因此循环),诀窍是只要节点的dfs返回,我们便将其对应的标记设置回false,这样,如果我们从另一条路线再次访问它,就不会上当。
这是我的剥离叶节点算法的红宝石实现。
def detect_cycles(initial_graph, number_of_iterations=-1)
# If we keep peeling off leaf nodes, one of two things will happen
# A) We will eventually peel off all nodes: The graph is acyclic.
# B) We will get to a point where there is no leaf, yet the graph is not empty: The graph is cyclic.
graph = initial_graph
iteration = 0
loop do
iteration += 1
if number_of_iterations > 0 && iteration > number_of_iterations
raise "prevented infinite loop"
end
if graph.nodes.empty?
#puts "the graph is without cycles"
return false
end
leaf_nodes = graph.nodes.select { |node| node.leaving_edges.empty? }
if leaf_nodes.empty?
#puts "the graph contain cycles"
return true
end
nodes2 = graph.nodes.reject { |node| leaf_nodes.member?(node) }
edges2 = graph.edges.reject { |edge| leaf_nodes.member?(edge.destination) }
graph = Graph.new(nodes2, edges2)
end
raise "should not happen"
end
刚刚在Google访谈中遇到了这个问题。
您可以尝试按拓扑排序,即O(V + E),其中V是顶点数,E是边数。有向图在且仅当可以做到时才是非循环的。
递归地删除叶节点,直到没有剩下的节点为止;如果还剩下一个以上的节点,那么您就有一个循环。除非我弄错了,否则这是O(V ^ 2 + VE)。
但是,最有效的DFS式算法(最坏情况为O(V + E))是:
function isAcyclic (root) {
const previous = new Set();
function DFS (node) {
previous.add(node);
let isAcyclic = true;
for (let child of children) {
if (previous.has(node) || DFS(child)) {
isAcyclic = false;
break;
}
}
previous.delete(node);
return isAcyclic;
}
return DFS(root);
}
您可以在这里从我的答案中使用查找周期的反转https://stackoverflow.com/a/60196714/1763149
def is_acyclic(graph):
return not has_cycle(graph)