Como posso encontrar (iterar) TODOS os ciclos em um gráfico direcionado de / para um determinado nó?
Por exemplo, eu quero algo como isto:
A->B->A
A->B->C->A
mas não: B-> C-> B
algorithm
graph-theory
graph-algorithm
user7305
fonte
fonte
Respostas:
Encontrei esta página na minha pesquisa e, como os ciclos não são os mesmos que os componentes fortemente conectados, continuei pesquisando e, finalmente, encontrei um algoritmo eficiente que lista todos os ciclos (elementares) de um gráfico direcionado. É de Donald B. Johnson e o artigo pode ser encontrado no seguinte link:
http://www.cs.tufts.edu/comp/150GA/homeworks/hw1/Johnson%2075.PDF
Uma implementação java pode ser encontrada em:
http://normalisiert.de/code/java/elementaryCycles.zip
Uma demonstração do algoritmo de Johnson no Mathematica pode ser encontrada aqui , a implementação pode ser baixada da direita ( "Baixar código do autor" ).
Nota: Na verdade, existem muitos algoritmos para esse problema. Alguns deles estão listados neste artigo:
http://dx.doi.org/10.1137/0205007
Segundo o artigo, o algoritmo de Johnson é o mais rápido.
fonte
A->B->C->A
elementar também?simple_cycle
no networkx.A primeira pesquisa profunda com retorno deve funcionar aqui. Mantenha uma matriz de valores booleanos para acompanhar se você visitou um nó antes. Se você ficar sem novos nós para acessar (sem bater em um nó que você já esteve), basta voltar atrás e tentar uma ramificação diferente.
O DFS é fácil de implementar se você tiver uma lista de adjacências para representar o gráfico. Por exemplo, adj [A] = {B, C} indica que B e C são filhos de A.
Por exemplo, pseudo-código abaixo. "start" é o nó do qual você inicia.
Chame a função acima com o nó inicial:
fonte
if (node == start):
- o que hánode and start
na primeira chamadastart
). Começa nesse vértice e faz um DFS até voltar ao vértice novamente, e sabe que encontrou um ciclo. Mas na verdade não produz os ciclos, apenas uma contagem deles (mas modificá-lo para fazer isso não deve ser muito difícil).start
. Você realmente não precisa limpar as bandeiras visitadas, pois cada bandeira visitada será limpa devido avisited[node]=NO;
. Mas lembre-se de que, se você tiver um cicloA->B->C->A
, poderá detectá-lo três vezes, assim comostart
qualquer um deles. Uma idéia para evitar isso é ter outra matriz visitada em que todos os nós que foram ostart
nó em algum momento estão definidos e você não os revisita novamente.Primeiro de tudo - você realmente não quer tentar encontrar literalmente todos os ciclos, porque se houver 1, haverá um número infinito deles. Por exemplo, ABA, ABABA etc. Ou pode ser possível unir 2 ciclos em um ciclo semelhante a 8 etc., etc ... A abordagem significativa é procurar todos os chamados ciclos simples - aqueles que não se cruzam, exceto no ponto inicial / final. Então, se desejar, você pode gerar combinações de ciclos simples.
Um dos algoritmos de linha de base para encontrar todos os ciclos simples em um gráfico direcionado é o seguinte: Faça uma travessia profunda de todos os caminhos simples (aqueles que não se cruzam) no gráfico. Sempre que o nó atual tem um sucessor na pilha, um ciclo simples é descoberto. Consiste nos elementos da pilha que começam com o sucessor identificado e terminam com o topo da pilha. O primeiro percurso de profundidade de todos os caminhos simples é semelhante à primeira pesquisa de profundidade, mas você não marca / grava os nós visitados que não sejam os que estão atualmente na pilha como pontos de parada.
O algoritmo de força bruta acima é terrivelmente ineficiente e, além disso, gera várias cópias dos ciclos. No entanto, é o ponto de partida de vários algoritmos práticos que aplicam várias melhorias para melhorar o desempenho e evitar a duplicação de ciclos. Fiquei surpreso ao descobrir há algum tempo que esses algoritmos não estão prontamente disponíveis em livros didáticos e na web. Então, pesquisei e implementei 4 desses algoritmos e 1 algoritmo para ciclos em gráficos não direcionados em uma biblioteca Java de código aberto aqui: http://code.google.com/p/niographs/ .
BTW, desde que mencionei gráficos não direcionados: O algoritmo para esses é diferente. Construa uma árvore de abrangência e, em seguida, todas as arestas que não fazem parte da árvore formam um ciclo simples, juntamente com algumas arestas. Os ciclos encontrados desta maneira formam a chamada base de ciclo. Todos os ciclos simples podem ser encontrados combinando 2 ou mais ciclos básicos distintos. Para mais detalhes, por exemplo, consulte: http://dspace.mit.edu/bitstream/handle/1721.1/68106/FTL_R_1982_07.pdf .
fonte
jgrapht
, que é usada emhttp://code.google.com/p/niographs/
que você pode tomar exemplo de github.com/jgrapht/jgrapht/wiki/DirectedGraphDemoA escolha mais simples que encontrei para resolver esse problema foi usar a lib python chamada
networkx
.Ele implementa o algoritmo de Johnson mencionado na melhor resposta desta pergunta, mas simplifica sua execução.
Em resumo, você precisa do seguinte:
Resposta: [['a', 'b', 'd', 'e'], ['a', 'b', 'c']]
fonte
nx.DiGraph({'a': ['b'], 'b': ['c','d'], 'c': ['a'], 'd': ['e'], 'e':['a']})
Esclarecer:
Os Componentes fortemente conectados encontrarão todos os subgráficos que possuem pelo menos um ciclo, nem todos os ciclos possíveis no gráfico. por exemplo, se você pegar todos os componentes fortemente conectados e recolher / agrupar / mesclar cada um deles em um nó (ou seja, um nó por componente), obterá uma árvore sem ciclos (na verdade, um DAG). Cada componente (que é basicamente um subgráfico com pelo menos um ciclo) pode conter muitos mais ciclos possíveis internamente, portanto, o SCC NÃO encontrará todos os ciclos possíveis, encontrará todos os grupos possíveis que têm pelo menos um ciclo e, se você agrupar eles, o gráfico não terá ciclos.
para encontrar todos os ciclos simples em um gráfico, como outros mencionados, o algoritmo de Johnson é um candidato.
fonte
Recebi isso como uma pergunta de entrevista uma vez. Suspeito que isso tenha acontecido com você e você está vindo aqui para obter ajuda. Divida o problema em três perguntas e fica mais fácil.
Problema 1) Use o padrão do iterador para fornecer uma maneira de iterar os resultados da rota. Um bom lugar para colocar a lógica para obter a próxima rota é provavelmente o "moveNext" do seu iterador. Para encontrar uma rota válida, isso depende da sua estrutura de dados. Para mim, era uma tabela sql cheia de possibilidades válidas de rota, então tive que criar uma consulta para obter os destinos válidos, de acordo com uma fonte.
Problema 2) Empurre cada nó à medida que os encontrar em uma coleção à medida que os obtém, isso significa que você pode ver se está "dobrando de volta" sobre um ponto com muita facilidade, interrogando a coleção que está construindo em tempo real.
Problema 3) Se, a qualquer momento, você voltar a dobrar, poderá retirar as coisas da coleção e "fazer backup". Então, a partir desse ponto, tente "avançar" novamente.
Hack: se você estiver usando o Sql Server 2008, existem algumas novas coisas de "hierarquia" que você pode usar para resolver rapidamente isso se você estruturar seus dados em uma árvore.
fonte
As variantes baseadas em DFS com bordas traseiras encontrarão ciclos de fato, mas em muitos casos NÃO serão ciclos mínimos . Em geral, o DFS indica que há um ciclo, mas não é bom o suficiente para encontrar ciclos. Por exemplo, imagine 5 ciclos diferentes compartilhando duas arestas. Não há uma maneira simples de identificar ciclos usando apenas DFS (incluindo variantes de retorno).
De fato, o algoritmo de Johnson fornece todos os ciclos simples únicos e possui boa complexidade de tempo e espaço.
Mas se você deseja apenas encontrar ciclos MÍNIMOS (o que significa que pode haver mais de um ciclo passando por qualquer vértice e estamos interessados em encontrar um mínimo) E seu gráfico não é muito grande, tente usar o método simples abaixo. É MUITO simples, mas bastante lento em comparação com o de Johnson.
Portanto, uma das maneiras absolutamente mais fáceis de encontrar ciclos MÍNIMOS é usar o algoritmo de Floyd para encontrar caminhos mínimos entre todos os vértices usando a matriz de adjacência. Esse algoritmo não é nem de longe tão ótimo quanto o de Johnson, mas é tão simples e seu loop interno é tão rígido que, para gráficos menores (<= 50-100 nós), faz absolutamente sentido usá-lo. A complexidade do tempo é O (n ^ 3), a complexidade do espaço O (n ^ 2) se você usar o rastreamento dos pais e O (1) se não usar. Primeiro de tudo, vamos encontrar a resposta para a pergunta, se houver um ciclo. O algoritmo é simples. Abaixo está um trecho de código em Scala.
Originalmente, esse algoritmo opera no gráfico de borda ponderada para encontrar todos os caminhos mais curtos entre todos os pares de nós (daí o argumento de pesos). Para que funcione corretamente, é necessário fornecer 1 se houver uma borda direcionada entre os nós ou NO_EDGE, caso contrário. Após a execução do algoritmo, você pode verificar a diagonal principal, se houver valores menores que NO_EDGE do que este nó participará de um ciclo de duração igual ao valor. Todos os outros nós do mesmo ciclo terão o mesmo valor (na diagonal principal).
Para reconstruir o próprio ciclo, precisamos usar a versão ligeiramente modificada do algoritmo com o rastreamento dos pais.
A matriz dos pais inicialmente deve conter o índice de vértices de origem em uma célula de aresta, se houver uma aresta entre os vértices e -1 caso contrário. Após o retorno da função, para cada borda, você terá referência ao nó pai na árvore de caminho mais curto. E então é fácil recuperar os ciclos reais.
Em suma, temos o seguinte programa para encontrar todos os ciclos mínimos
e um pequeno método principal apenas para testar o resultado
e a saída é
fonte
No caso de gráfico não direcionado, um artigo publicado recentemente ( lista ideal de ciclos e caminhos st em gráficos não direcionados ) oferece uma solução assintoticamente ideal. Você pode lê-lo aqui http://arxiv.org/abs/1205.2766 ou aqui http://dl.acm.org/citation.cfm?id=2627951 Eu sei que isso não responde à sua pergunta, mas desde o título de sua pergunta não mencionar direção, ainda pode ser útil para a pesquisa do Google
fonte
Inicie no nó X e verifique todos os nós filhos (os nós pai e filho são equivalentes se não forem direcionados). Marque esses nós filhos como filhos de X. Em qualquer nó filho A, marque seus filhos como filhos de A, X ', onde X' é marcado como 2 passos de distância.). Se você pressionar X mais tarde e marcar como filho de X '', isso significa que X está em um ciclo de 3 nós. Voltar para o pai é fácil (como está, o algoritmo não tem suporte para isso, então você encontrará o pai com X ').
Nota: Se o gráfico não for direcionado ou tiver arestas bidirecionais, esse algoritmo se tornará mais complicado, supondo que você não deseje atravessar a mesma aresta duas vezes durante um ciclo.
fonte
Se o que você deseja é encontrar todos os circuitos elementares em um gráfico, você pode usar o algoritmo EC, de JAMES C. TIERNAN, encontrado em um artigo desde 1970.
O algoritmo EC muito original , como eu consegui implementá-lo em php (espero que não haja erros, é mostrado abaixo). Pode encontrar loops também, se houver algum. Os circuitos nesta implementação (que tenta clonar o original) são os elementos diferentes de zero. Zero aqui significa inexistência (nulo como o conhecemos).
Além disso, segue uma outra implementação que dá ao algoritmo mais independência, isso significa que os nós podem começar de qualquer lugar, inclusive de números negativos, por exemplo, -4, -3, -2, etc.
Nos dois casos, é necessário que os nós sejam seqüenciais.
Você pode precisar estudar o artigo original, James C. Tiernan Elementary Circuit Algorithm
então esta é a outra implementação, mais independente do gráfico, sem saltar e sem valores de matriz; em vez disso, usa chaves de matriz, o caminho, o gráfico e os circuitos são armazenados como chaves de matriz (use valores de matriz, se desejar, altere apenas o necessário linhas). O gráfico de exemplo começa em -4 para mostrar sua independência.
Analisei e documentei a CE, mas infelizmente a documentação está em grego.
fonte
Existem duas etapas (algoritmos) envolvidas na localização de todos os ciclos em um DAG.
O primeiro passo é usar o algoritmo de Tarjan para encontrar o conjunto de componentes fortemente conectados.
O segundo passo é encontrar ciclos (caminhos) dentro dos componentes conectados. Minha sugestão é usar uma versão modificada do algoritmo de Hierholzer.
A ideia é:
Aqui está o link para uma implementação Java com um caso de teste:
http://stones333.blogspot.com/2013/12/find-cycles-in-directed-graph-dag.html
fonte
http://www.me.utexas.edu/~bard/IP/Handouts/cycles.pdf
fonte
Eu tropecei no algoritmo a seguir, que parece ser mais eficiente que o algoritmo de Johnson (pelo menos para gráficos maiores). No entanto, não tenho certeza sobre seu desempenho em comparação com o algoritmo de Tarjan.
Além disso, só verifiquei se há triângulos até agora. Se estiver interessado, consulte "Algoritmos de listagem de arboricidade e subgráfico" de Norishige Chiba e Takao Nishizeki ( http://dx.doi.org/10.1137/0214017 )
fonte
Solução Javascript usando listas vinculadas de conjuntos disjuntos. Pode ser atualizado para separar florestas definidas para tempos de execução mais rápidos.
fonte
DFS a partir do nó inicial s, acompanhe o caminho do DFS durante a travessia e registre o caminho se encontrar uma borda do nó v no caminho para s. (v, s) é uma extremidade traseira na árvore DFS e, portanto, indica um ciclo contendo s.
fonte
Em relação à sua pergunta sobre o ciclo de permutação , leia mais aqui: https://www.codechef.com/problems/PCYCLE
Você pode tentar este código (digite o tamanho e o número dos dígitos):
fonte
Versão DFS c ++ para o pseudocódigo na resposta do segundo andar:
fonte