¿Cómo verifico si un gráfico dirigido es acíclico? ¿Y cómo se llama el algoritmo? Agradecería una referencia.
¿Cómo verifico si un gráfico dirigido es acíclico? ¿Y cómo se llama el algoritmo? Agradecería una referencia.
Respuestas:
Intentaría ordenar el gráfico topológicamente , y si no puedes, entonces tiene ciclos.
Hacer una búsqueda simple en profundidad no es suficiente para encontrar un ciclo. Es posible visitar un nodo varias veces en un DFS sin que exista un ciclo. Dependiendo de dónde empiece, es posible que tampoco visite el gráfico completo.
Puede comprobar los ciclos en un componente conectado de un gráfico de la siguiente manera. Encuentre un nodo que solo tenga bordes salientes. Si no existe tal nodo, entonces hay un ciclo. Inicie un DFS en ese nodo. Al atravesar cada borde, verifique si el borde apunta hacia un nodo que ya está en su pila. Esto indica la existencia de un ciclo. Si no encuentra tal borde, no hay ciclos en ese componente conectado.
Como señala Rutger Prins, si su gráfico no está conectado, debe repetir la búsqueda en cada componente conectado.
Como referencia, el algoritmo de componentes fuertemente conectado de Tarjan está estrechamente relacionado. También le ayudará a encontrar los ciclos, no solo a informar si existen.
El lema 22.11 del libro Introduction to Algorithms
(segunda edición) establece que:
Un gráfico dirigido G es acíclico si y solo si una búsqueda en profundidad de G no produce bordes posteriores
Solución 1 : algoritmo de Kahn para comprobar el ciclo . Idea principal: mantener una cola donde el nodo con cero grados se agregará a la cola. Luego despegue los nodos uno por uno hasta que la cola esté vacía. Compruebe si existen bordes internos de algún nodo.
Solución 2 : algoritmo de Tarjan para comprobar el componente conectado fuerte.
Solución 3 : DFS . Utilice una matriz de enteros para etiquetar el estado actual del nodo: es decir, 0 - significa que este nodo no ha sido visitado antes. -1: significa que este nodo ha sido visitado y sus nodos secundarios están siendo visitados. 1: significa que este nodo ha sido visitado y está hecho. Entonces, si el estado de un nodo es -1 mientras realiza DFS, significa que debe haber existido un ciclo.
La solución proporcionada por ShuggyCoUk está incompleta porque es posible que no verifique todos los nodos.
def isDAG(nodes V):
while there is an unvisited node v in V:
bool cycleFound = dfs(v)
if cyclefound:
return false
return true
Esto tiene complejidad de tiempo O (n + m) u O (n ^ 2)
m = O(n^2)
ya que el gráfico completo tiene exactamente m=n^2
bordes. Entonces eso es O(n+m) = O(n + n^2) = O(n^2)
.
Sé que este es un tema antiguo, pero para los futuros buscadores, aquí hay una implementación de C # que creé (¡no hay afirmación de que sea más eficiente!). Está diseñado para utilizar un número entero simple para identificar cada nodo. Puede decorarlo como desee, siempre que su objeto de nodo tenga valores hash e iguales correctamente.
Para gráficos muy profundos, esto puede tener una sobrecarga alta, ya que crea un conjunto de hash en cada nodo en profundidad (se destruyen en el ancho).
Ingresa el nodo desde el que desea buscar y la ruta a ese nodo.
Al verificar ciclos debajo de cualquier nodo, simplemente pase ese nodo junto con un hashset vacío
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;
}
aquí hay un código rápido para encontrar si un gráfico tiene ciclos:
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)
La idea es así: un algoritmo dfs normal con una matriz para realizar un seguimiento de los nodos visitados, y una matriz adicional que sirve como marcador para los nodos que condujeron al nodo actual, de modo que siempre que ejecutemos una dfs para un nodo establecemos su elemento correspondiente en la matriz de marcadores como verdadero, de modo que siempre que se encuentre un nodo ya visitado, verifiquemos si su elemento correspondiente en la matriz de marcadores es verdadero, si es verdadero, entonces es uno de los nodos que se permite a sí mismo (por lo tanto, un ciclo), y el truco es que cada vez que un dfs de un nodo regresa, volvemos a establecer su marcador correspondiente en falso, de modo que si lo visitamos nuevamente desde otra ruta no nos engañemos.
Acabo de tener esta pregunta en una entrevista de Google.
Puede intentar ordenar topológicamente, que es O (V + E) donde V es el número de vértices y E es el número de aristas. Un gráfico dirigido es acíclico si y solo si esto se puede hacer.
Elimina de forma recursiva los nodos hoja hasta que no quede ninguno, y si queda más de un nodo, tienes un ciclo. A menos que me equivoque, esto es O (V ^ 2 + VE).
Sin embargo, un algoritmo eficiente al estilo DFS, el peor de los casos O (V + E), es:
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);
}
Aquí está mi implementación ruby del algoritmo de nodo peel off leaf .
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
Puede usar la inversión del ciclo de búsqueda de mi respuesta aquí https://stackoverflow.com/a/60196714/1763149
def is_acyclic(graph):
return not has_cycle(graph)