Qual é o algoritmo mais eficiente para detectar todos os ciclos em um gráfico direcionado?
Eu tenho um gráfico direcionado representando uma agenda de trabalhos que precisam ser executados, um trabalho sendo um nó e uma dependência sendo uma aresta. Preciso detectar o caso de erro de um ciclo neste gráfico, levando a dependências cíclicas.
algorithm
graph-theory
directed-graph
Peauters
fonte
fonte
Respostas:
O algoritmo de componentes fortemente conectados do Tarjan possui
O(|E| + |V|)
complexidade de tempo.Para outros algoritmos, consulte Componentes fortemente conectados na Wikipedia.
fonte
O(|E| + |V|)
. Usando branco (nunca visitado), cinza (o nó atual é visitado, mas todos os nós alcançáveis ainda não foram visitados) e preto (todos os nós alcançáveis são visitados junto com o atual) codificação de cores, se um nó cinza encontrar outro nó cinza, então ' tenho um ciclo. [Praticamente o que temos no livro de algoritmos de Cormen]. Pensando se o 'algoritmo de Tarjan' tem algum benefício sobre esse DFS !!Como esse é um cronograma de trabalhos, suspeito que, em algum momento, você os classifique em uma ordem de execução proposta.
Se for esse o caso, uma implementação de classificação topológica pode, em qualquer caso, detectar ciclos. O UNIX
tsort
certamente faz. Eu acho que é provável que seja, portanto, mais eficiente detectar ciclos ao mesmo tempo que o tsorting, e não em uma etapa separada.Portanto, a pergunta pode se tornar "como faço para tsort com mais eficiência", em vez de "como faço para detectar loops com mais eficiência". Para a qual a resposta provavelmente é "use a library", mas falhando no seguinte artigo da Wikipedia:
possui o pseudo-código para um algoritmo e uma breve descrição de outro do Tarjan. Ambos têm
O(|V| + |E|)
complexidade de tempo.fonte
A maneira mais simples de fazer isso é fazer uma primeira travessia em profundidade (DFT) do gráfico .
Se o gráfico tiver
n
vértices, este é umO(n)
algoritmo de complexidade de tempo. Como você possivelmente precisará executar uma DFT a partir de cada vértice, a complexidade total se tornaráO(n^2)
.É necessário manter uma pilha contendo todos os vértices no primeiro percurso de profundidade atual , com o primeiro elemento sendo o nó raiz. Se você se deparar com um elemento que já está na pilha durante a DFT, então você tem um ciclo.
fonte
O(n)
sugerida para verificar a pilha para ver se ela já contém um nó visitado? A varredura da pilha adiciona tempo ao tempo deO(n)
execução porque ela precisa varrer a pilha em cada novo nó. Você pode conseguirO(n)
se marcar os nós visitadosDe acordo com o Lema 22.11 de Cormen et al., Introdução aos algoritmos (CLRS):
Isso foi mencionado em várias respostas; aqui também fornecerei um exemplo de código baseado no capítulo 22 do CLRS. O gráfico de exemplo é ilustrado abaixo.
O pseudocódigo do CLRS para a pesquisa de profundidade é o seguinte:
No exemplo da Figura 22.4 do CLRS, o gráfico consiste em duas árvores DFS: uma que consiste nos nós u , v , x e y e a outra nos nós w e z . Cada árvore contém uma extremidade traseira: uma de x para ve outra de z para z (um auto-loop).
A principal conclusão é que uma borda traseira é encontrada quando, na
DFS-VISIT
função, enquanto itera sobre os vizinhosv
deu
, um nó é encontrado com aGRAY
cor.O código Python a seguir é uma adaptação do pseudocódigo do CLRS com uma
if
cláusula adicionada que detecta ciclos:Observe que, neste exemplo, o
time
pseudocódigo do CLRS não é capturado porque estamos interessados apenas em detectar ciclos. Há também algum código padrão para criar a representação da lista de adjacências de um gráfico a partir de uma lista de arestas.Quando esse script é executado, ele imprime a seguinte saída:
Essas são exatamente as arestas traseiras no exemplo da Figura 22.4 do CLRS.
fonte
Comece com um DFS: existe um ciclo se e somente se uma extremidade traseira for descoberta durante o DFS . Isso é provado como resultado do teorema do caminho branco.
fonte
Na minha opinião, o algoritmo mais compreensível para detectar ciclo em um gráfico direcionado é o algoritmo de coloração de gráfico.
Basicamente, o algoritmo de coloração de gráfico percorre o gráfico de maneira DFS (Depth First Search, que significa que ele explora um caminho completamente antes de explorar outro caminho). Quando encontra uma aresta traseira, marca o gráfico como contendo um loop.
Para uma explicação detalhada do algoritmo de coloração de gráficos, leia este artigo: http://www.geeksforgeeks.org/detect-cycle-direct-graph-using-colors/
Além disso, forneço uma implementação de coloração de gráfico no JavaScript https://github.com/dexcodeinc/graph_algorithm.js/blob/master/graph_algorithm.js
fonte
Se você não puder adicionar uma propriedade "visitada" aos nós, use um conjunto (ou mapa) e adicione todos os nós visitados ao conjunto, a menos que eles já estejam no conjunto. Use uma chave exclusiva ou o endereço dos objetos como a "chave".
Isso também fornece informações sobre o nó "raiz" da dependência cíclica, que será útil quando um usuário precisar corrigir o problema.
Outra solução é tentar encontrar a próxima dependência a ser executada. Para isso, você deve ter uma pilha onde possa lembrar onde está agora e o que precisa fazer a seguir. Verifique se já existe uma dependência nessa pilha antes de executá-la. Se for, você encontrou um ciclo.
Embora isso possa parecer ter uma complexidade de O (N * M), lembre-se de que a pilha tem uma profundidade muito limitada (portanto, N é pequena) e que M se torna menor a cada dependência que você pode marcar como "executado" mais você pode interromper a pesquisa quando encontrou uma folha (para nunca precisar verificar todos os nós -> M também será pequeno).
No MetaMake, criei o gráfico como uma lista de listas e, em seguida, excluí todos os nós enquanto os executava, o que naturalmente reduzia o volume de pesquisa. Na verdade, nunca tive que executar uma verificação independente, tudo aconteceu automaticamente durante a execução normal.
Se você precisar de um modo "somente teste", basta adicionar um sinalizador "execução a seco" que desativa a execução dos trabalhos reais.
fonte
Não existe um algoritmo capaz de encontrar todos os ciclos em um gráfico direcionado em tempo polinomial. Suponha que o gráfico direcionado tenha nós e todos os pares tenham conexões entre si, o que significa que você possui um gráfico completo. Portanto, qualquer subconjunto não vazio desses nós indica um ciclo e há 2 ^ n-1 número desses subconjuntos. Portanto, não existe algoritmo de tempo polinomial. Portanto, suponha que você tenha um algoritmo eficiente (não estúpido) que pode lhe dizer o número de ciclos direcionados em um gráfico, você pode primeiro encontrar os componentes conectados fortes e, em seguida, aplicar seu algoritmo nesses componentes conectados. Como os ciclos existem apenas dentro dos componentes e não entre eles.
fonte
Eu havia implementado esse problema no sml (programação imperativa). Aqui está o esboço. Encontre todos os nós que possuem um grau indegree ou outdegree de 0. Esses nós não podem fazer parte de um ciclo (remova-os). Em seguida, remova todas as arestas de entrada ou saída desses nós. Aplique recursivamente esse processo ao gráfico resultante. Se no final você não tiver nenhum nó ou aresta, o gráfico não terá ciclos, caso contrário, ele terá.
fonte
O jeito que eu faço é fazer uma Classificação Topológica, contando o número de vértices visitados. Se esse número for menor que o número total de vértices no DAG, você terá um ciclo.
fonte
/mathpro/16393/finding-a-cycle-of-fixed-length Eu gosto desta solução, a melhor, especialmente para 4 comprimentos :)
Também phys wizard diz que você tem que fazer O (V ^ 2). Acredito que precisamos apenas de O (V) / O (V + E). Se o gráfico estiver conectado, o DFS visitará todos os nós. Se o gráfico tiver conectado subgráficos, toda vez que executarmos um DFS em um vértice desse subgrafo, encontraremos os vértices conectados e não precisaremos considerá-los para a próxima execução do DFS. Portanto, a possibilidade de execução para cada vértice está incorreta.
fonte
Se o DFS encontrar uma aresta que aponte para um vértice já visitado, você terá um ciclo lá.
fonte
Como você disse, você tem um conjunto de trabalhos, ele precisa ser executado em certa ordem.
Topological sort
dado o pedido necessário para agendamento de trabalhos (ou para problemas de dependência, se for umdirect acyclic graph
). Executedfs
e mantenha uma lista e comece a adicionar um nó no início da lista e se você encontrou um nó que já foi visitado. Então você encontrou um ciclo em determinado gráfico.fonte
Se um gráfico satisfazer esta propriedade
então o gráfico contém pelo menos no ciclo.
fonte