Universidade Federal do ABC Curso de Pós-Graduação em Ciência da Computação Dissertação de Mestrado Edilson José Rodrigues Um Algoritmo para o Problema do Isomorfismo de Grafos Santo André 2014 Universidade Federal do ABC Curso de Pós-Graduação em Ciência da Computação Dissertação de Mestrado Edilson José Rodrigues Um Algoritmo para o Problema do Isomorfismo de Grafos Trabalho apresentado como requisito parcial para a obtenção do título de Mestre em Ciência da Computação, sob orientação do Professor Doutor Daniel Morgato Martin. Santo André 2014 Resumo Neste trabalho estudamos o Problema do Isomorfismo de Grafos e a sua complexidade para resolvê-lo. Nossa principal contribuição é a proposta de um algoritmo para o caso geral do Problema, baseado no particionamento do conjunto de vértices e em emparelhamentos perfeitos de grafos bipartidos. Estudamos também o algoritmo de Brendan McKay, que é o mais rápido algoritmo para o Problema do Isomorfismo de Grafos conhecido. Ao final, implementamos o algoritmo proposto nesta dissertação e o algoritmo de McKay. Após a comparação dos dois algoritmos, verificamos que os resultados obtidos pelo algoritmo proposto não foram satisfatórios, porém apresentamos possíveis melhorias de como deixá-lo mais eficiente. Palavras-chave: Problema do Isomorfismo de Grafos. Emparelhamentos em grafos bipartidos. Particionamento do conjunto de vértices. Abstract In this work we study the Graph Isomorphism Problem and their complexity to solve it. Our main contribution is to propose an algorithm for the general case of the Problem, based on partitioning the set vertex and perfect matchings of bipartite graphs. We also studied the Brendan McKay’s algorithm, who is the fastest algorithm for the Graph Isomorphism Problem known. At the end, we implemented the algorithm proposed in this dissertation and McKay’s algorithm. After comparison of the two algorithms, we found that the results obtained by the proposed algorithm were not satisfactory, but improvements are possible as to make it more efficient. Key words: Graph Isomorphism Problem. Matchings in bipartite graphs. Partitioning of vertex set. Agradecimentos Agradeço primeiramente a Deus, a minha esposa Taís, aos meus pais José e Maria, ao meu orientador Prof. Dr. Daniel Martin, a UFABC, e todos aqueles que, direta ou indiretamente me ajudaram na realização deste trabalho. Conteúdo 1 Introdução 1.1 Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Motivação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3 Estrutura do trabalho . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Preliminares 2.1 Definições básicas . . . . . . . . . . 2.1.1 Grafo . . . . . . . . . . . . 2.1.2 Vizinhança e grau . . . . . 2.1.3 Subgrafo . . . . . . . . . . . 2.1.4 Representações de grafos no 2.2 Isomorfismo de grafos . . . . . . . 2.3 Simetrias em grafos . . . . . . . . . 2.4 Problemas computacionais . . . . . 2.4.1 Classes de problemas . . . . 2.4.2 NP-Completo . . . . . . . . . . . . . . . . . . . . . . . . plano . . . . . . . . . . . . . . . . . . . . 3 Isomorfismo de Grafos 3.1 Problema do Isomorfismo de Grafos . 3.2 Problema do Automorfismo de Grafos 3.3 Relação entre os problemas . . . . . . 3.4 Problema do Isomorfismo de Subgrafos 3.5 Aplicações práticas . . . . . . . . . . . 3.5.1 Impressões digitais . . . . . . . 3.5.2 Zero knowledge proof . . . . . 3.5.3 Aplicações em química . . . . . 3.5.4 Outras aplicações . . . . . . . . 3.6 Revisão bibliográfica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi xi xii xii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 1 2 2 3 4 5 7 8 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 11 13 13 14 15 15 15 17 17 18 4 Emparelhamentos em grafos bipartidos 4.1 Definições . . . . . . . . . . . . . . . . . . . 4.2 Encontrando um emparelhamento máximo . 4.3 Enumerando os emparelhamentos perfeitos 4.3.1 Componentes fortemente conexas . . 4.3.2 Gerando novos emparelhamentos . . 4.3.3 Análise de complexidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 19 20 24 25 29 32 5 Algoritmo para o Problema do Isomorfismo de Grafos 5.1 Simulação . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1.1 Efeitos de uma p-simulação . . . . . . . . . . . . 5.1.2 Partições . . . . . . . . . . . . . . . . . . . . . . 5.1.3 Partição associada a uma rodada . . . . . . . . . 5.1.4 Algoritmo para uma p-simulação . . . . . . . . . 5.1.5 Árvore de partição . . . . . . . . . . . . . . . . . 5.2 Propriedades de uma p-simulação . . . . . . . . . . . . . 5.3 Algoritmo proposto . . . . . . . . . . . . . . . . . . . . . 5.3.1 Emparelhamentos perfeitos e isomorfismo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 36 37 38 39 40 42 44 46 50 6 Algoritmo Pratical Graph Isomorphism 6.1 Rotulação canônica e isomorfo canônico 6.2 Refinamento de partições . . . . . . . . 6.3 Algoritmo de refinamento . . . . . . . . 6.4 Partições aninhadas . . . . . . . . . . . 6.5 Árvore de busca . . . . . . . . . . . . . 6.6 Podando a árvore de busca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 54 55 56 59 59 63 7 Resultados 7.1 Gerador de grafos aleatórios - instâncias positivas 7.1.1 Resultados . . . . . . . . . . . . . . . . . 7.2 Grafos com alto padrão de simetria . . . . . . . . 7.2.1 Grafos fortemente regulares não isomorfos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 66 66 68 68 8 Considerações Finais 8.1 Trabalhos futuros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 72 Bibliografia 73 9 Anexo 78 . . . . . . . . . . . . . . . . . . . . . . . . Lista de Figuras 2.1 2.2 2.3 2.4 2.5 2.6 Grafo completo com 4 vértices. H é subgrafo de G . . . . . . . Um grafo planar. . . . . . . . . Dois grafos isomorfos. . . . . . Hipercubo de dimensão 3. . . . Um grafo rígido. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 3 3 4 6 7 4.1 4.2 Grafo dirigido por um emparelhamento perfeito. . . . . . . . . . . . ~ . . . . . . . . . Componentes fortemente conexas em um digrafo D. 21 26 5.1 5.2 5.3 5.4 5.5 Simulação em um grafo G . . . . . . . . . . . . . Grafo G rotulado . . . . . . . . . . . . . . . . . . Árvore de partição. . . . . . . . . . . . . . . . . . Exemplos de partições. . . . . . . . . . . . . . . . Exemplo de árvore de recursão do Algoritmo 15. . . . . . 37 43 43 49 50 6.1 6.2 Grafo G. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Árvore T (G). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 62 7.1 Partição π obtida após uma simulação em um grafo fortemente regular. 70 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lista de Tabelas 2.1 Um isomorfismo entre G e H. . . . . . . . . . . . . . . . . . . . . . . 7.1 7.2 7.3 7.4 Grafos Grafos Grafos Grafos aleatórios densos. . . . . . . . . . . aleatórios esparsos. . . . . . . . . . fortemente regulares isomorfos. . . fortemente regulares não isomorfos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 66 67 68 70 Lista de Siglas PAG Problema do Automorfismo de Grafos PGI Pratical Graph Isomorphism PIG Problema do Isomorfismo de Grafos ZKP Zero knowledge proof CAPÍTULO 1 Introdução 1.1 Objetivos Este trabalho tem como finalidade estudar o Problema do Isomorfismo de Grafos. Nesse problema, o conjunto de instâncias válidas são pares {G, H} de grafos finitos, e o objetivo é decidir se os grafos G e H são isomorfos, isto é, se possuem a mesma estrutura subjacente. A principal contribuição deste trabalho é a proposta de uma nova abordagem para o Problema do Isomorfismo de Grafos baseada num algoritmo desenvolvido por McKay [25] em 1981 e que utiliza fortemente o conceito de emparelhamentos em grafos bipartidos e tópicos relacionados. Nossa proposta consiste em distinguir os vértices de um grafo através de um processo de simulação e posteriormente utilizar o resultado dessas simulações para acelerar um algoritmo de força bruta que enumera mapas entre G e H na busca de um possível isomorfismo entre esses grafos. O objetivo específico deste trabalho é apresentar um algoritmo que execute, na prática, em um tempo razoável. É sabido que os algoritmos conhecidos para o caso geral do Problema do Isomorfismo de Grafos possuem complexidade de tempo expo- Capítulo 1. Introdução xii nencial, demandando muito tempo de processamento (a complexidade do Problema e o consumo de tempo dos algoritmos serão abordados no Capítulo 3). 1.2 Motivação O Problema do Isomorfismo de Grafos tem uma vasta gama de aplicabilidade prática em diversas áreas de conhecimento, e por isso resolvemos estudá-lo. No Capítulo 3, mostraremos uma série de aplicações práticas do problema. 1.3 Estrutura do trabalho Esta monografia, que doravante será chamada de dissertação, está dividida em seis capítulos, além deste breve capítulo introdutório: o Capítulo 2 introduz os principais conceitos e definições da teoria dos grafos que são utilizados ao longo da dissertação. No Capítulo 3, é apresentado o Problema do Isomorfismo de Grafos, com definições, resultados relacionados e aplicações. O Capitulo 4 aborda o conceito de emparelhamentos em grafos bipartidos que servirá de base para o algoritmo apresentado no Capítulo 5. No Capítulo 6 apresentamos o Algoritmo proposto por McKay, comparando as diferenças e semelhanças com o algoritmo apresentado nesta dissertação. O último Capítulo 7 apresenta os resultados obtidos, bem como os detalhes da sua implementação na linguagem C++. CAPÍTULO 2 Preliminares 2.1 Definições básicas Neste capítulo serão introduzidas algumas definições e notações necessárias para que o problema considerado nesta dissertação seja descrito de forma precisa. Essas definições foram baseadas nas referências [18], [34], [22], [28], [6], [5] e [12]. 2.1.1 Grafo Um grafo é um par ordenado (V, E) onde V é um conjunto finito não vazio, e E é um conjunto de pares não ordenados de elementos de V . O conjunto V é chamado de conjunto de vértices 1 , e E de conjunto de arestas. Quando um grafo G não é definido de maneira explícita, usa-se V (G) e E(G) para fazer referência aos conjuntos de vértices e de arestas de G respectivamente. 1 Neste trabalho, é comum supor que o conjunto V é o conjunto de números naturais {1, . . . , n} para algum n. Capítulo 2. Preliminares 2.1.2 2 Vizinhança e grau Se u e v são vértices de um grafo, e a = {u, v} é uma aresta desse grafo, diz-se que u e v são vizinhos ou vértices adjacentes, ou ainda que estão ligados pela aresta a. Dizemos ainda que u e v são as pontas ou extremidades da aresta a. O conjunto de vizinhos de um vértice v num grafo G é denotado por NG (v), ou seja, tem-se NG (v) = u ∈ V (G) : {u, v} ∈ E(G) . O grau de um vértice v em um grafo G é o número de vizinhos de v em G, ou seja, é o número de elementos de NG (v). Denota-se o grau de v por dG (v). Quando o grafo G puder ser identificado pelo contexto, denotamos NG (v) e dG (v) simplesmente por N (v) e d(v) respectivamente. Quando todos os vértices de um grafo possuem grau d, diz-se que esse grafo é d-regular. 0 1 G 2 3 Figura 2.1: Grafo completo com 4 vértices. Na Figura 2.1, o grafo G é 3-regular. O grafo completo de ordem n é um grafo Kn = (V, E), onde V = {1, 2, . . . , n}, que possui todas as arestas, isto é, com E = {u, v} : {u, v} ⊆ V . 2.1.3 Subgrafo Um grafo H é um subgrafo de um grafo G se VH ⊆ VG e EH ⊆ EG . 3 2.1. Definições básicas 2 3 2 3 4 5 1 H G 4 5 Figura 2.2: H é subgrafo de G Na figura 2.2, o grafo H é um subgrafo de G. 2.1.4 Representações de grafos no plano O objeto grafo leva esse nome porque pode ser representado graficamente de modo natural. Pode-se representar os vértices de um grafo por pontos no plano, e as arestas desse grafo por curvas que ligam pares de pontos. Há infinitas maneiras de se representar um mesmo grafo no plano, e cada uma delas é chamada de uma imersão no plano. Um grafo é planar se possui uma imersão no plano de modo que nenhum par de arestas se cruze. 1 2 K4 4 3 Figura 2.3: Um grafo planar. Na figura 2.3 temos um exemplo de um grafo planar. O objetivo deste trabalho não é estudar representações de grafos no plano, mas esses conceitos serão utilizados Capítulo 2. Preliminares 4 para ilustrar e exemplificar ideias. 2.2 Isomorfismo de grafos Os grafos da Figura 2.4 compartilham algumas propriedades entre si (como o número de vértices e de arestas), dando a impressão que são dois grafos iguais. 1 2 3 G 1 2 3 H 4 5 6 4 5 6 Figura 2.4: Dois grafos isomorfos. Claramente, esses grafos não são iguais pois seus respectivos conjuntos de arestas não são os mesmos: G possui a aresta {2, 4} e H não; G possui a aresta {2, 6} e H não; H possui a aresta {4, 5} enquanto G não a possui. No entanto, pode-se dizer que, de algum modo, esses grafos compartilham certas propriedades, como conjunto de vértices e padrão de vizinhanças. A definição a seguir, que é central nesta dissertação, formaliza o que acabamos de dizer. Dois grafos G e H são isomorfos se há uma bijeção ϕ : V (G) → V (H) tal que, para todo par de vértices i, j ∈ V (G), tem-se {i, j} ∈ E(G) ⇐⇒ {ϕ(i), ϕ(j)} ∈ E(H). Uma bijeção com esta propriedade é chamada de isomorfismo. Se G e H são isomorfos, denota-se esse fato pela expressão G ∼ = H. O conjunto de todos os isomorfismos entre G e H é denotado por Iso(G, H). 5 2.3. Simetrias em grafos Note que Iso(G, H) é um conjunto de funções. A Tabela 2.1 mostra um isomorfismo entre os grafos G e H da Figura 2.4. Tabela 2.1: Um isomorfismo entre G e H. i ϕ(i) 1 1 2 5 3 3 4 4 5 2 6 6 Seja G = (V, E) um grafo e seja K o conjunto de todos os pares não ordenados de V . Então G = (V, K\E) é chamado de grafo complementar de G, ou simplesmente, de complemento de G. É um fato simples que, se G ∼ = H. = H, então G ∼ 2.3 Simetrias em grafos Alguns grafos, como o da Figura 2.5 são visualmente simétricos2 . O grafo da Figura 2.5 é chamado de cubo. Em geral, um hipercubo de dimensão n é um grafo Qn = (V, E) onde os conjuntos V e E são dados por V = {0, 1}n E = {{u, v} ⊆ V | u difere de v em apenas um bit} 2 O conceito de grafo simétrico será enunciado após o conceito de automorfismo Capítulo 2. Preliminares 6 000 Q3 010 011 001 100 101 110 111 Figura 2.5: Hipercubo de dimensão 3. O hipercubo de dimensão n > 3 também é simétrico, embora não seja tão fácil de se perceber isso pela observação de uma imersão do Qn no plano. Para formalizar o conceito de simetrias em grafos de modo abstrato utilizamos o conceito de automorfismo. Um automorfismo de um grafo G = (V, E) é uma permutação3 ϕ : V → V que preserva as adjacências em G, isto é, que satisfaz {i, j} ∈ E ⇐⇒ {ϕ(i), ϕ(j)} ∈ E para todo par de vértices i, j ∈ V . O conjunto de todos os automorfismos ϕ de um grafo G é denominado Aut(G), isto é, tem-se Aut(G) = {ϕ : V → V | ϕ é automorfismo de G}. Todo grafo possui um automorfismo trivial, a permutação identidade. Se um grafo 3 Uma permutação é uma bijeção de um conjunto nele mesmo. 7 2.4. Problemas computacionais não possui nenhum automorfismo, exceto o trivial, ele é chamado de grafo rígido. 6 1 2 3 4 5 Figura 2.6: Um grafo rígido. Um grafo G é não rígido se |Aut(G)| > 1, ou seja, se G não for rígido. Os grafos das Figuras 2.1 e 2.5 são exemplos de grafos simétricos. Um grafo G é vértice-transitivo se, dado qualquer par de vértices u, v, há um automorfismo ϕ : V → V tal que ϕ(u) = v. Um grafo G é aresta-transitivo se, dados quaisquer dois pares de vértices adjacentes i1 , j1 e i2 , j2 , há um automorfismo ϕ : V → V tal que ϕ(i1 ) = i2 e ϕ(j1 ) = j2 . Um grafo G é simétrico se é vértice-transitivo e aresta-transitivo. O conjunto de todas as permutações de {1, 2, . . . , n} é denotado por Sn . O grafo completo com n vértices é um exemplo de grafo para o qual Aut(G) = Sn . Dado um grafo G = (V, E) e uma função injetora ϕ : V → V , denotamos por ϕ(G) o grafo definido por ϕ(G) = (V, ϕ(E)) onde ϕ(E) = {ϕ(u), ϕ(v)} : {u, v} ∈ E(G) . 2.4 Problemas computacionais As definições4 que se seguem foram baseadas nas referências [18] e [33]. 4 Estas definições são baseadas em um modelo computacional de Turing Capítulo 2. Preliminares 8 Um alfabeto é um conjunto finito cujos elementos são chamados de símbolos. Uma palavra sobre um alfabeto Σ é uma sequência finita de símbolos de Σ. O comprimento de uma palavra w é o número de elementos da sequência w e é denotado por |w|. O conjunto de todas as palavras sobre Σ é denominado Σ∗ . Uma linguagem sobre um alfabeto Σ é um subconjunto de Σ∗ . Em computação, um tipo de problema muito estudado é o problema de decisão. Problemas desse tipo admitem apenas as respostas sim ou não. Por exemplo, descobrir se uma página na web contêm o texto “index of” é um problema de decisão; descobrir se a conectividade de uma rede de computadores é tolerante à falha de k máquinas nessa rede é outro problema de decisão. Todo problema de decisão em computação corresponde ao problema de se decidir se uma dada palavra w sobre um alfabeto Σ pertence ou não a uma linguagem L. Neste trabalho, os termos linguagem e problema se referem ao mesmo conceito. Por isso, quando se diz que um algoritmo A decide uma linguagem L, o que se quer dizer é que o algoritmo A, ao receber uma palavra w como entrada, devolve corretamente sim caso w ∈ L e não caso w 6∈ L. 2.4.1 Classes de problemas Classificamos os problemas de decisão em classes de complexidade de acordo com a quantidade de recursos5 necessários para resolvê-los. Embora existam diversas classes de complexidade, este trabalho apenas faz referência às classes P e NP, que serão definidas a seguir (conforme [18]). Uma linguagem L pertence à classe P se existe um polinômio p(n) e um algoritmo que decide L cujo tempo total de execução com entrada w (até uma resposta sim ou não) não ultrapassa p(|w|). Uma linguagem L está na classe NP se e somente se há uma linguagem A ∈ P 5 As definições apresentadas são aplicadas ao modelo computacional de Turing. 9 2.4. Problemas computacionais e um polinômio p(n) tal que, para toda palavra x ∈ Σ∗ , tem-se x ∈ L se e somente se existe y ∈ Σ∗ com |y| ≤ p(|x|) tal que (x, y) ∈ A. A palavra y é chamada de um certificado para x ∈ L. Sejam A e B duas linguagens. Dizemos que A é Turing-redutível a B em tempo polinomial (escreve-se A ≤pT B) se existe um algoritmo que decide A, que executa em tempo polinomial, e que pode ter instruções condicionais na forma se y ∈ B então . . . senão . . . fim. onde considera-se (para o cômputo do consumo de tempo do algoritmo) que uma consulta da forma y ∈ B leva tempo constante. 2.4.2 NP-Completo Uma linguagem L é NP-Completo se L ∈ NP e toda linguagem em NP é Turingredutível a L. Esta classe é de suma importância para o entendimento do problema P versus NP, pois haverá um algoritmo em tempo polinomial para um problema NPCompleto se e somente se todo problema em NP possuir um algoritmo em tempo polinomial que o resolva. Em outras palavras, se houver um algoritmo polinomial para um problema NP-Completo, então P = NP. Como há evidências que P e NP são diferentes, a prova de que um problema é NP-Completo é considerada uma forte evidência desta intratabilidade. CAPÍTULO 3 Isomorfismo de Grafos Neste capítulo, apresentamos dois problemas computacionais: o Problema do Isomorfismo de Grafos e o Problema do Automorfismo de Grafos. Daremos também algumas aplicações do Problema do Isomorfismo em situações práticas. 3.1 Problema do Isomorfismo de Grafos Definimos a versão de decisão para o Problema do Isomorfismo de Grafos (PIG) do seguinte modo: dado um par de grafos {G, H}, decidir se esse par pertence ao conjunto I = {G, H} | G é isomorfo a H . Um algoritmo para a versão de decisão do PIG retorna sim se os grafos da entrada forem isomorfos e não caso contrário. O melhor algoritmo conhecido para o PIG é o proposto por Babai e Luks em [4], Capítulo 3. Isomorfismo de Grafos √ que possui tempo de execução 2O( n log n) , 12 onde n é o número de vértices. Foggia [10] comparou os algoritmos propostos por Ullmann [36], Cordella[11], McKay [25] e Schimidt [32], e concluiu que o algoritmo implementado no programa Nauty (e elaborado por B. McKay [25]) é o mais rápido na prática. Em [35], Torán demonstrou que o PIG é pelo menos tão difícil quanto o problema de se computar o determinante de uma matriz com entradas inteiras. Existem algoritmos que decidem casos especiais do PIG em tempo polinomial. Tais algoritmos existem para: árvores [18], grafos planares [15], grafos de intervalo [20], grafos de permutação [7], grafos de genus limitado [26], grafos de grau limitado [21], e grafos cujos autovalores possuem multiplicidade limitada [3]. No entanto, não se conhece nenhum algoritmo eficiente para o caso geral e portanto não se sabe se o PIG está na classe P ou se é NP-Completo [16], embora suspeita-se que ele esteja estritamente entre as duas classes [2]. Por outro lado, dados dois grafos G e H e um mapa ϕ entre seus conjuntos de vértices, há um algoritmo polinomial que decide se ϕ é um isomorfismo entre os grafos dados. Algoritmo 1: Um verificador para o PIG Entrada: Grafos G e H com mesmo número de vértices, e função injetora ϕ : V (G) → V (H) Saída: sim se ϕ(G) = H e não caso contrário início para todo u ∈ V faça para todo v ∈ V faça se {u, v} ∈ E(G) e {ϕ(u), ϕ(v)} ∈ / E(H) então retorna não fim se se {u, v} ∈ / E(G) e {ϕ(u), ϕ(v)} ∈ E(H) então retorna não fim se fim para todo fim para todo retorna sim fim 13 3.2. Problema do Automorfismo de Grafos Conforme a definição de certificado, (vide seção 2.4.1), sabemos que o PIG pertence à classe NP. Para demonstrar tal afirmação, considere a linguagem A = ({G, H}, ϕ) : G, H são grafos e ϕ ∈ Iso(G, H) Note que {G, H} ∈ P IG ⇔ ({G, H}, ϕ) ∈ A. Ademais, A ∈ P pois o Algoritmo 1 decide A e é claramente polinomial. Note também que qualquer mapeamento ϕ : V (G) → V (H) pode ser representado usando tamanho O(n). Com isso, podemos concluir que PIG ∈ NP. 3.2 Problema do Automorfismo de Grafos Seja G um grafo. Decidir se o grupo de automorfismos de G possui algum automorfismo diferente do trivial é um problema de decisão chamado Problema do Automorfismo de Grafos (PAG), e consiste em determinar se G pertence ao conjunto A = {G : |Aut(G)| > 1}. Assim como o PIG, o Problema do Automorfismo de Grafos também pertence à classe NP [18]. 3.3 Relação entre os problemas Podemos mostrar que o PAG é Turing-redutível ao PIG. Antes de mostrarmos a redução, precisamos da seguinte definição: G[i] é o grafo obtido de G ligando ao vértice i um caminho com |V (G)| + 1 novos vértices. O propósito desta construção é que todo automorfismo deste grafo modificado mapeia o vértice i em si mesmo, e além disso, não ocorre no grafo modificado nenhum automorfismo que não estivesse Capítulo 3. Isomorfismo de Grafos 14 presente no grafo original (isto é, Aut(G[i] ) ⊆ Aut(G)). A ideia central da Turing-redução apresentada no Algoritmo 2 é a seguinte: se os grafos G e H são isomorfos, e existe um isomorfismo ϕ de G em H tal que v = ϕ(u), então os grafos G[u] e H[v] também são isomorfos. O Algoritmo 2 computa o PAG em tempo polinomial fazendo consultas de decisão ao PIG como descrito a seguir. Algoritmo 2: Algoritmo para o PAG usando consultas ao PIG Entrada: Grafo G com conjunto de vértices {1, 2, . . . , n} Saída: sim se |Aut(G)| > 1 e não se G é rígido início para i ← 1 até n − 1 faça para j ← i + 1 até n faça se {G[i] , G[j] } ∈ PIG então retorna sim fim se fim para fim para retorna não fim Retomando a demonstração da corretude da Turing-redução, o grafo G possui um automorfismo não trivial se, e somente se, G tem um automorfismo que mapeia i em j para algum par de vértices i 6= j. Pela observação no parágrafo que precede o Algoritmo 2, um automorfismo que mapeia i em j existe se, e somente se, os grafos G[i] e G[j] são isomorfos. Como o algoritmo testa todas as possíveis combinações de diferentes vértices i e j, ele consegue determinar, em tempo O(n2 ) se o grafo de entrada é rígido ou não. Portanto P AG ≤pT P IG. Atualmente não se conhece nenhuma Turing-redução do PIG ao PAG [18]. 3.4 Problema do Isomorfismo de Subgrafos Dados dois grafos G e H, o Problema do Isomorfismo de Subgrafos consiste em decidir se H é isomorfo a algum subgrafo de G, ou seja, se H pode ser transformado 15 3.5. Aplicações práticas em um subgrafo de G apenas renomeando-se seus vértices. Este problema é uma generalização do PIG e pertence à classe dos problemas NP-Completos, que são os problemas mais difíceis da classe NP [37]. 3.5 Aplicações práticas Nesta seção, destacaremos algumas aplicações práticas onde o problema de isomorfismo de grafos é utilizado. 3.5.1 Impressões digitais Nandi [27] utiliza o PIG para confrontar imagens de impressões digitais, que são representadas por grafos gerados pelas características presentes em cada imagem. Cada uma dessas características representa um vértice no grafo, e as arestas representam as relações que estas características possuem entre si. Para verificar se uma impressão digital corresponde a outra, resolve-se uma instância do Problema do Isomorfismo dos Grafos. Para essa aplicação, seria desejada a existência de um algoritmo polinomial para o PIG ou que as instâncias geradas a partir das imagens de impressões digitais fossem grafos de uma das classes onde o PIG é solúvel em tempo polinomial (como aquelas listadas na Seção 3.1). 3.5.2 Zero knowledge proof Sistemas de zero knowledge proof 1 tem aplicações em criptografia e protocolos de segurança que requerem autenticação. Uma dessas aplicações, que se baseia na dificuldade de se resolver o PIG, é descrita a seguir. Um agente P conhece um circuito Hamiltoniano C em um grafo G, e um outro agente, chamado Q, conhece o grafo G mas não o circuito Hamiltoniano. O 1 Adaptado de [18] e [13] Capítulo 3. Isomorfismo de Grafos 16 agente Q está disposto a pagar uma grande quantia em dinheiro pelo circuito Hamiltoniano2 C, porém, antes de fazer o pagamento, ele precisa se certificar de que P realmente conhece o circuito Hamiltoniano que diz conhecer. O agente P, por sua vez, precisa provar a Q que conhece o circuito C sem revelá-lo, pois se o revelar a Q, este poderá ser desonesto e não fazer o pagamento, uma vez que já adquiriu o objeto de seu desejo. Os dois começam, então, um jogo no qual, em cada rodada, P escolhe uma permutação qualquer ϕ : V (G) → V (G) e cria um grafo H = ϕ(G) (que é claramanete isomorfo a G) e o envia a Q. Note que, se P conhece um circuito Hamiltoniano C em G, então ele também conhece um em H, a saber, o circuito ϕ(C). Em seguida, Q solicita a P que revele exatamente uma dentre duas coisas: ou o isomorfismo ϕ entre G e H, ou um circuito Hamiltoniano em H. Se Q solicitou um isomorfismo, então P mostra o mapeamento ϕ e Q verifica que, de fato, este mapeamento é um isomorfismo de G em H. Se Q solicitou o circuito, P mostra o circuito ϕ(C) em H. As respostas de P não revelam o ciclo Hamiltoniano original em G (a menos que Q seja tão poderoso computacionalmente que consiga resolver o PIG). A cada rodada, Q saberá apenas o isomorfismo de H em G ou um circuito Hamiltoniano em H, mas nunca ambos numa mesma rodada. Ele precisaria de ambas as respostas na mesma rodada a fim de descobrir o circuito em G; assim, a informação permanece protegida. Para ser capaz de responder às duas perguntas corretamente, P deve conhecer um isomorfismo entre G e H e também um circuito Hamiltoniano em H (e portanto também em G). Suponhamos agora que P não conheça um circuito Hamiltoniano e esteja tentando enganar Q para ficar com o dinheiro. A cada rodada, P não sabe qual pergunta lhe será feita até enviar H para Q. O agente P pode tentar adivinhar qual pergunta Q irá fazer e gerar o grafo H de acordo. Se P acha que Q irá lhe 2 Um circuito é Hamiltoniano quando vértice é visitado apenas uma vez. 17 3.5. Aplicações práticas perguntar um isomorfismo ele pode gerar um grafo H isomorfo a G. Nesse caso, suas más intenções seriam descobertas caso Q lhe fizesse a outra pergunta pois ele não conhece um circuito Hamiltoniano em G (e portanto também não o conhece em H). Se P acha que Q irá lhe perguntar por um circuito Hamiltoniano ele pode tentar gerar um grafo H que tenha um tal circuito, mas seria desmascarado caso Q lhe pedisse por um isomorfismo. Com isso, se Q escolhe a pergunta aleatoriamente com probabilidade 1/2, a chance de P enganar Q é de 2−n , onde n é o número de rodadas. Portanto, após um número razoável de rodadas, Q se convence, com um alto grau de confiabilidade, de que P de fato conhece um circuito Hamiltoniano em G. Observa-se que, enquanto a primeira aplicação seria beneficiada caso PIG ∈ P , a segunda perderia completamente seu sentido, pois baseia-se na dificuldade computacional de se decidir a linguagem PIG. 3.5.3 Aplicações em química Para se determinar se uma molécula possui uma estrutura similar a uma outra, é necessário fazer uma comparação desta molécula com um banco de dados de moléculas existentes. Cada molécula é representada por um grafo, onde os vértices são os átomos e as arestas correspondem às ligações atômicas. O processo de comparação consiste em verificar se as estruturas moleculares são idênticas, ou seja, se os grafos que representam duas moléculas são isomorfos [30]. 3.5.4 Outras aplicações O autor Conte [8] utiliza o PIG para o reconhecimento de padrões. Já Farouk [9] trabalha com o reconhecimento de imagens e Pedarsani [29], em seu artigo, trata da segurança de informação em redes sociais, todos utilizam o PIG para a resolução de seus problemas. Capítulo 3. Isomorfismo de Grafos 3.6 18 Revisão bibliográfica Para a realização desta pesquisa foram consultados diversos trabalhos, entre artigos e dissertações. O primeiro a ser destacado é o artigo escrito por McKay [25], onde o autor apresenta um algoritmo para o PIG. Este algoritmo determina uma representação canônica dos grafos utilizando um particionamento ordenado de seus vértices. Todos os vértices de uma partição possuem uma mesma rotulação, distinguindo-os dos demais vértices do grafo. Este particionamento é calculado aplicando-se um conjunto de invariantes de vértices a uma primeira partição, que, inicialmente, agrupa todos os vértices do grafo. Ao final do processo, dois grafos são isomorfos se, e somente se, possuem a mesma representação. No Capítulo 6 descreveremos com maiores detalhes o algoritmo proposto por McKay. Para obter o referencial teórico do Problema do Isomorfismo de Grafos, foi estudado o livro de Köbler, Schöning e Torán [18]. Este livro traz as definições do PIG e do PAG, apresenta quais problemas computacionais são redutíveis ao PIG, e apresenta uma abordagem muito detalhada do PIG do ponto de vista de sua complexidade. CAPÍTULO 4 Emparelhamentos em grafos bipartidos O algoritmo que será proposto no Capítulo 5 utiliza emparelhamentos em um grafo bipartido para a verificação do isomorfismo entre dois grafos G e H. Portanto, neste capítulo vamos apresentar o conceito de emparelhamento, e também um método eficiente para enumerar todos os emparelhamentos perfeitos de um grafo bipartido. 4.1 Definições Um grafo G é chamado bipartido se seu conjunto de vértices V pode ser dividido em dois conjuntos disjuntos V1 e V2 , onde toda aresta de G é da forma {u, v} com u ∈ V1 , v ∈ V2 . Dado um grafo G, um emparelhamento M em G é um conjunto de arestas disjuntas duas a duas. Um vértice é dito saturado (ou coberto) por M quando é incidente a uma aresta do emparelhamento. Um emparelhamento M é dito: perfeito quando todos os vértices estão saturados; maximal quando não há nenhum emparelhamento Capítulo 4. Emparelhamentos em grafos bipartidos 20 M ′ que contenha propriamente M ; e máximo se não existe um emparelhamento com maior número de arestas que M . Observe que todo emparelhamento perfeito é máximo. Um caminho em um grafo é chamado de caminho alternante em relação a um emparelhamento M (ou M -alternante) se, para quaisquer duas arestas consecutivas desse caminho, uma está em M e a outra não. Um caminho alternante é dito caminho aumentante em relação a M (ou M -aumentante) quando o primeiro e o último vértice não estão saturados por M . ~ é um par ordenado (V, A) onde V é um conjunto Um grafo dirigido (ou digrafo) G finito de vértices e A é um conjunto de pares ordenados de V chamados arcos. Seja ~ dizemos que u é o vértice de partida e v o vértice de chegada a = (u, v) um arco de G, de a. Dizemos ainda que o arco a é orientado de u para v. A vizinhança de saída ~ : (u, v) ∈ A(G)}. ~ de um vértice u é o conjunto N + (u) = {v ∈ V (G) Dados um grafo bipartido G = (V1 ∪ V2 , E) e um emparelhamento M de G, ~ = D(G, ~ ~ = definimos o digrafo associado como sendo o digrafo D M ), onde V (D) ~ é o conjunto de arcos que é obtido orientando-se as arestas de M de V (G) e A(D) V1 para V2 e as arestas de E \ M no sentido oposto. A Figura 4.1 nos mostra um exemplo de um grafo bipartido G, de um emparelhamento perfeito M em G e do ~ digrafo associado D(G, M ). 4.2 Encontrando um emparelhamento máximo Nesta seção, apresentamos um algoritmo para encontrar um emparelhamento máximo em um grafo bipartido. Porém, antes de apresentarmos tal algoritmo, vamos enunciar o Teorema de Berge1 , que será importante para seu entendimento. Teorema 1. Um emparelhamento M de um grafo G é máximo se e somente se não 1 Adaptado de [17] e [31] 21 4.2. Encontrando um emparelhamento máximo 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 G M ~ D(G, M) Figura 4.1: Grafo dirigido por um emparelhamento perfeito. existe um caminho M -aumentante em G. Demonstração. Se existe um caminho M -aumentante P , podemos criar um emparelhamento maior eliminando as arestas de M que pertencem a P e incluindo as arestas de P que originalmente não pertençam a M . Note que esta permuta de arestas corresponde a tomar o emparelhamento2 M ∆P . Para provar a recíproca, suponhamos que M não é máximo. Portanto, existe um outro emparelhamento M ′ com mais arestas que M . Considere o grafo H definido por H = (V (G), M ∆M ′ ). Cada vértice v ∈ V (H) tem grau 0, 1 ou 2 porque v pode ser incidente em, no máximo, uma aresta de M e uma de M ′ . As componentes conexas de H podem 2 O símbolo ∆ denota a operação de diferença simétrica entre dois conjuntos. Capítulo 4. Emparelhamentos em grafos bipartidos 22 então ser: ou vértices isolados, ou circuitos de ordem par3 com arestas alternadas de M e de M ′ , ou ainda caminhos com arestas alternadas de M e de M ′ . Como |M ′ | > |M |, o grafo H tem mais arestas de M ′ que de M e, portanto, pelo menos uma das componentes de H é um caminho que começa e termina com arestas de M ′ (e que não pertencem a M ); esse caminho é um caminho M -aumentante no grafo G. Isso encerra a prova do teorema. Com base no conceito de caminho M -aumentante e no Teorema 1, podemos desenvolver um algoritmo que encontra um emparelhamento máximo (Algoritmo 3). A ideia do algoritmo é encontrar caminhos M -aumentantes e fazer a permuta das arestas de M com as arestas dos caminhos encontrados. A busca por caminhos aumentantes e a permuta de arestas são realizadas sucessivamente até que o emparelhamento corrente seja máximo4 . Vamos usar S para denotar o conjunto de vértices saturados. O algoritmo tem como entrada um grafo G e um emparelhamento inicial M , que ~ pode ser vazio. Note que, caso M = ∅, todas os arcos de D(G, M ) serão orientados em um único sentido. Quanto maior for o emparelhamento inicial M , mais rápido o algoritmo encontra um emparelhamento máximo. Em seguida, para todo vértice a ∈ V1 não saturado, é realizada uma busca em profundidade, e é obtido um caminho dirigido P entre a e um vértice não saturado b ∈ V2 . O vértice a é não saturado pela própria escolha de a (na linha 8 do Algoritmo 3) e b é não saturado pois é o vértice devolvido pelo algoritmo de busca em profundidade (Algoritmo 4), que possui justamente como critério de parada encontrar um vértice não saturado em V2 (veja linha 2). Note que P é um caminho M -aumentante, pois começa em a e termina em b[a], que são ambos não saturados. 3 4 Circuitos que possuem um número par de arestas. Este algoritmo é uma adaptação do algoritmo de Hopcroft-Karp, disponível em [14] 23 4.2. Encontrando um emparelhamento máximo Algoritmo 3: Encontrar emparelhamento máximo de um grafo G Entrada: Grafo bipartido G = (V1 ∪ V2 , E), emparelhamento M 1 início 2 S ← {v ∈ V1 ∪ V2 : v incide em alguma aresta de M } ~ ← D(G, ~ 3 D M) 4 repita ~ 5 para todo v ∈ D(G) faça 6 pai[v] ← N U LL 7 fim para todo 8 para todo a ∈ V1 \ S faça 9 pai[a] = a 10 b[a] ← Busca(a) 11 fim para todo 12 para todo a ∈ V1 \ S faça 13 se b[a] 6= N U LL então 14 Inverte(b[a]) 15 S ← S ∪ {a, b[a]} 16 fim se 17 fim para todo 18 até até que a cardinalidade de S não aumente; 19 fim Uma vez encontrado o caminho M -aumentante P de a até b, o emparelhamento M é substituido por M ∆P . Essa atualização do emparelhamento é feita de modo implícito, invertendo-se a orientação dos arcos no caminho P (através de uma ~ é, na realidade, D(G, ~ M ∆P ). chamada ao Algoritmo 5). Desse modo o novo grafo D O laço entre as linhas 4 e 18 do Algoritmo 3 é executado até que não seja mais possível saturar nenhum novo vértice, ou seja, até que nenhum outro caminho M aumentante possa ser encontrado. Quando isto ocorre, pelo Teorema 1, temos que M é máximo. Capítulo 4. Emparelhamentos em grafos bipartidos 24 Algoritmo 4: Busca Entrada: vértice u Saída: vértice b ou N U LL 1 início 2 se u ∈ V2 e N + (u) = ∅ então 3 retorna u 4 fim se 5 para todo v ∈ N + (u) faça 6 se pai[v] = N U LL então 7 pai[v] ← u 8 b ← Busca(v) 9 se b 6= N U LL então 10 retorna b 11 fim se 12 fim se 13 fim para todo 14 retorna N U LL 15 fim 4.3 Enumerando os emparelhamentos perfeitos Nesta seção apresentamos um algoritmo que enumera os emparelhamentos perfeitos em um grafo bipartido G, baseado no algoritmo proposto por Uno [38]. Seja G ~ = D(G, ~ um grafo bipartido, M um emparelhamento perfeito de G e D M ) o grafo dirigido associado. Podemos obter um novo emparelhamento de G permutando arestas de M com arestas presentes em um circuito ou em caminhos M -alternantes ~ Em outras palavras, o grafo gerado pela diferença simétrica de M com um de D. emparelhamento diferente é composta por circuitos, caminhos alternantes ou vértices isolados. Como em um emparelhamento perfeito todos os vértices estão saturados, o grafo gerado pela diferença simétrica entre dois emparelhamentos perfeitos é composta apenas por circuitos alternantes e vértices isolados. Portanto, um grafo G possuirá ~ emparelhamentos perfeitos diferentes de M somente se o digrafo D(G, M ) possuir circuitos alternantes. Portanto, a ideia central do Algoritmo 9 é encontrar um cir- 25 4.3. Enumerando os emparelhamentos perfeitos Algoritmo 5: Inverte Entrada: vértice b 1 início 2 x←b 3 enquanto pai[x] 6= x faça ~ 4 Remover arco (pai[x], x) de D ~ 5 Inserir arco (x, pai[x]) em D 6 x ← pai[x] 7 fim enquanto 8 fim ~ no digrafo D(G, ~ cuito dirigido C M ) e permutar suas arestas com as arestas emparelhamento perfeito M . Essa permuta irá gerar um novo emparelhamento perfeito ~ Um modo de se encontrar um circuito dirigido em D ~ seria fazer uma M ′ = M ∆C. ~ buscando uma aresta de retorno [19]. Uma outra busca em profundidade no grafo D, ~ em componentes fortemente conexas (este assunto será abormaneira é decompor D dado na próxima seção). Este método é mais eficiente em relação ao primeiro pois é possível encontrar todos os circuitos com apenas duas buscas em profundidade. 4.3.1 Componentes fortemente conexas ~ = (V, A) é um subconjunto Uma componente fortemente conexa de um digrafo G ~ tal que, para todo par de vértices u e v em S, há um maximal de vértices S ⊆ V (G) ~ é o grafo G ~ T = (V, AT ) onde caminho de u a v e vice-versa. O grafo transposto de G AT = (u, v) : (v, u) ∈ A(G)) . A Figura 4.2 apresenta as componentes fortemente ~ conexas de um digrafo D. Os conceitos de circuito dirigido e componentes fortemente conexas estão associados, visto que um arco está em um circuito dirigido se e somente se suas extremidades estão em uma mesma componente fortemente conexa. De fato, sejam u e v vértices ~ Como u, v ∈ F , haverá e F uma componente fortemente conexa de um digrafo G. um caminho P de u até v e um caminho P ′ de v até u. Com isso, é fácil verificar Capítulo 4. Emparelhamentos em grafos bipartidos 26 que P ∪ P ′ contém um circuito. Portanto, um modo de se encontrar um circuito ~ no grafo D(G, ~ ~ em C M ) é executar um algoritmo que particiona os vértices de D componentes fortemente conexas e, em seguida, verificar quais arcos pertencem a uma mesma componente fortemente conexa. Suponha que um arco e não pertença a nenhum circuito. Podemos excluir o ~ pois ele será irrelevante para encontrarmos um emparelhamento arco e do digrafo D ~ Vamos perfeito M ′ . Neste ponto, suponha que o arco e tenha sido removido de D. analisar os dois casos possíveis para o arco e: em primeiro lugar, se e ∈ M , então e estará presente também em M ′ = M ∆C. No segundo caso, se e ∈ / M , então ~ não teve relevância para e∈ / M ′ = M ∆C. Em ambos os casos, a presença de e em D encontrarmos um novo emparelhamento perfeito M ′ . Portanto, podemos excluir e ~ sem nenhum prejuízo ao emparelhamento M ′ . de D 0 1 2 3 4 5 6 7 ~ D ~ Figura 4.2: Componentes fortemente conexas em um digrafo D. ~ mas Como não estamos apenas interessados em encontrar um circuito em D, ~ todos os arcos que não pertencem a circuitos, é também em eliminar do grafo D ~ Para tanto, optamos por utilizar um importante encontrar todos os circuitos de D. ~ em algoritmo (com custo de tempo linear, ou seja, O(|V | + |E|)) que particione D componentes fortemente conexas. Na próxima seção vamos apresentar um algoritmo ~ os que faça essa decomposição e na Seção 4.3.2 vamos explicar como remover de D 27 4.3. Enumerando os emparelhamentos perfeitos arcos que não estão em circuitos usando componentes fortemente conexas. Algoritmo para decomposição em componentes fortemente conexas O Algoritmo 6 decompõe um digrafo em suas componentes fortemente conexas5 , e depende dos Algoritmos 7 e 8, que executam uma busca em profundidade a partir de um conjunto ordenado de vértices. Algoritmo 6: ComponentesFortementeConexas ~ Entrada: Digrafo G Saída: Vetor C2 1 início ~ 2 F0 ← ordem qualquer dos vértices de V (G) ~ F0 ) 3 (F1 , C1 ) ← DFS(G, ~ T , F1 ) 4 (F2 , C2 ) ← DFS(G 5 retorna C2 6 fim Nos Algoritmos 7 e 8 temos um vetor chamado pai, que é utilizado para guardar as árvores de uma floresta de busca em profundidade. Para todo nó v em uma árvore de busca, o nó pai de v estará armazenado em pai[v]. Definimos também que os vértices ainda não explorados pela busca em profundidade recebem o valor N U LL como pai e um vértice raiz de uma árvore de busca terá a si mesmo como pai. Temos também nos Algoritmos 7 e 8 um estrutura do tipo fila, denominada F , que armazena os vértices na ordem em que eles foram explorados na busca em profundidade. Note que, na linha 10 do Algoritmo 7 e nas linhas 7 e 10 do Algoritmo 8 temos uma operação de concatenação (representada pelo símbolo ·) entre um vértice e uma lista de vértices ou entre duas listas de vértices. Observe que ao percorrer~ ordenados pelo momento mos a fila F do fim para o início, temos os vértices de G 5 Adaptado de [19] Capítulo 4. Emparelhamentos em grafos bipartidos 28 Algoritmo 7: DFS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ~ lista de vértices L Entrada: Digrafo G, Saída: Vetor C e Fila F início F ←∅ ~ elementos C ← novo vetor com |V (G)| ~ faça para todo u ∈ V (G) pai[u] ← N U LL fim para todo k←0 para todo u ∈ L faça se pai[u] = N U LL então pai[u] ← u F ← DFSVISIT(u, C) · F k ←k+1 fim se fim para todo retorna F, C fim em que foram explorados pelo algoritmo de busca em profundidade (vértices mais a esquerda tiveram sua exploração finalizada por último). Na linha 11 do Algoritmo 7, k é um contador de componentes da floresta de ~ Ao se encerrar a exploração de uma árvore na floresta de busca busca do digrafo G. em profundidade, o contador k é incrementado (observe que cada árvore na floresta da busca inicializada na linha 4 do Algoritmo 6 está associada a uma componente fortemente conexa) e, ao se iniciar a exploração em uma nova árvore enraizada por u, o Algoritmo 8 armazena no vetor C a componente fortemente conexa na qual u e seus descendentes v pertencem (veja linha 3). ~ Como todo Sejam n o número de vértices e m o número de arcos do digrafo G. vértice u deve ser explorado pela busca em profundidade, temos n chamadas ao Algoritmo 8 (muitas delas recursivas). Em cada chamada, todo vértice v ∈ N + (u) será testado. No total, o número de testes realizados será igual ao número de arcos. 29 4.3. Enumerando os emparelhamentos perfeitos Algoritmo 8: DFSVISIT Entrada: vértice u, vetor C Saída: Vértice u concatenado com a Fila F 1 início 2 F ←∅ 3 C[u] ← k 4 para todo v ∈ N + (u) faça 5 se pai[v] = N U LL então 6 pai[v] ← u 7 F ← DFSVISIT(v, C) · F 8 fim se 9 fim para todo 10 retorna u · F 11 fim O tempo total gasto para as chamadas ao Algoritmo 8 é O(m). Devemos acrescentar também o tempo gasto para inicialização do vetor pai (linhas 3 a 5 do Algoritmo 7) e o tempo gasto para se verificar se o vértice s já foi explorado pela busca em profundidade (linha 8 do Algoritmo 7). Ao todo, isso leva tempo O(n). Portanto, o tempo de execução6 do Algoritmo 7 é O(n + m). 4.3.2 Gerando novos emparelhamentos ~ serão ou Note que, após a decomposição, as componentes fortemente conexas de D ~ ou estarão em um circuito vértices isolados ou circuitos. Portanto, os arcos de D ou terão seus vértices de partida e chegada localizados em componentes fortemente conexas diferentes. O Algoritmo 11 se utiliza desta propriedade para eliminar todos os arcos que não pertencem a nenhum circuito pois, como veremos mais adiante, apenas arcos que estejam em um circuito são utilizados para se encontrar novos emparelhamentos (linha 8 do Algoritmo 9 e linhas 10 e 13 do Algoritmo 10. Em uma descrição informal, o Algoritmo 9 recebe inicialmente um emparelha~ = D(G, ~ M ) e escolhe um mento perfeito M (obtido pelo Algoritmo 3) e o digrafo D 6 Para mais explicações e verificação da corretude do algoritmo, consulte [19] Capítulo 4. Emparelhamentos em grafos bipartidos 30 ~ que também pertença a M . No próximo passo, é arco arbitrário e = (u, v) de D, ~ contendo e, e é construído um emparelhamento perfeito encontrado um circuito C, M ′ = M ∆C. Depois, o algoritmo gera dois novos digrafos: o primeiro é obtido excluindo-se os vértices u e v e os arcos que incidem neles; o segundo é obtido excluindo-se o arco e. Em ambos os digrafos obtidos, os arcos que não pertencem a um circuito são excluídos do digrafo. Por fim, o algoritmo realiza uma chamada recursiva para cada grafo gerado. Veremos com maiores detalhes o funcionamento do algoritmo após enunciá-lo. Com base nas definições acima, e no algoritmo de decomposição em componentes fortemente conexas, podemos então mostrar o Algoritmo 9, que enumera todos os emparelhamentos perfeitos em um grafo bipartido. Algoritmo 9: Enumerar Emparelhamentos Perfeitos Entrada: Grafo bipartido G 1 início 2 Encontrar um emparelhamento perfeito M de G pelo Algoritmo 3 3 se M não existe então 4 Fim 5 fim se 6 Imprimir M ~ ← D(G, ~ 7 D M) ~ 8 EliminarArestasDesnecessarias (D) ~ M ). 9 EnumEmpPerfeitosRecursivo (D, 10 fim Primeiramente o Algoritmo 9 verifica se há um emparelhamento perfeito no grafo dado utilizando o Algoritmo 3. Caso exista, inicia-se o processo de listar todos os emparelhamentos perfeitos, procurando um circuito C, M -alternante, e gerando um novo emparelhamento perfeito M ′ = M ∆C. O próximo passo é escolher um arco e que esteja tanto em M quanto no circuito C. Observe que e está em M mas não 31 4.3. Enumerando os emparelhamentos perfeitos Algoritmo 10: EnumEmpPerfeitosRecursivo ~ M Entrada: D, 1 início ~ = ∅ então 2 se D 3 Fim 4 fim se 5 Encontrar um circuito M -alternante C 6 Escolher uma aresta arbitrária e ∈ C ∩ M 7 M ′ ← C∆M 8 Imprimir M ′ ~ + e) 9 Gerar grafo D( + 10 EliminarArestasDesnecessarias(D( e )) 11 EnumEmpPerfeitosRecursivo (D( e), M ) 12 Gerar grafo D( e) 13 EliminarArestasDesnecessarias(D( e )) 14 15 + − − − EnumEmpPerfeitosRecursivo (D( e), M ′ ) fim em M ′ . No próximo passo, o algoritmo gera dois subproblemas de mesma natureza: enumerar todos os emparelhamentos perfeitos que possuem e, e todos os emparelhamentos perfeitos que não possuem o arco e. Vamos analisar primeiramente o segundo caso: para enumerar os emparelhamentos perfeitos que não contém e, con~ e em seguida o ~ − e) que é obtido removendo-se o arco e de D, sideramos o digrafo D( − Algoritmo 10 chama-se a si mesmo, passando como parâmetro o digrafo D( e) e o emparelhamento perfeito M ′ . Já no primeiro caso, para enumerar os emparelhamentos + ~ perfeitos que possuem e, consideramos o grafo D( e) que é obtido removendo-se de D as extremidades de e, e todos os arcos que partem ou chegam nelas. Note que todo + emparelhamento perfeito de D( e) corresponde a um único emparelhamento perfeito ~ que contém e. Portanto, para se enumerar os emparelhamentos perfeitos que de D contém e, o Algoritmo 10 executa uma chamada recursiva, tendo como parâmetros + + D( e) e M . Note que um emparelhamento perfeito de D( e) não conterá o arco e, Capítulo 4. Emparelhamentos em grafos bipartidos 32 Algoritmo 11: EliminarArestasDesnecessarias ~ Entrada: D 1 início ~ para todo (u, v) ∈ A(D) ~ 2 cf c ← ComponentesFortementeConexas (D) faça 3 se cf c(u) 6= cf c(v) então ~ ← A(D) ~ \ (u, v) 4 A(D) 5 fim se 6 fim para todo 7 fim porém e estará presente em M (que foi passado como parâmetro na chamada recursiva). Portanto, todo emparelhamento perfeito obtido pela diferença simétrica de + M com um circuito dirigido de D( e) é um emparelhamento perfeito do grafo G que contém e. 4.3.3 Análise de complexidade Nesta seção, vamos analisar a complexidade de tempo para enumerar todos os emparelhamentos perfeitos de um grafo bipartido. Primeiramente, vamos analisar a complexidade do Algoritmo 3, que encontra um emparelhamento máximo em um grafo bipartido G = (V1 ∪ V2 , E). Sejam n = |V1 ∪ V2 | e m = E. Cada execução das linhas 8 a 11 corresponde a um busca em profundidade no grafo, o que leva tempo O(n + m). Para se inicializar o vetor pai, gasta-se tempo O(n). Já o Algoritmo 5, no pior caso, levará tempo O(n), pois um caminho terá, no máximo, n vértices. Temos, no máximo, |V2 | chamadas ao Algoritmo 5, pois no pior caso todos os caminhos a serem invertidos possuirão apenas uma aresta {a ∈ V1 \ S, b ∈ V2 \ S}. Cada iteração no laço das linhas 4 a 17 levará, no pior caso, tempo O(n2 + n + |V2 |) (um grafo bipartido terá, no máximo, n2 arestas). Teremos, no máximo, n/2 iterações no laço das linhas 4 a 17, pois inicialmente S pode ser vazio, e no pior caso, apenas 2 vértices são inseridos 33 4.3. Enumerando os emparelhamentos perfeitos em S a cada iteração (linha 15). Portanto, o Algoritmo 3 demandará um tempo O(n/2(n2 + n + |V2 |)) = O(n3 ). No Algoritmo 10, as operações de eliminar arestas desnecessárias e gerar os digra+ − fos D( e) e D( e) levam tempo O(n). Portanto, o tempo de execução do Algoritmo 10 é proporcional a O(n3 + nk), onde k é o número de emparelhamentos perfeitos. Há √ um algoritmo mais rápido, que demanda tempo O = ( n(n + m)) [14], porém não utilizamos devido a complexidade de implementação e também porque ele não melhoraria muito a velocidade de execução na prática do algoritmo para o Problema do Isomorfismo de Grafos proposto nesta dissertação. CAPÍTULO 5 Algoritmo para o Problema do Isomorfismo de Grafos Neste capítulo será apresentado o algoritmo que desenvolvemos para o Problema do Isomorfismo de Grafos, baseado em um processo de simulação sobre os vértices de um grafo G. O objetivo destas simulações é encontrar partições do conjunto de vértices de G, que servirão de base para decidir se dois grafos G e H são isomorfos. O algoritmo é dividido em três partes principais: a primeira consiste em executar p-simulações em G e H, aplicando-se uma função f sobre seus respectivos conjuntos de vértices. Ao final desta etapa, os vértices estarão agrupados em classes, e os vértices de uma determinada classe compartilham as mesmas propriedades entre si (grau, padrão de vizinhança). Na segunda etapa, construímos um grafo bipartido R = (V (G) ∪ V (H), E), onde o conjunto de arestas representa a compatibilidade entre os vértices de G e H. Esse grafo R é construído de acordo com o particionamento de V (G) e V (H) obtidos na etapa anterior. Capítulo 5. Algoritmo para o Problema do Isomorfismo de Grafos 36 A terceira etapa consiste em encontrar emparelhamentos perfeitos de R e verificar se um emparelhamento perfeito M corresponde a um isomorfismo ϕ(V (G)) → V (H). 5.1 Simulação Como definimos na Seção 2.1.2, o conjunto NG (v) é também chamado de vizinhança aberta de um vértice v no grafo G. Agora vamos definir NG [v] = NG (v) ∪ {v} como a vizinhança fechada 1 de v em G. Um multiconjunto é definido como um par (X, m), onde X é um conjunto qualquer e m : X → N∗ uma função que associa cada elemento de X a um inteiro positivo. Denotamos por M a família de todos os multiconjuntos finitos de N. Fixamos um grafo G = (V, E), uma função f : M → N e um inteiro positivo k. Para um vértice p ∈ V , que será denominado pivô, uma p-simulação é uma sequência de vetores x(1) , x(2) , . . . , x(k) com entradas em N indexados por V que são definidos (1) indutivamente pela seguinte relação. Para i = 1, atribua xv = 1 se v = p ou (1) xv = 0 se v 6= p. Para i > 1, defina (i−1) x(i) : w ∈ NG [v]} . v = f {xw Por conveniência, suponha que x(0) é um vetor nulo. Exemplo A Figura 5.1 traz o exemplo de uma p-simulação no grafo da Figura 5.2, para k = 8, com a função f dada por f (M ) = X m. m∈M 1 Suprimiremos o subscrito quando o grafo estiver subentendido no texto. 37 5.1. Simulação x(0) x(1) 0 1 0 p 0 x(2) 0 1 0 p 0 0 0 1 0 0 0 0 0 0 0 0 0 x(3) 0 0 7 2 p 2 25 9 p 2 9 28 10 6 0 26 2 x(6) 10 0 x(7) 81 105 92 1031 370 370 1359 p 1359 350 48 1288 198 92 794 1274 348 12 2 x(8) 291 p 105 p 46 26 2 2 0 28 p 6 0 0 x(5) x(4) 3 1 p 186 60 732 258 Figura 5.1: Simulação em um grafo G 5.1.1 Efeitos de uma p-simulação (i) Sabemos que, na i-ésima rodada de uma p-simulação, xv é o valor atribuído pela função f ao vértice v. Antes do inicio da p-simulação, todos os vértices possuem valor 0, não sendo possível distinguir os vértices entre si. Na primeira rodada, temos que o valor de p é 1 e o valor dos demais vértices é 0. Com isso, podemos dizer que a primeira rodada de uma p-simulação distingue o vértice pivô dos demais vértices do grafo. Conforme o número de rodadas vai aumentando, mais vértices podem ser Capítulo 5. Algoritmo para o Problema do Isomorfismo de Grafos 38 distinguidos uns dos outros através dos valores atribuídos pela função f . Assim, se (i) (i) na i-ésima rodada os valores de u e v forem diferentes (isto é, se xu 6= xv ), dizemos que u é distinguível de v, e estes vértices permanecem distinguíveis até o final da simulação (mesmo que em alguma rodada posterior eles voltem a ter o mesmo valor). Desse modo, podemos associar uma partição dos vértices do grafo a cada rodada de uma p-simulação, onde vértices são agrupados de acordo com seus respectivos valores naquela rodada. Antes de formalizar o conceito de uma partição associada a uma rodada vamos definir partição e alguns conceitos relacionados. 5.1.2 Partições Uma partição de V é uma família de subconjuntos disjuntos não vazios de V , onde a união de todos eles é o próprio conjunto V . Uma partição ordenada de V é uma sequência (V1 , V2 , . . . , Vr ) tal que {V1 , V2 , . . . , Vr } é uma partição de V . O conjunto de todas as partições de V e o conjunto de todas as partições ordenadas de V são ′ denotados por P(V ) e P (V ), respectivamente. ′ Seja π ∈ P(V ) ∪ P (V ) uma partição (ordenada ou não). Cada membro de π é chamado de célula. Dizemos que uma célula em π é trivial se tiver apenas um elemento. Se todas as células de π forem triviais, então π é chamada de partição discreta, enquanto que, se π possuir apenas uma célula, então π é chamada de partição unitária. Duas partições ordenadas (V1 , V2 , . . . , Vr ) e (W1 , W2 , . . . , Ws ) são compatíveis se r = s e, para todo índice i entre 1 e r, vale que |Vi | = |Wi |. Seja π = (V1 , V2 , . . . , Vr ) uma partição ordenada de V . Para cada x ∈ V definimos idx(x, π) = i, onde x ∈ Vi . Sejam π1 e π2 duas partições. Dizemos que π1 é um refinamento de π2 se toda célula de π1 for um subconjunto de alguma célula de π2 . Dizemos que (W1 , . . . , Ws ) é um refinamento ordenado de (V1 , . . . , Vr ) se: 39 5.1. Simulação (i) {W1 , . . . , Ws } for um refinamento de {V1 , . . . , Vr }; (ii) se Wi ⊆ Va , Wj ⊆ Vb e a < b, então i < j. 5.1.3 Partição associada a uma rodada (i) Vamos definir agora, para cada i ∈ {0, 1, . . . , k}, uma partição ordenada ψG,p que será a partição associada a x(i) , ou seja, a partição associada à i-ésima rodada de (i) uma p-simulação. Em ψG,p , temos que u está na mesma célula que v se e somente se (j) x(j) u = xv , (5.1) (i) para todo j, com 1 ≤ j ≤ i. Para definir a ordem das células em ψG,p , sejam u e v vértices em células distintas, e seja j, com 1 ≤ j ≤ i, o primeiro índice que viola a (i) condição (5.1). Nesse caso, a célula de u precede a célula de v em ψG,p se e somente (j) (j) (i) se xu < xv . Observe que, para todo i ∈ [k], ψG,p é um refinamento ordenado de (i−1) ψG,p . Proposição 1. Se a função f satisfaz f ({0, . . . , 0}) = 0 e f ({1, 0, 0, . . . , 0}) 6= 0 (2) então a partição ψG,p possui exatamente três células, a saber: (2) ψG,p = NḠ (p), NG (p), {p} . (1) Demonstração. Pela definição de simulação, temos que ψG,p terá apenas o vértice p distinguido dos demais, porque o vetor x(1) possuirá o valor 0 em todas as posições, (1) exceto em xp , que terá o valor 1. Seja Miv um multiconjunto formado pelos valores do vetor x(i) nas posições dadas pelos vértices de NG [v], para v ∈ V (G). Na segunda rodada, o vetor x(2) terá a seguinte configuração: a posição p terá o valor 1, pois em x(1) , todas as posições (1) relativas aos vizinhos de p possuem valor 0, e xp possui valor 1. Logo, M1p terá um Capítulo 5. Algoritmo para o Problema do Isomorfismo de Grafos 40 único elemento 1, e os demais serão 0. Com isso, f (M1p ) 6= 0 por hipótese. Agora vamos analisar a aplicação da função nos vizinhos de p. Para todo u ∈ NG (p), o multiconjunto M1u também será formado por um único elemento 1, e os demais 0. Isso ocorre pois o único vizinho de u que possui o valor 1 em sua respectiva posição no vetor x(1) é p. Os demais terão o valor 0 em suas posições em x(1) . Também por hipótese, f (M1u ) 6= 0. Para todo o vértice w ∈ NḠ (p), o multiconjunto M1w conterá apenas elementos 0, pois todas as posições relativas aos vértices da vizinhança fechada de w possui o valor 0 em x(1) . Portanto, f (M1w ) = 0 por hipótese. (2) Com isso, o vetor x(2) apresentará a seguinte configuração: xp (2) = 1, xu = 1, (2) (2) para todo u ∈ NG (p), e xw = 0, para todo w ∈ NḠ (p). Note que, apesar de xp (2) e xu poderem possuir os mesmos valores, o vértice p já foi distinguido na primeira (2) (2) rodada. Com isso, a partição ψG,p possuirá três células: ψG,p [1] contendo os vértices (2) (2) que não são vizinhos de p, ψG,p [2], com os vértices vizinhos de p e ψG,p [3] contendo apenas o vértice p, o que encerra a prova. 5.1.4 Algoritmo para uma p-simulação Antes de apresentarmos o algoritmo para uma p-simulação, é necessário algumas definições: dada a partição ordenada π, denotamos por π[i] a i-ésima célula de π. O símbolo · denota a operação de concatenação de duas partições, que é definida por: π · σ = (π[1], π[2], . . . , π[r], σ[1], σ[2], . . . , σ[s]), onde r = |π| e s = |σ|. Sejam π e σ duas partições compatíveis. Dizemos que duas células π[i] e σ[j] são correspondentes se i = j. Seja X uma célula de uma partição, denotamos por Xhji o j-ésimo menor elemento de X. O Algoritmo 12 formaliza os passos envolvidos em uma p-simulação2 . (k) O Algoritmo 13 subdivide uma célula Z, da partição ψG,p de acordo com os 2 O conceito de partição igualitária será abordado na Seção 5.3 41 5.1. Simulação Algoritmo 12: Simulação Entrada: G, p, f : M → N (k) Saída: Célula ψG,p 1 início 2 k←1 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 (1) ψG,p ← V (G) \ {p} · {p} para todo v ∈ V (G) faça (1) xv ← 0 fim para todo (1) xp ← 1 repita ψ̃ ← () (k) t ← |ψG,p | para todo v ∈ V (G) faça (k) (k−1) xv ← f {xw : w ∈ NG [v]} fim para todo para i de 1 até t faça (k) ψ̃ ← ψ̃· RefinaCelula(ψG,p [i], x(k) ) fim para (k) ψG,p ← ψ̃ k ←k+1 (k) até ψG,p seja igualitária; (k) retorna ψG,p fim valores obtidos através da aplicação de uma função f em NG (z), para todo z ∈ Z. Estes valores são armazenados no vetor x(k) , na posição z. O primeiro passo é ordenar os vértices de Z de acordo com os valores que eles possuem no vetor x(k) , de modo crescente. Este passo é realizado para otimizar o processo de verificação de quais vértices possuem valores iguais no vetor x(k) (pela equação 5.1 dois vértices (k) a, b estão em uma mesma célula se e somente se xa (k) = xb ). O próximo passo do algoritmo é realizar uma varredura no vetor x(k) , inserindo os vértices em suas (k) respectivas células. Uma nova célula é criada sempre que o valor de xs for diferente (k) de seu antecessor xs−1 . Capítulo 5. Algoritmo para o Problema do Isomorfismo de Grafos 42 Algoritmo 13: RefinaCelula 1 2 3 4 5 6 7 8 9 10 11 12 13 Entrada: Célula Z, vetor x(k) Saída: Partição π início (k) Ordenar Z conforme o valor de xz para cada z ∈ Z, em ordem crescente j←1 π[1] ← {Zh1i} para s de 2 até |Z| faça (k) (k) se xZ<s−1> 6= xZhsi então j ← j +1 π[j] ← ∅ fim se π[j] ← π[j] ∪ {Zhsi} fim para retorna π fim 5.1.5 Árvore de partição Uma árvore de partição TG,p é construída a partir das partições de V associadas a cada rodada de uma p-simulação em G. O conjunto de nós dessa árvore é a (i) união disjunta de todas as partições ψG,p , para i ∈ {0, 1, . . . , k}. A raiz de TG,p é (0) (i) o conjunto V (que é uma célula da partição ψG,p ) e um nó A ∈ ψG,p é filho de um (i−1) nó C ∈ ψG,p se A ⊆ C. Ademais, se dois nós A e B no nível i são filhos de um (i) (i) mesmo nó C, então A encontra-se à esquerda de B se xa < xb para algum a ∈ A e (i) (i) algum b ∈ B; e A encontra-se à direita de B se xa > xb para algum a ∈ A e algum b ∈ B. Isto ocorre pois no refinamento de uma célula, os vértices são ordenados de forma crescente (linha 2 do Algoritmo 13). Por fim, temos que a partição associada (k) à última rodada da p-simulação será denotada por ψG,p , ou seja, ψG,p = ψG,p . Exemplo A Figura 5.3 traz a árvore de partição para a p-simulação mostrada na Figura 5.1. A Figura 5.2 abaixo mostra o grafo de exemplo com seus vértices rotulados. 43 5.1. Simulação a p g b f e c d Figura 5.2: Grafo G rotulado (0) ψG,p Partição Unitária {a, b, c, d, e, f, g} ψG,p {a, g} {p} ψG,p {b, f } {a, g} {p} ψG,p {b, c, d, e, f } {c, d, e} (1) {p} (2) (3) (4) {d} {c, e} {b, f } {a, g} {p} ψG,p {d} {c, e} {b, f } {a, g} {p} ψG,p {b, f } {a, g} {p} ψG,p (5) (6) {d} {e} {c} {d} {e} {c} {f } {b} {a, g} {p} ψG,p {d} {e} {c} {f } {b} {a, g} {p} ψG,p Figura 5.3: Árvore de partição. (7) (8) Capítulo 5. Algoritmo para o Problema do Isomorfismo de Grafos 5.2 44 Propriedades de uma p-simulação Lema 1. Seja ϕ um isomorfismo entre grafos G e H, e seja v ∈ V (G) um vértice qualquer. Então NH [ϕ(v)] = ϕ(NG [v]). Demonstração. Seja v como no enunciado e w ∈ NG (v) um vértice qualquer. Se {v, w} ∈ E(G), então {ϕ(v), ϕ(w)} ∈ E(H). Portanto ϕ(w) ∈ NH (ϕ(v)). Como w é qualquer, fica estabelecido que NH (ϕ(v)) ⊇ ϕ(NG (v)). Reciprocamente, seja u ∈ NH (ϕ(v)) um vértice qualquer. Como ϕ é sobrejetora, existe w ∈ V (G) com ϕ(w) = u. Com isso temos {ϕ(w), ϕ(v)} ∈ E(H) ⇒ {v, w} ∈ E(G) ⇒ w ∈ NG (v). Conclui-se que u = ϕ(w) ∈ ϕ(NG (v)). Como u é qualquer, fica estabalecida a inclusão NH (ϕ(v)) ⊆ ϕ(NG (v)). Para concluir a prova do lema, observe que ϕ(v) ∈ NH [ϕ(v)] ∩ ϕ(NG [v]). A seguir, apresentamos a propriedade fundamental de uma p-simulação. Teorema 2. Sejam G ∼ = H dois grafos, seja ϕ um isomorfismo entre G e H, e seja ψG,p = (V1 , . . . , Vr ) a partição associada a uma p-simulação em G, para algum p ∈ V (G). Nessas condições, vale que ψH,ϕ(p) = (ϕ(V1 ), . . . , ϕ(Vr )), (5.2) onde ϕ(Vi ) = {ϕ(v) : v ∈ Vi }. Demonstração. Sejam G, H, ϕ e p conforme o enunciado do teorema. Seja q = ϕ(p). Seja x(1) , . . . , x(k) uma p-simulação em G, e seja y (1) , . . . , y (k) uma q-simulação em 45 5.2. Propriedades de uma p-simulação H. Vamos primeiro mostrar, por indução em i, que (i) ∀ v ∈ V (G) : yϕ(v) = x(i) v . (5.3) (1) (1) Quando i = 1, pela definição de p-simulação, temos xv = 1 se v = p e xv = 0 (1) se v 6= p. Pela definição de q-simulação, temos yϕ(v) = 1 se ϕ(v) = q (isto é, se (1) v = p) e xϕ(v) = 0 caso contrário. Assim, segue que (5.3) vale para i = 1. Suponha então que i > 1, e suponha que (5.3) valha com i − 1 no lugar de i. Nesse caso, temos (i) yϕ(v) = f {yu(i−1) : u ∈ NH [ϕ(v)]} (i−1) = f {yϕ(w) : w ∈ ϕ−1 (NH [ϕ(v)]} (i−1) = f {yϕ(w) : w ∈ ϕ−1 (ϕ(NG [v]))} (i−1) = f {yϕ(w) : w ∈ NG [v]} = f {x(i−1) : w ∈ NG [v]} w = x(i) v . (i) A primeira igualdade segue da definição de yϕ(v) , a segunda do fato de que ϕ é um isomorfismo, a terceira segue do Lema 1, a quinta segue da hipótese de indução (i) e a sexta da definição de xv . Portanto (5.3) é verdade. Agora temos que mostrar (5.2). Sejam u, v ∈ V (G) vértices quaisquer. Se (j) (j) u, v ∈ Vℓ para algum ℓ, então xu = xv para todo j ∈ {0, 1, . . . , k}. Usando (5.3), (j) (j) temos que yϕ(u) = yϕ(v) para todo j ∈ {0, 1, . . . , k}, o que implica que ϕ(u) e ϕ(v) pertencem à mesma célula de ψH,ϕ(p) . Isso mostra que {ϕ(V1 ), . . . , ϕ(Vr )} é um refinamento de ψH,ϕ(p) . Um argumento simétrico mostra que ψH,ϕ(p) é um refinamento de {ϕ(V1 ), . . . , ϕ(Vr )}. Portanto, as células dessas partições são as mesmas. Por se tratar de uma identidade de partições ordenadas, resta ainda mostrar Capítulo 5. Algoritmo para o Problema do Isomorfismo de Grafos 46 que a ordem relativa das células de ψH,ϕ(p) e (ϕ(V1 ), . . . , ϕ(Vr )) é a mesma. Para (ℓ) (ℓ) isso, suponha que u ∈ Vi e v ∈ Vj com i < j. Isso implica que xu < xv (m) algum ℓ ≤ k e xu (m) = xv para para todo m entre 0 e ℓ − 1. Mas então, por (5.3), (m) (m) (ℓ) (ℓ) vale que ∀m ∈ {0, . . . , ℓ − 1} temos yϕ(u) = yϕ(v) e yϕ(u) < yϕ(u) . Isso implica que idx(ϕ(u), ψH,ϕ(p) ) < idx(ϕ(v), ψH,ϕ(p) ) e portanto a ordem das classes também é respeitada. O Teorema 2 permite que p-simulações sejam usadas para resolver o Problema do Isomorfismo de Grafos, pois a célula em que um dado vértice é colocado, ao final de uma p-simulação, independe do nome inicialmente dado a ele. É por esse motivo que, se dois grafos G e H são isomorfos e se q ∈ V (H) é a imagem de um vértice p ∈ V (G) por algum isomorfismo em Iso(G, H), então uma p-simulação em G e uma q-simulação em H fazem a mesma classificação dos vértices. Essa idéia é resumida no enunciado a seguir. Corolário 1. A partição ψG,p independe dos nomes dos vértices do grafo. 5.3 Algoritmo proposto Denotamos por L(G) o conjunto L(G) = {ψG,p : p ∈ V (G)}. Nesta seção, propomos o Algoritmo 14, que decide se dois grafos G e H são isomorfos. Vamos supor que V (G) ∩ V (H) = ∅. Este algoritmo tem como entrada os grafos G e H, e como saída sim se os grafos são isomorfos, e não caso contrário. Podemos dividir o algoritmo em duas etapas principais: na primeira, fazemos p-simulações para todo vértice p ∈ V (G) e, ao final 47 5.3. Algoritmo proposto Algoritmo 14: Verifica se dois grafos são isomorfos Entrada: Grafos G e H Saída: sim se G e H são isomorfos, não caso contrário 1 início 2 π←∅ 3 para todo p ∈ V (G) faça 4 ψG,p ← Simulação(G, p, f ) 5 se ψG,p é discreta então 6 π ← ψG,p 7 Sai do laço 8 senão 9 L(G) ← L(G) ∪ {ψG,p } 10 11 12 13 14 15 16 17 18 se π = 6 ∅ então {u1 }, {u2 }, . . . , {un } ← π para todo q ∈ V (H) faça fazer q-simulação em H para obter ψH,q se ψH,q é discreta então {v1 }, {v2 }, . . . , {vn } ← ψH,q ϕ ← mapeamento que leva ui 7→ vi se ϕ é um isomorfismo de G em H então retorna sim retorna não 19 20 21 22 23 24 25 26 27 senão para todo q ∈ V (H) faça fazer q-simulação em H para obter ψH,q L(H) ← L(H) ∪ {ψH,q } Construir grafo bipartido R = (V (G) ∪˙ V (H), E = ∅) para todo {p, q} ∈ E(R) faça se ψG,p for compatível com ψH,q então E(R) ← E(R) ∪ {p, q} M ← Emparelhamento perfeito obrido pelo Algoritmo 3 ~ ← D(R, ~ D M) ~ M) EnumerarCandidatoIsomorfismo(D, retorna não 28 29 30 31 32 fim desta etapa, temos o conjunto L(G) que contém todas as partições ψG,p . A segunda etapa do algoritmo é a verificação do isomorfismo. Esta etapa subdivide-se em dois Capítulo 5. Algoritmo para o Problema do Isomorfismo de Grafos 48 casos: 1. existe vértice u ∈ V (G) tal que ψG,u é a partição discreta; 2. ψG,u não é discreta para nenhum vértice u. No primeiro caso, é necessário fazer q-simulações para todo vértice q ∈ V (H) e verificar, para toda partição ψH,q que for discreta, se o mapeamento que leva ψG,u nos vértices correspondentes de ψH,q (preservando a ordem das células) é um isomorfismo entre G e H. Se algum desses mapas for um isomorfismo, o algoritmo para e devolve sim. Caso contrário o algoritmo devolve não. No segundo caso, considera-se um grafo bipartido R = (V (G)∪V (H), E), inicialmente vazio (sem nenhuma aresta entre V (G) e V (H)). Para cada par não ordenado {p, q} de V (G) × V (G), acrescenta-se a aresta {p, q} se as partições ψG,p e ψH,q são compatíveis3 . Caso contrário, não pode haver um isomorfismo que mapeie p ∈ V (G) a q ∈ V (H). Portanto, neste caso, a aresta {p, q} não é inserida em R. Dados v ∈ V e W ⊂ V , definimos dG (v, W ) como o número de elementos de W ′ que são adjacentes a v em G. Dizemos que π ∈ P(V ) ∪ P (V ) é uma partição igualitária com relação ao grafo G (ou G-igualitária) se, para todo par de células V1 , V2 ∈ π (não necessariamente distintas) e para quaisquer elementos x, y ∈ V1 , temos que dG (x, V2 ) = dG (y, V2 ). O Teorema 3 mostra que, se uma partição Gigualitária for encontrada, então a árvore de partições estabilizará. A Figura 5.4 mostra um exemplo de partição G-igualitária e não igualitária: na partição π, tomamos as célula V1 = {0, 1} e V2 = {2, 3}. A partição π não é igualitária, pois dG (0, V2 ) = 0 e dG (1, V2 ) = 1. 3 Como visto anteriormente, duas partições são compatíveis se ambas possuírem o mesmo número de células, e se todas as células correspondentes possuírem o mesmo número de elementos 49 5.3. Algoritmo proposto π não é igualitária. σ é igualitária. 0 0 1 5 1 5 6 6 2 4 2 4 3 3 Figura 5.4: Exemplos de partições. Teorema 3. Uma árvore de partições Tp se estabiliza ao encontrar uma partição G-igualitária. (k) Demonstração. Seja π a partição ψG,p e i = idx(v, π) para todo v ∈ V (G). Definimos por dyj o grau que um vértice qualquer da célula π[y] possui dentro da célula π[j]. (k) Observe que d(v, π[j]) = dij . Definimos por xi (k+1) Pela definição de xv (k) o valor de xu para algum u ∈ π[i]. temos: = f {x(k) x(k+1) v u : u ∈ N [v]} (k) = f {xidx(u,π) : u ∈ N [v]} =f [ n j=1 (k) dij {xj } Observe que a fórmula à direita do símbolo = independe de v, mas apenas de (k+1) i = idx(v, π), que é igual para todo vértice em π[i]. Portanto, xv todo v ∈ π[i]. (k) (k) (k+1) é igual para (k) Logo, se ψG,p é G-igualitária, então ψG,p [j] = ψG,p [j],∀j ∈ {0, . . . , |ψG,p |}. Capítulo 5. Algoritmo para o Problema do Isomorfismo de Grafos 5.3.1 50 Emparelhamentos perfeitos e isomorfismo Após a construção do grafo bipartido R, o algoritmo verifica se os emparelhamentos perfeitos de R podem corresponder a um isomorfismo. Como vimos na Seção 4.1, o algoritmo de Uno [38] lista todos os emparelhamentos perfeitos em um grafo bipartido de forma recursiva, onde em um ramo da recursão é fixada uma aresta arbitrária e em um emparelhamento, e no outro ramo e é retirado do emparelhamento perfeito. Como o objetivo do nosso algoritmo é encontrar um isomorfismo entre os grafos G e H, ao escolher uma aresta e, podemos ignorar os emparelhamentos perfeitos que não correspondem a um isomorfismo. Para tanto, modificamos o Algoritmo 10 de modo que arestas cujas extremidades estão em células não correspondentes não se~ + e). jam inseridas em D( Veja o exemplo: dados dois grafos G e H, um isomorfismo ϕ : V (G) → V (H) e as arestas a = {p, q}, f = {u, v} ∈ R. Suponha que ϕ(p) = q. Se ϕ(u) ∈ ψH,q [i] e v ∈ ψH,q [j], com i 6= j, então ϕ 6= v. Portanto, a aresta f não estará presente no ~ + e) pois não há um isomorfismo que leva u em v. digrafo D( D + − D(e1 ) + + + − D(e1 e2 ) + + + D(e1 e2 e3 ) D(e1 ) − + D(e1 e2 ) + + − D(e1 e2 e3 ) + − + D(e1 e2 e3 ) − − D(e1 e2 ) + − − D(e1 e2 e3 ) − + + D(e1 e2 e3 ) D(e1 e2 ) − + − D(e1 e2 e3 ) − − + D(e1 e2 e3 ) − − − D(e1 e2 e3 ) Figura 5.5: Exemplo de árvore de recursão do Algoritmo 15. Assim, reescrevemos o Algoritmo 10 de modo que as arestas de R cujas extremidades estejam em células não correspondentes em duas partições compatíveis ψG,p ~ + e). e ψH,q não estejam presentes no digrafo D( Na primeira chamada do Algoritmo 15 é passado o digrafo D(R, M ), onde M foi + obtido pelo Algoritmo 3. O grafo D( e) é obtido eliminando-se as pontas da aresta 51 5.3. Algoritmo proposto − e e todas as arestas não correspondentes. Já o grafo D( e) é obtido deletando-se a aresta e. Algoritmo 15: EnumerarCandidatoIsomorfismo Entrada: D, M 1 início 2 se M ′ corresponde a um isomorfismo então 3 para e imprime isomorfismo entre G e H 4 fim se 5 se E(D) = ∅ então 6 Fim 7 fim se 8 Encontrar um circuito C M -alternante 9 Escolher uma aresta arbitrária e ∈ C ∩ M 10 M ′ ← C∆M + 11 Gerar grafo D( e) 12 EliminarArestasDesnecessarias (D( e)) 13 EnumerarCandidatoIsomorfismo (D( e), M ) 14 Gerar grafo D( e) 15 EliminarArestasDesnecessarias (D( e)) 16 17 + + − − − EnumerarCandidatoIsomorfismo (R( e), M ′ ) fim + Como visto na Seção 4.1, todo emparelhamento perfeito obtido em D( e) pode ser estendido a R adicionando-se a aresta e. Já o emparelhamento perfeito obtido − através de D( e) é também um emparelhamento perfeito de R. CAPÍTULO 6 Algoritmo Pratical Graph Isomorphism Neste Capítulo descreveremos o funcionamento do algoritmo utilizado no programa Nauty, proposto por McKay [25]. O algoritmo Pratical Graph Isomorphism (PGI) é baseado em uma rotulação canônica e no grupo de automorfismos de um grafo. Uma rotulação canônica pode ser entendida como um processo que recebe um grafo e devolve uma rotulação dos vértices desse grafo, sempre produzindo a mesma saída quando as entradas são grafos isomorfos. A ideia central do PGI é comparar as rotulações canônicas de G e H e caso sejam iguais, os grafos G e H são isomorfos. Caso contrário, o PGI retorna não-isomorfos. Para encontrar uma rotulação canônica, o PGI se divide em duas partes principais: o procedimento de refinamento e o procedimento de busca. Em linhas gerais, o procedimento de refinamento tem como entrada uma partição qualquer π e como retorno uma partição igualitária dos vértices de G que é um refinamento de π. O pro- Capítulo 6. Algoritmo Pratical Graph Isomorphism 54 cedimento de busca é um algoritmo recursivo que recebe uma partição G-igualitária e introduz uma assimetria nesta partição para uma subsequente execução do procedimento de refinamento. Este processo recursivo é repetido até se encontrar todas1 as partições discretas de V (G). A cada partição discreta obtida pelo procedimento de busca é associado um grafo, e a rotulação será um destes grafos, escolhido segundo uma ordem pré-estabelecida. Nas próximas seções, mostraremos o funcionamento da etapa de refinamento e do algoritmo recursivo do procedimento de busca. 6.1 Rotulação canônica e isomorfo canônico Nesta seção, definiremos o conceito de isomorfo canônico. Para tanto, precisamos introduzir alguns conceitos iniciais: seja G o conjunto de todos os grafos sobre o conjunto de vértices {1, . . . , n}. Uma rotulação canônica é uma função C : G → G tal que, para todo par de grafos isomorfos G e H pertencentes a G, vale C(G) = C(H). Para um dado grafo G, o grafo C(G) é chamado isomorfo canônico. Seja π uma partição discreta de V = {1, 2, . . . , n}. Definimos γπ ∈ Sn como uma permutação tal que γπ (v) = idx(v, π). Denotamos por γπ (G) o grafo obtido através da permutação γπ em V (G). Seja P = {x1 , y1 }, . . . , {xt , yt } o conjunto de pares V 2 . Podemos representar o grafo G por um vetor característico χE(G) , de comprimento t = n(n − 1)/2, que possui o valor 1 na posição j se o par {xj , yj } ∈ E(G) e 0 caso contrário. Dados grafos G e H, definimos G H se χE(G) ≤ χE(H) lexicograficamente. Para encontrar um isomorfo canônico, o PGI refina uma partição dos vértices de G, de modo a encontrar uma partição discreta de V (G). Caso isso não seja possível, o PGI constrói uma árvore de busca na qual todas as folhas corresponderão a uma 1 Este processo não lista todas as n! partições discretas, mas apenas aquelas que terão utilidade para se encontrar a rotulação canônica. 55 6.2. Refinamento de partições partição discreta π de V (G). O isomorfo canônico C(G) então será o maior grafo γπ (G), de acordo com a ordem de , onde π é uma partição discreta associada a uma folha da árvore de partições de G. Em seguida, o PGI executa os mesmos passos para calcular C(H) e verifica o isomorfismo entre G e H comparando os isomorfos canônicos C(G) e C(H), pois G e H serão isomorfos se e somente se C(G) = C(H). A comparação dos isomorfos canônicos é muito eficiente, pois basta comparar o conjunto de arestas destes grafos, que demanda um tempo O(n2 ). Na próxima seção, mostraremos como o PGI executa o procedimento de refinamento. 6.2 Refinamento de partições O objetivo principal da etapa de refinamento é encontrar uma partição G-igualitária que seja um refinamento de uma determinada partição π. A seguir, mostraremos como este procedimento é realizado. Para uma partição ordenada π de V (G), existe um conjunto Z(π) de partições ordenadas de V (G) que são refinamentos de π e, ao mesmo tempo, partições Gigualitárias.2 Note que Z(π) 6= ∅, pois a partição discreta é sempre G-igualitária e é um refinamento de π. É possível argumentar3 que existe uma partição ζ(π) ∈ Z(π) que satisfaz σ ≤ ζ(π) para toda σ ∈ Z(π). Em outras palavras, existe uma única partição que é um refinamento G-igualitário de π com o número mínimo possível de células, denominada ζ(π). A estratégia do PGI é encontrar a partição ζ(π) a partir de uma partição ordenada π. Mas para determinados tipos de grafos (por exemplo, os grafos vérticetransitivos), a partição ζ(π) não será discreta. Isso torna-se uma dificuldade para o PGI, pois como vimos, o isomorfo canônico está associado a uma partição dis2 3 Neste capítulo, omitiremos as provas. Para detalhes, consulte o artigo [25] Seção 2.3 de [25] Capítulo 6. Algoritmo Pratical Graph Isomorphism 56 creta de V (G). Para resolver este problema, o PGI cria novas partições ordenadas σ1 , σ2 , . . . , σm (onde m é o tamanho da maior célula de ζ(π)), refina cada uma destas novas partições ordenadas obtidas e executa este processo sucessivamente até encontrar partições discretas de V (G) (esta etapa será explicada nas Seções 6.4 e 6.5). A seguir, apresentamos o algoritmo que executa o procedimento de refinamento para uma partição ordenada π. 6.3 Algoritmo de refinamento O objetivo principal do Algoritmo 16 é encontrar uma partição G-igualitária que seja um refinamento da partição π passada como argumento. Para tanto, o Algoritmo 16 de refinamento recebe, além do grafo G, uma partição ordenada π ∈ V (G) e uma sequência α = (W1 , W2 , . . . , WM ) de células distintas de π, que respeita a ordem de π, e tem como saída uma partição ordenada, denominada R(G, π, α), de V (G). O processo de refinamento consiste, a grosso modo, em verificar o número de vizinhos que os vértices de uma célula de π possuem em relação a algum elemento de α. Esta verificação será abordada mais adiante, ainda nesta seção. Dado que antes da execução do Algoritmo 16, a única partição de V (G) conhecida é a partição unitária π = V (G), então, inicialmente, o Algoritmo 16 receberá como argumentos o grafo G, a partição unitária π e a sequência de células α = π. Pelo Teorema 4, temos que a partição R(G, π, π) retornada pelo Algoritmo 16 é a partição ζ(π). Teorema 4. ([25]) Para todo grafo G e toda partição ordenada π de V (G), vale que R(G, π, π) = ζ(π). Antes de mostrarmos o Algoritmo 16, faz-se necessário algumas definições: o tamanho de uma partição ordenada π é a quantidade de células que ela possui, 57 6.3. Algoritmo de refinamento denotado por |π|. Lembre-se de que π[i] denota a i-ésima célula de π. Denotamos por π[i, j] a sequência de células (π[i], π[i + 1], . . . , π[j]). O símbolo · denota a operação de concatenação de duas partições, que é definida por: π · σ = (π[1], π[2], . . . , π[r], σ[1], σ[2], . . . , σ[s]), onde r = |π| e s = |σ|. Seja γ uma permutação do conjunto V (G). A imagem de v ∈ V sob γ é denotada por γ(v). Para W ⊆ V , temos γ(W ) = {γ(w)|w ∈ W }. Para π = (V1 , V2 , · · · , Vr ), definimos γ(π) = (γ(V1 ), γ(V2 ), · · · , γ(Vr )). A sequência de células α pode ser entendida como uma fila de processamento de células. Para cada m, a célula π[k] é subdividida. Esta subdivisão é feita com base no número de vizinhos que cada vértice em π[k] possui no conjunto α[m]. Ao término desta subdivisão, a célula π[k] foi quebrada em novas células (σ[1], . . . , σ[s]). Dados os subconjuntos X, Y ⊂ V (G), dizemos que X é homogêneo em relação a Y se d(x, Y ) = d(x′ , Y ) para todo x, x′ ∈ X. Pelo modo como π[k] foi subdividida, os vértices x, y ∈ σ[i] têm o mesmo número de vizinhos dentro de α[m]. Portanto, todo σ[i] é homogêneo em relação à α[m]. Após a subdivisão, todo σ[i] é inserido na fila. Isso é necessário pois para uma célula π[j] qualquer, π[j] poderia ser homogênea em relação a π[k]. Com o refinamento, π[j] pode não ser homogênea em relação a todas as células de σ criadas a partir de π[k]. Portanto, é necessário incluir todo4 σ[i] em α. Por fim, π̃ é atualizado, concatenando-se π̃ com σ. Em outras palavras, a célula π[k] é substituída por σ, visto que σ é um refinamento de π[k]. O processo então se repete com a próxima célula da lista de processamento, até que o algoritmo encontre uma partição discreta, ou termine de processar todas as células da lista α. Caso a partição retornada não seja discreta, é necessário então continuar o processo para forçar a obtenção de uma partição que seja discreta. Isto é feito através 4 No Algoritmo 16, a maior célula de σ (linha 23: σ[t]) não é inserida na fila devido a uma otimização do algoritmo. Capítulo 6. Algoritmo Pratical Graph Isomorphism 58 Algoritmo 16: Procedimento de Refinamento Entrada: Grafo G, partição ordenada π de V (G) e α subsequência de π Saída: R(G, π, α) 1 início 2 π̃ ← π 3 m←1 4 enquanto π̃ não é discreta ou m ≤ |α| faça 5 π̂ ← () 6 para k de 1 até |π̃| faça 7 p ← |π̃[k]| 8 (u1 , . . . , up ) ← elementos de π̃[k] ordenados de tal forma que d(ui , α[m]) < d(uj , α[m]) ⇒ i < j 9 σ ← ({u1 }) 10 s←1 11 para i de 2 até p faça 12 se d(ui−1 , W ) = d(ui , W ) então 13 σ[s] ← σ[s] ∪ {ui } 14 senão 15 s←s+1 16 σ[s] ← {ui } 17 fim se 18 fim para 19 se s > 1 então 20 t ← arg max{|σ[t]| : 1 ≤ t ≤ s} 21 se α[j] = π̃[k] para algum j (m ≤ j ≤ |α|) então 22 α[j] ← σ[t] 23 fim se 24 α ← α · σ[1, t − 1] · σ[t + 1, s] 25 π̂ ← π̂ · σ 26 fim se 27 fim para 28 π̃ ← π̂ 29 m←m+1 30 fim enquanto 31 retorna π̃ 32 fim de um algoritmo recursivo, que isola um vértice na partição obtida e executa o procedimento de refinamento novamente. Nas duas próximas seções, mostraremos como é feita a escolha deste vértice e explicaremos o funcionamento deste algoritmo 59 6.4. Partições aninhadas recursivo. 6.4 Partições aninhadas Seja π = (V1 , V2 , . . . , Vk ) uma partição ordenada de V (G) e v ∈ Vi para algum i. Definimos π ◦ v = 1. π, se |Vi | = 1. 2. (V1 , . . . , Vi−1 , v, Vi \ v, Vi+1 , . . . , Vk ), se |V1 | > 1 Definimos também π ⊥ v = R(G, π ◦ v, (v)). Teorema 5. ([25]) Seja G um grafo, π uma partição ordenada de V (G) e suponha que há alguma partição G-igualitária σ tal que π ≤ σ. Se escolhermos α ⊆ π tal que para todo W ∈ σ, temos X ⊆ W para, no máximo, um X ∈ π \ α, então, R(G, π, α) é G-igualitária. Pelo Teorema 5, temos que R(G, π ◦ v, (v)) é G-igualitária. Dado um grafo G, uma partição ordenada π de V (G) e uma sequência v = v1 , v2 , . . . , vm−1 de vértices distintos de V , definimos por ν = (π1 , π2 , . . . , πm ) a sequência de partições aninhadas derivadas de G, π e v, onde: 1. π1 = R(G, π, π) e, 2. πi = πi−1 ⊥ vi−1 , para 2 ≤ i ≤ m. O conjunto de todas as sequências de partições aninhadas derivadas de algum G, π e um vetor v de elementos distintos de V é denotado por N (V ). 6.5 Árvore de busca Uma árvore de busca T (G) é o conjuntos de todas as partições aninhadas ν ∈ N (V ) tal que ν é derivado de um grafo G, uma partição ordenada π de V (G) e uma Capítulo 6. Algoritmo Pratical Graph Isomorphism 60 sequência v1 , v2 , . . . , vm−1 onde, para 1 ≤ i ≤ m − 1, vi é um elemento da primeira célula não trivial de πi que possui o menor tamanho. A definição implica que |πi | < |πi+1 | para 1 ≤ i < m. A sequência ν também é chamada de nó da árvore. Seja ν = (π1 , π2 , . . . , πm ) um nó da árvore T (G). Dizemos que ν é um nó terminal se πm for discreta. Dados dois nós ν1 = (π1 , π2 , . . . , πm ) e µ = (π1 , π2 , . . . , πm , πm+1 ), dizemos que µ é sucessor (ou filho) de ν. De mesmo modo, dizemos que ν é antecessor (ou pai) de µ. O nó ν = (π1 ) é um ancestral comum a todos os nós, já que a escolha de π1 está fixada: π1 = R(G, π, π). O algoritmo 17 encontra todos os nós da árvore T (G), tendo como partição inicial a partição unitária π = ({V (G)}). Algoritmo 17: Árvore de Busca Entrada: Grafo G 1 início 2 π ← V (G) 3 ν ← R(G, π, π) 4 Gera(ν) 5 fim Observe que o Algoritmo 18 é um algoritmo de backtracking, onde os nós ν de T (G) são gerados de forma recursiva5 . Note que, no Algoritmo 18, a partição π será impressa se ela for discreta. As demais partições do nó ν são utilizadas apenas para o backtracking do algoritmo, pois como já visto anteriormente, apenas as partições discretas geradas neste processo terão utilidade para o cômputo do isomorfo canônico C(G) e, consequentemente, para a verificação do isomorfismo. Observe que não serão todas as n! partições discretas de V (G) que serão geradas pelo Algoritmo 18. Isso ocorre pois os vértices que serão isolados durante o backtrac5 O algoritmo 17 é uma versão recursiva do algoritmo iterativo apresentado por B. McKay em [25], página 55. 61 6.5. Árvore de busca Algoritmo 18: Gera(ν) Entrada: ν 1 início 2 k ← |ν| 3 π ← ν[k] 4 se π é discreta então 5 Imprime π 6 retorna 7 fim se 8 t ← min{i : |π[i]| > 1} 9 W ← π[t] 10 para cada v ∈ W faça 11 σ←π⊥v 12 Gera(ν · (σ)) 13 fim para 14 fim king estarão obrigatoriamente em uma célula não trivial (veja linha 7 do Algoritmo 18). Assim, células triviais não serão alteradas e terão suas posições fixadas no nós descendentes na árvore de busca. Além disso, pelo Teorema 6, temos que a quantidade de partições discretas geradas pelo Algoritmo 18 para o grafo G é igual ao número de partições discretas geradas pelo Algoritmo 18 para o grafo H. Figuras 6.1 e 6.2 temos um exemplo de um grafo G e a árvore de busca T (G). 1 0 2 3 4 5 6 Figura 6.1: Grafo G. 7 Capítulo 6. Algoritmo Pratical Graph Isomorphism 62 π = ({0, . . . , 7}) π1 = ({7}, {6}, {0}, {5}, {1, 2}, {4}, {3}) ν1 = π1 , π2 = ({7}, {6}, {0}, {5}, {1}, {2}, {4}, {3}) ν2 = π1 , π2 = ({7}, {6}, {0}, {5}, {2}, {1}, {4}, {3}) Figura 6.2: Árvore T (G). Como visto anteriormente, para cada partição discreta π impressa pelo Algoritmo 18, associamos uma permutação γπ . Ao aplicarmos esta permutação no grafo G, obtemos o grafo γπ (G), que é representado por seu vetor característico χ . E γπ (G) Denotamos por P (G) o conjunto de todos grafos γπ (G). Assim, definimos o isomorfo canônico de G como sendo o grafo C(G) = max{γπ (G) ∈ P (G)} Teorema 6. ([25]) Se G e H são isomorfos, então P (G) = P (H). Como visto, o Algoritmo 18 produz as partições discretas que serão utilizadas para o cômputo do isomorfo canônico, através da associação de uma partição discreta a uma permutação do grafo G. O processo então se repete para o grafo H e comparase os isomorfos canônicos C(G) e C(H). O Teorema 6 nos garante que se os grafos G e H são isomorfos, então C(G) = C(H), pois como P (G) = P (H), então o grafo máximo de P (G) é o mesmo para P (H). A verificação do isomorfo canônico pode ser otimizada, utilizando-se o grupo de automorfismos para descartar partições discretas impressas pelo Algoritmo 18. Na próxima seção, veremos como o PGI utiliza o grupo de automorfismos de G para podar a árvore de busca T (G) e otimizar ainda mais a busca pelo isomorfo canônico. 63 6.6. Podando a árvore de busca 6.6 Podando a árvore de busca Se o grupo de automorfismos de um grafo G for grande (isso ocorre, por exemplo, em grafos vértice-transitivos), então a árvore de busca T (G) terá muitos ramos e, consequentemente, o número de nós terminais será, no mínimo, o tamanho de Aut(G). Como a árvore é explorada em profundidade, então os automorfismos que são descobertos durante a busca serão usados para podar a árvore de busca. Sejam ν1 e ν2 dois nós terminais de uma árvore de busca T (G), e sejam π ∈ ν1 e σ ∈ ν2 partições discretas. Suponha que ν1 está a esquerda (será analisado primeiramente pelo Algoritmo 18) de ν2 na árvore de busca. Se γπ (G) = γσ (G), então a permutação τ : V → V definida por τ (v) = γπ−1 γσ (v) é um automorfismo de G. Seja γ uma permutação de Sn e σ uma partição ordenada de V (G). Denotamos por γ(σ) a partição ordenada obtida permutando-se os vértices de σ conforme γ. Seja ν = (π1 , π2 , . . . , πm ) um nó na árvore T (G). Denotamos por γ(ν) a permutação γ aplicada a todas as partições de ν, ou seja, γ(ν) = (γ(π1 ), γ(π2 ), . . . , γ(πm )). Sejam a, b, c nós de T (G) tal que a é o ancestral comum mais recente de ν1 e ν2 , b é o filho de a e ancestral de ν1 e c é o filho de a e ancestral de ν2 . A permutação τ envia π a σ, fixa a e envia b para c. Em outras palavras, temos que τ (π) = τ (σ) e τ (b) = τ (c). Denotamos por T (G, x) uma subárvore de T (G) enraizada pelo nó x. Então T (G, c) é isomorfa a T (G, b) (pela permutação τ ), e portanto o conjunto de grafos gerados por nós terminais de T (G, c) é o mesmo conjunto de grafos gerados por nós terminais de T (G, b). Como a árvore de busca T (G) é explorada em profundidade, então T (G, b) é visitada antes de T (G, c). Portanto6 , não é necessário explorar T (G, c) e a busca continua pelo nó a. 6 O Algoritmo 18 não incorpora esta otimização CAPÍTULO 7 Resultados Neste capítulo vamos discutir os resultados obtidos nos testes do Algoritmo PGI e também do algoritmo proposto. Para realizar os testes, implementamos o Algoritmo proposto na linguagem C++, e o programa Nauty foi obtido no sítio nauty and Traces [24] e testados em um computador com processador Intel Core i5 2.5GHz, 4GB de memória RAM e sistema operacional OS X Mavericks. Para efetuar os testes, utilizamos um gerador de pares de grafos isomorfos e também bibliotecas de grafos disponíveis em [23]. Nas próximas seções, o programa Nauty refere-se a implementação do algoritmo PGI, e o programa Mabi (Matching based isomorphism é a implementação do algoritmo proposto nesta dissertação, ambos na linguagem C++, utilizando o compilador gcc, com diretiva de compilação O2. O código fonte da implementação do algoritmo proposto está disponível no Anexo 9 e no repositório Bitbucket 1 . 1 https://bitbucket.org/rodrigedilson/dissertacao-isomorfismo-grafos Capítulo 7. Resultados 7.1 66 Gerador de grafos aleatórios - instâncias positivas Utilizamos o modelo de Erdős-Rényi, que gera um grafo G com n vértices e com m arestas, em média. A probabilidade de uma aresta {u, v} ser inserida no grafo G é dada por p= m n . 2 Para se gerar o grafo G, para cada par de vértices {u, v} é realizado um sorteio, com probabilidade p de sucesso. Caso o sorteio seja bem sucedido, a aresta {u, v} é inserida em G. O número de arestas do grafo será, com alta probabilidade, muito próximo a m, pois a distribuição do número de arestas é binominal e possui grande concentração em torno da média2 . Já o grafo H, isomorfo a G, é gerado permutando-se os vértices de G com uma permutação γ ∈ Sn aleatória. 7.1.1 Resultados A metodologia de testes foi gerar cinco pares de grafos com n vértices e m arestas e executar tanto o programa Nauty quanto o programa Mabi para cada par de grafo. Nas Tabelas 7.1 e 7.2, os valores se referem à média dos tempos de execução, em segundos. Para os grafos densos, a quantidade de arestas foi dada por grafos esparsos, a quantidade de arestas foi definida por 2n. Tabela 7.1: Grafos aleatórios densos. Vértices 50 100 200 400 2 Veja desigualdade de Chernoff, em [1]. Nauty 0.004 0.017 0.150 0.945 Mabi 0.287 2.490 22.876 191.211 n 2 e para os 67 7.1. Gerador de grafos aleatórios - instâncias positivas Tabela 7.2: Grafos aleatórios esparsos. Vértices 50 100 200 400 Nauty 0.002 0.002 0.002 0.005 Mabi 0.257 0.375 3.877 24.014 Como observado nas tabelas, o algoritmo PGI teve um desempenho muito superior em todos os testes realizados. O motivo principal para tal superioridade é que o PGI isola um vérice a cada chamada recursiva do algoritmo de refinamento. Em outras palavras, a cada chamada recursiva, o algoritmo de refinamento tem como parâmetro uma partição obtida em uma etapa anterior do backtracking. Já o algoritmo proposto, por não ser um algoritmo de backtracking, não se utiliza da partição obtida em uma simulação como informação para realizar uma próxima simulação. Além disso o PGI possui uma otimização, que é a poda de ramos da árvore, diminuindo a quantidade de grafos permutados que são obtidos a partir de uma partição discreta de V (G), e consequentemente haverá uma diminuição do tempo de cômputo do isomorfo canônico. Ademais, na etapa de refinamento do PGI, a partição obtida sempre será igualitária, o que não ocorre no algoritmo proposto, uma vez que o processo de simulação pode se estabilizar sem encontrar uma partição igualitária. Isto ocorre pois a função f utilizada na simulação não é bijetora, e ao implementarmos uma versão do Mabi com uma função bijetora, o desempenho se mostrou ainda pior, pois era necessário armazenar na memória todos os multiconjuntos encontrados durante as simulações. Capítulo 7. Resultados 7.2 68 Grafos com alto padrão de simetria McKay, ao testar o Algoritmo PGI, mencionou em seu artigo alguns tipos de grafos que trariam dificuldades para o PGI resolver o Problema de Isomorfismo de Grafos. Dentre eles, testamos os grafos fortemente regulares. É possível demonstrar formalmente que o Mabi é ineficiente para grafos fortemente regulares. Um grafo regular G, com v vértices e grau k, é dito fortemente regular se houver inteiros i e j tal que todo par de vértices adjacentes possua i vizinhos em comum e todo par de vértices não adjacentes possua j vizinhos em comum.3 Para efetuar os testes com grafos fortemente regulares, aplicamos uma permutação aleatória a um grafo obtido na biblioteca de grafos fortemente regulares, e assim conseguimos um par de grafos fortemente regulares isomorfos. Como no caso dos grafos aleatórios, executamos os programas Nauty e Mabi por cinco vezes e calculamos a sua média. A seguir, a Tabela 7.3 apresenta os resultados obtidos para grafos fortemente conexos isomorfos (tempo em segundos): Tabela 7.3: Grafos fortemente regulares isomorfos. Características v = 25, k = 12, i = 5, j = 6 v = 36, k = 14, i = 4, j = 6 7.2.1 Nauty 0.002 0.002 Mabi 0.041 16.496 Grafos fortemente regulares não isomorfos Ao testarmos um par de grafos fortemente regulares não isomorfos com as mesmas características (v, k, i e j comum aos dois grafos), detectamos que o processo de simulação gerava v partições igualitárias com apenas três células, e todas elas 3 Para os testes, utilizamos a biblioteca de grafos fortemente regulares, disponível em [23]. 69 7.2. Grafos com alto padrão de simetria compatíveis entre si. Com isso, o grafo bipartido R tornava-se completo, com v! emparelhamentos perfeitos (lembre-se que uma aresta {p, q} é inserida em R se e somente se as partições ψG,p e ψH,q são compatíveis), e consequentemente o algoritmo proposto um número exponencial de testes para verificar que G e H não eram isomorfos, o que deixou o programa Mabi ineficiente. É possível demonstrar formalmente que o algoritmo proposto é ineficiente para (2) grafos fortemente regulares. Seja G um grafo fortemente regular e π = ψG,p uma partição obtida a partir de uma p-simulação. Pela Proposição 1, π possui apenas três células e podemos argumentar que π é G-regular. A célula π[3] contém apenas o vértice p, a célula π[2] possui os vizinhos de p e a célula π[1] contém os vértices não adjacentes a p. A homegeneidade de π[3] com relação às outras duas células é óbvia. Cada vértice u ∈ π[2] possui um vizinho em π[3], e pela definição de grafo fortemente regular, u e p possuem i vizinhos em comum. Como todos os vizinhos de p estão em π[2], logo u terá i vizinhos em π[2]. Também pela definição, cada vértice v ∈ π[1] possui j vizinhos em π[2], pois v e p não são adjacentes, e portanto possuem j vizinhos em comum. Como os vizinhos de p estão em π[2], cada vértice v terá j vizinhos em π[2]. Como G é k-regular, então todo vértice u ∈ π[2] terá k − i − 1 vizinhos em π[1] e todo v ∈ π[1] terá k − j vizinhos em π[1]. Portanto π é G-igualitária, para todo p ∈ V (G). Pelo Teorema 3, a p-simulação se estabiliza ao encontrar uma partição igualitária. Ao construir o grafo R, ele será completo, pois todo par de partições ψG,p e ψG,q será compatível, o que torna o algoritmo proposto ineficiente para grafos fortemente regulares. A Figura 7.1 ilustra o particionamento obtido após a realização de um p-simulação em um grafo fortemente regular. Os valores marcados nas setas representam a quantidade de vizinhos que um vértice presente em uma célula possui em relação a uma outra célula. Capítulo 7. Resultados 70 0 k−i−1 k π[3] π j 1 π[2] π[1] i k−j Figura 7.1: Partição π obtida após uma simulação em um grafo fortemente regular. A seguir, temos a Tabela 7.4 que mostra os resultados obtidos para pares de grafos fortemente conexos não isomorfos, mas que possuem os mesmos valores de v, k, i, j em ambos. Tabela 7.4: Grafos fortemente regulares não isomorfos. Características v = 25k = 12i = 5j = 6 v = 36k = 14i = 4j = 6 Nauty 0.001 0.004 Mabi 28.334 4929.180 CAPÍTULO 8 Considerações Finais Neste trabalho apresentamos conceitos básicos sobre teoria dos grafos, estudamos o Problema do Isomorfismo de Grafos e o algoritmo proposto por McKay. Como contribuição, apresentamos um algoritmo para o caso geral do PIG e um estudo detalhado do algoritmo PGI. Como visto no capítulo anterior, apesar de o algoritmo proposto não ter superado o algoritmo PGI em desempenho, houve uma contribuição válida, pois não foi encontrado na literatura um algoritmo para o PIG baseado em emparelhamentos perfeitos em grafo bipartido. Além disso, acreditamos que este algoritmo seja de fácil paralelização, o que traria um ganho de desempenho. Na próxima seção, apresentamos quais serão os trabalhos futuros relacionados a esta dissertação. Capítulo 8. Considerações Finais 8.1 72 Trabalhos futuros Em um trabalho futuro, pretendemos paralelizar este algoritmo, utilizando a seguinte estratégia: em primeiro lugar, a simulação seria paralelizada, pois foi verificado empiricamente que este processo demanda muito tempo de processamento. Em segundo momento, a paralelização seria na geração de emparelhamentos perfeitos. Outro trabalho a ser desenvolvido é paralelizar o algoritmo PGI. Além disso, pretendemos implementar duas melhorias no algoritmo proposto, com o objetivo de diminuir o tempo de execução: a primeira é modificar o processo de simulação, para que uma simulação tenha como parâmetro, além do vértice pivô e a função f , uma partição obtida em uma simulação anterior. A segunda melhoria é na etapa de verificação do isomorfismo a partir de um emparelhamento perfeito. Se um emparelhamento perfeito M não corresponde a um isomorfismo ϕ, certamente haverá um par de arestas e = {i, j} e a = {u, v} de M em que i = ϕ(j) e u 6= ϕ(v). Com isso, podemos modificar o algoritmo que gera os emparelhamentos perfeitos, de modo que as arestas e e a não estejam simultaneamente nos próximos emparelhamentos perfeitos gerados. Outras pesquisas a serem desenvolvidas é comparar o consumo de memória dos dois algoritmos, e testá-los em sistemas com pouca memória RAM e cache inexistente ou limitado. Bibliografia [1] Alon, N. e Spencer, J. (2004). The Probabilistic Method. Wiley-Interscience series in discrete mathematics e optimization. Wiley. [2] Arvind, V. e Torán, J. (2005). Isomorphism testing: Perspective e open problems. Bulletin of the EATCS, 86:66–84. [3] Babai, L., Grigoryev, D. Y., e Mount, D. M. (1982). Isomorphism of graphs with bounded eigenvalue multiplicity. In Proceedings of the fourteenth annual ACM symposium on Theory of computing, STOC ’82, pages 310–324, New York, NY, USA. ACM. [4] Babai, L. e Luks, E. M. (1983). Canonical labeling of graphs. In Proceedings of the Fifteenth Annual ACM Symposium on Theory of Computing, STOC ’83, pages 171–183, New York, NY, USA. ACM. [5] Barbosa, R. M. (1975). Combinatória e Grafos. Nobel. [6] Chen, W.-K. (1997). Graph theory e its engineering applications. Advanced Series in Electrical e Computer Engineering. World Scientific. Bibliografia [7] Colbourn, C. J. (1981). 74 On testing isomorphism of permutation graphs. Networks, 11(1):13–21. [8] Conte, D., Foggia, P., Sansone, C., e Vento, M. (2004). Thirty Years Of Graph Matching In Pattern Recognition. International Journal of Pattern Recognition e Artificial Intelligence. [9] Farouk, R. M. (2011). Iris recognition based on elastic graph matching e gabor wavelets. Computer Vision e Image Understanding, 115(8):1239–1244. [10] Foggia, P., Sansone, C., e Vento, M. (2001). A performance comparison of five algorithms for graph isomorphism. Proc. of the 3rd IAPR TC-15 Workshop on Graph-based Representations in Pattern Recognition, pages 188–199. [11] Foggia, P., Sansone, C., e Vento, M. (2004). A (sub)graph isomorphism algorithm for matching large graphs. IEEETPAMI: IEEE Transactions on Pattern Analysis e Machine Intelligence, 26. [12] Gallian, J. (1994). Contemporary abstract algebra. D.C. Heath. [13] Goldreich, O., Micali, S., e Wigderson, A. (1986). Proofs that yield nothing but their validity e a methodology of cryptographic protocol design. In Proceedings of the 27th Annual Symposium on Foundations of Computer Science, SFCS ’86, pages 174–187, Washington, DC, USA. IEEE Computer Society. [14] Hopcroft, J. e Karp, R. (1973). An n5/2 algorithm for maximum matchings in bipartite graphs. SIAM Journal on Computing, 2(4):225–231. [15] Hopcroft, J. E. e Wong, J. K. (1974). Linear time algorithm for isomorphism of planar graphs (preliminary report). In Proceedings of the sixth annual ACM symposium on Theory of computing, STOC ’74, pages 172–184, New York, NY, USA. ACM. 75 Bibliografia [16] Jenner, B., Köbler, J., McKenzie, P., e Torán, J. (2003). Completeness results for graph isomorphism. J. Comput. Syst. Sci., 66(3):549–566. [17] Jukna, S. (2001). Extremal Combinatorics: With Applications in Computer Science. Springer. [18] Köbler, J., Schöning, U., e Torán, J. (1993). The Graph Isomorphism Problem: Its Structural Complexity. Progress in Theoretical Computer Science. Birkhäuser. [19] Leiserson, C., Cormen, T., Rivest, R., e Stein, C. (2002). Algoritmos: teoria e prática. Campus. [20] Lueker, G. S. e Booth, K. S. (1979). A linear time algorithm for deciding interval graph isomorphism. J. ACM, 26(2):183–195. [21] Luks, E. M. (1982). Isomorphism of graphs of bounded valence can be tested in polynomial time. Journal of Computer e System Sciences, 25(1):42 – 65. [22] Martin, D. M. (2011). Teoria dos grafos: notas de aula. Online. [23] McKay, B. (2013). Combinatorial data. [Online; http://cs.anu.edu.au/ bdm/data/graphs.html; accessado em 28-Dezembro-2013]. [24] McKay, B. e Piperno, A. (2013). nauty e traces. [Online; http://cs.anu.edu.au/ bdm/nauty/; accessado em 28-Dezembro-2013]. [25] McKay, B. D. (1981). Practical graph isomorphism. In Congressus Numerantium (10th. Manitoba Conference on Numerical Mathematics e Computing), volume 30, pages 45–87. [26] Miller, G. (1980). Isomorphism testing for graphs of bounded genus. In Proceedings of the twelfth annual ACM symposium on Theory of computing, STOC ’80, pages 225–235, New York, NY, USA. ACM. Bibliografia 76 [27] Nandi, R. C. (2006). Isomorfismo de grafos aplicado à comparação de impressões digitais. Mestrado, UFPR. [28] Netto, P. O. B. (2006). Grafos: Teoria, modelos, algoritmos. Edgard Blucher, 4 edition. [29] Pedarsani, P. e Grossglauser, M. (2011). On the privacy of anonymized networks. In Proceedings of the 17th ACM SIGKDD international conference on Knowledge discovery e data mining, KDD ’11, pages 1235–1243, New York, NY, USA. ACM. [30] Raymond, J. W. e Willett, P. (2002). Maximum common subgraph isomorphism algorithms for the matching of chemical structures. Journal of Computer-Aided Molecular Design, 16(7):521–533. [31] Rodrigues, P. M. (2013). Emparelhamentos em grafos. [Online; http://www.math.ist.utl.pt/ pmartins/EMF/Notas/Grafos5.pdf; accessado em 28-Dezembro-2013]. [32] Schmidt e Druffel (1976). A fast backtracking algorithm to test directed graphs for isomorphism using distance matrices. JACM: Journal of the ACM, 23. [33] Sipser, M. (1996). Introduction to the Theory of Computation. International Thomson Publishing, 1st edition. [34] Szwarcfiter, J. L. (1988). Grafos e algoritmos computacionais. Campus, Rio de Janeiro. [35] Torán, J. (2004). On the hardness of graph isomorphism. SIAM J. Comput., 33(5):1093–1108. [36] Ullmann, J. R. (1976a). An algorithm for subgraph isomorphism. JACM: Journal of the ACM, 23. 77 Bibliografia [37] Ullmann, J. R. (1976b). An algorithm for subgraph isomorphism. J. ACM, 23(1):31–42. [38] Uno, T. (2001). A fast algorithm for enumerating bipartite perfect matchings. In ISAAC, pages 367–379. CAPÍTULO 9 Anexo 1 #pragma once 2 #i f n d e f CELULA_H_ 3 #d e f i n e CELULA_H_ 4 5 #i n c l u d e <s e t > 6 7 u s i n g namespace s t d ; 8 9 10 c l a s s Cell { public : 11 Cell () ; 12 Cell ( int label ) ; 13 14 s e t <i n t > v e r t i c e s ; 15 16 17 int label ; 79 b o o l o p e r a t o r <( C e l l c ) { 18 return label < c . label ; 19 } 20 21 }; 22 23 #e n d i f /∗ CELULA_H_ ∗/ codigo/cell.h 1 2 #i f n d e f CELULA_CPP_ 3 #d e f i n e CELULA_CPP_ 4 5 #i n c l u d e " c e l l . h " 6 7 Cell : : Cell () { 8 label = 0; 9 } 10 11 Cell : : Cell ( int label ) { t h i s −>l a b e l = l a b e l ; 12 13 } 14 15 #e n d i f codigo/cell.cpp 1 #pragma once 2 #i f n d e f GRAPH_H_ 3 #d e f i n e GRAPH_H_ 4 5 #i n c l u d e < l i s t > 6 #i n c l u d e <i o s t r e a m > 7 #i n c l u d e <f s t r e a m > Capítulo 9. Anexo 80 8 9 u s i n g namespace s t d ; 10 11 // Uma c l a s s e para r e p r e s e n t a r g r a f o s d i r i g i d o s 12 c l a s s Digraph { 13 protected : 14 int n ; // numero de v e r t i c e s 15 i n t m; // numero de a r e s t a s 16 17 // Matriz de a d j a c e n c i a 18 i n t ∗∗ matrix ; 19 20 // Uma m a t r i z de a d j a c e n c i a e s p e c i a l 21 /∗ / 22 ∗ | Adj [ u ] . end ( ) | the i t e r a t o r pointing to v in u ’ s l i s t i f v i s not i n u ’ s a d j a c e n c y list , 23 ∗ M[ u ] [ v ] = < 24 ∗ otherwise . 25 ∗ 26 ∗/ 27 \ l i s t <i n t > : : i t e r a t o r ∗∗M; 28 29 30 public : l i s t <i n t > ∗ a d j ; 31 32 Digraph ( int n) ; 33 Digraph ( Digraph ∗G) ; // C o n s t r u t o r // C o n s t r u t o r ( f a z c o p i a de G) 34 35 /∗ C o n s t r o i um g r a f o d i r i g i d o a p a r t i r do g r a f o b i p a r t i d o balan− 36 ∗ ceado R e emparelhamento M. As a r e s t a s de M são o r i e n t a d a s de 37 ∗ ’ b a i x o ’ para ’ cima ’ e a s a r e s t a s de R \ M são o r i e n t a d a s de 38 ∗ ’ cima ’ para ’ b a i x o ’ . 81 39 ∗/ ~ Digraph ( ) ; // D e s t r u t o r 42 bool insert_arc ( in t u , in t v) ; // I n s e r e o a r c o ( u , v ) 43 b o o l remove_arc ( i n t u , i n t v ) ; // Remove o a r c o ( u , v ) 44 bool exists_arc ( in t u , in t v) ; // V e r i f i c a s e ( u , v ) e s t a 40 41 presente 45 bool invert_arc ( in t u , in t v) ; // Troca orientação do a r c o ( u , v) 46 bool invert_path ( i n t u , const i n t ∗ parent ) ; 47 48 l i s t <i n t > : : i t e r a t o r r e m o v e _ a r c _ a l t e r n a t i v e ( i n t u , i n t v ) ; // Remove o a r c o ( u , v ) 49 50 l i s t <i n t > : : i t e r a t o r a r c ( i n t u , i n t v ) ; 51 52 void p r i n t ( ) ; 53 v o i d read_graph // Imprime o g r a f o na t e l a ( FILE ∗ ) ; // Le um g r a f o não d i r i g i d o de um a r q u i v o 54 v o i d read_digraph ( FILE ∗ ) ; // Le um g r a f o d i r i g i d o de um arquivo 55 56 i n t get_n ( ) ; // R etorna a q u a n t i d a d e de v e r t i c e s 57 i n t get_m ( ) ; // R etorna a q u a n t i d a d e de a r e s t a s 58 59 60 // Transpoe o g r a f o ( i n p l a c e ) 61 void t r an sp ose ( ) ; 62 63 // R etorna uma c o p i a do g r a f o t r a n s p o s t o 64 Digraph ∗ g e t _ t r a n s p o s e ( ) ; 65 66 // R etorna a m a t r i z de a d j a c e n c i a s Capítulo 9. Anexo c o n s t i n t ∗∗ adjacency_ma tri x ( ) ; 67 68 82 }; 69 70 #e n d i f codigo/digraph.h 1 #i f n d e f GRAFOS_CPP_ 2 #d e f i n e GRAFOS_CPP_ 3 4 #i n c l u d e " d i g r a p h . h " 5 #i n c l u d e <i o s t r e a m > 6 #i n c l u d e <f s t r e a m > 7 8 Digraph : : Digraph ( i n t n ) { t h i s −>n = n ; 9 10 a d j = new l i s t <i n t > [ n ] ; 11 matrix = NULL; 12 // I n i c i a l i z a a m a t r i z de a d j a c e n c i a 13 M = new l i s t <i n t > : : i t e r a t o r ∗ [ n ] ; 14 f o r ( i n t i = 0 ; i < n ; i ++) { 15 M[ i ] = new l i s t <i n t > : : i t e r a t o r [ n ] ; 16 f o r ( i n t j = 0 ; j < n ; j ++) 17 M[ i ] [ j ] = a d j [ i ] . end ( ) ; 18 } 19 20 } 21 22 23 Digraph : : Digraph ( Digraph ∗G) { n = G−>n ; 24 25 a d j = new l i s t <i n t > [ n ] ; 26 matrix = NULL; 27 83 // Aloca a m a t r i z de a d j a c e n c i a e i n i c i a l i z a com " 0 " 28 M = new l i s t <i n t > : : i t e r a t o r ∗ [ n ] ; 29 f o r ( i n t i = 0 ; i < n ; i ++) { 30 M[ i ] = new l i s t <i n t > : : i t e r a t o r [ n ] ; 31 f o r ( i n t j = 0 ; j < n ; j ++) 32 M[ i ] [ j ] = a d j [ i ] . end ( ) ; 33 } 34 35 36 // Copia o s a r c o s 37 f o r ( i n t i = 0 ; i < n ; i ++) f o r ( l i s t <i n t > : : i t e r a t o r j = G−>a d j [ i ] . b e g i n ( ) ; j != G−>a d j [ i ] . 38 end ( ) ; j ++) { a d j [ i ] . push_front ( ∗ j ) ; 39 M[ i ] [ ∗ j ] = a d j [ i ] . b e g i n ( ) ; 40 } 41 42 // Mantem a m a t r i z a t u a l i z a d a } 43 44 v o i d Digraph : : t r a n s p o s e ( ) { 45 l i s t <i n t > ∗ oldAdj = a d j ; 46 a d j = new l i s t <i n t > [ n ] ; 47 48 49 f o r ( i n t i = 0 ; i < n ; i ++) f o r ( i n t j = 0 ; j < n ; j ++) M[ i ] [ j ] = a d j [ i ] . end ( ) ; 50 51 52 53 f o r ( i n t i = 0 ; i < n ; i ++) f o r ( l i s t <i n t > : : i t e r a t o r j = oldAdj [ i ] . b e g i n ( ) ; j != oldAdj [ i ] . end ( ) ; j ++) { a d j [ ∗ j ] . push_front ( i ) ; 54 M[ i ] [ ∗ j ] = j ; 55 56 } 57 58 delete [ ] oldAdj ; Capítulo 9. Anexo 59 } 60 61 Digraph : : ~ Digraph ( ) { 62 // L i b e r a memoria 63 f o r ( i n t i = 0 ; i < n ; i ++) delete 64 [ ] M[ i ] ; 65 delete [ ] M; 66 delete [ ] adj ; 67 } 68 69 // I n s e r e o a r c o ( u , v ) 70 b o o l Digraph : : i n s e r t _ a r c ( i n t u , i n t v ) { i f (M[ u ] [ v ] == a d j [ u ] . end ( ) ) { 71 a d j [ u ] . push_front ( v ) ; 72 73 // A t u a l i z a a m a t r i z 74 M[ u ] [ v ] = a d j [ u ] . b e g i n ( ) ; 75 return true ; 76 } 77 78 return f a l s e ; 79 80 } 81 82 // Muda a direcão de um a r c o em tempo c o n s t a n t e 83 b o o l Digraph : : i n v e r t _ a r c ( i n t u , i n t v ) 84 { 85 86 i f (M[ u ] [ v ] == a d j [ u ] . end ( ) | | M[ v ] [ u ] != a d j [ v ] . end ( ) ) return f a l s e ; 87 88 a d j [ u ] . e r a s e (M[ u ] [ v ] ) ; 89 M[ u ] [ v ] = a d j [ u ] . end ( ) ; 90 91 a d j [ v ] . push_front ( u ) ; M[ v ] [ u ] = a d j [ v ] . b e g i n ( ) ; 84 85 92 return true ; 93 94 } 95 96 // Muda a direcão dos a r c o s em um caminho u , p [ u ] , p [ p [ u ] ] , 97 b o o l Digraph : : i n v e r t _ p a t h ( i n t u , c o n s t i n t ∗ p a r e n t ) 98 { ... bool r e s u l t = true ; 99 100 w h i l e ( p a r e n t [ u ] != u ) { 101 102 r e s u l t = r e s u l t && i n v e r t _ a r c ( p a r e n t [ u ] , u ) ; 103 u = parent [ u ] ; } 104 105 return r esu lt ; 106 107 } 108 109 110 // Remove o a r c o ( u , v ) em tempo c o n s t a n t e 111 b o o l Digraph : : remove_arc ( i n t u , i n t v ) { 112 i f (M[ u ] [ v ] != a d j [ u ] . end ( ) ) { 113 a d j [ u ] . e r a s e (M[ u ] [ v ] ) ; 114 // A t u a l i z a a m a t r i z 115 M[ u ] [ v ] = a d j [ u ] . end ( ) ; 116 return true ; 117 } 118 119 return f a l s e ; 120 121 } 122 123 l i s t <i n t > : : i t e r a t o r Digraph : : r e m o v e _ a r c _ a l t e r n a t i v e ( i n t u , i n t v ) 124 { Capítulo 9. Anexo l i s t <i n t > : : i t e r a t o r next_element = a d j [ u ] . e r a s e (M[ u ] [ v ] ) ; 125 126 M[ u ] [ v ] = a d j [ u ] . end ( ) ; 127 128 r e t u r n next_element ; 129 130 } 131 132 // V e r i f i c a s e o a r c o ( u , v ) p e r t e n c e ao g r a f o em tempo c o n s t a n t e 133 b o o l Digraph : : e x i s t s _ a r c ( i n t u , i n t v ) { i f (M[ u ] [ v ] == a d j [ u ] . end ( ) ) 134 return f a l s e ; 135 return true ; 136 137 } 138 139 l i s t <i n t > : : i t e r a t o r Digraph : : a r c ( i n t u , i n t v ) { r e t u r n M[ u ] [ v ] ; 140 141 } 142 143 // Imprime a s l i s t a s de v i z i n h a n c a na t e l a 144 v o i d Digraph : : p r i n t ( ) { 145 f o r ( i n t i = 0 ; i < n ; i ++) { c o u t << i << " : " ; 146 147 148 // Imprime p r i m e i r o v i z i n h o de i 149 l i s t <i n t > : : i t e r a t o r j = a d j [ i ] . b e g i n ( ) ; 150 i f ( j != a d j [ i ] . end ( ) ) { c o u t << " " << ∗ j ; 151 152 153 // P e r c o r r e o r e s t a n t e dos v i z i n h o s de i , imprimindo−o s 154 f o r ( j ++; j != a d j [ i ] . end ( ) ; j ++) c o u t << " , " << ∗ j ; 155 156 157 } 86 87 158 c o u t << e n d l ; 159 } 160 } 161 162 // Le o g r a f o da e n t r a d a e p r e e n c h e a e s t r u t u r a de dados na memoria 163 v o i d Digraph : : read_graph ( FILE ∗ e n t r a d a ) { while ( ! f e o f ( entrada ) ) { 164 int u , v ; 165 166 167 f s c a n f ( entrada , "%d " , &u ) ; 168 f s c a n f ( entrada , "%d " , &v ) ; 169 170 t h i s −>i n s e r t _ a r c ( u , v ) ; 171 t h i s −>i n s e r t _ a r c ( v , u ) ; } 172 173 } 174 175 // Le o d i g r a f o da e n t r a d a e p r e e n c h e a e s t r u t u r a de dados na memoria 176 v o i d Digraph : : read_digraph ( FILE ∗ e n t r a d a ) { while ( ! f e o f ( entrada ) ) { 177 int u , v ; 178 179 180 f s c a n f ( entrada , "%d " , &u ) ; 181 f s c a n f ( entrada , "%d " , &v ) ; 182 t h i s −>i n s e r t _ a r c ( u , v ) ; 183 } 184 185 } 186 187 // R etorna a q u a n t i d a d e de v e r t i c e s 188 i n t Digraph : : get_n ( ) { return n ; 189 190 } Capítulo 9. Anexo 88 191 192 /∗ ∗ R etorna a q u a n t i d a d e de a r e s t a s ∗/ 193 194 i n t Digraph : : get_m ( ) { int m = 0; 195 196 f o r ( i n t i = 0 ; i < n ; i ++) { 197 198 l i s t <i n t > : : i t e r a t o r j = a d j [ i ] . b e g i n ( ) ; 199 l i s t <i n t > : : i t e r a t o r f = a d j [ i ] . end ( ) ; 200 f o r ( ; j != f ; j ++) m ++; 201 } 202 203 r e t u r n m; 204 205 } 206 207 // R etorna um novo g r a f o que e o t r a n s p o s t o do g r a f o −o b j e t o 208 // I am not s u r e we need t h i s f u n c t i o n . . . 209 // s t r o n g l y Conn . comp may s i m p l y t r a n s p o s e t h e same graph G a l l a l o n g 210 Digraph ∗ Digraph : : g e t _ t r a n s p o s e ( ) { Digraph ∗T = new Digraph ( ∗ t h i s ) ; 211 212 T−>t r a n s p o s e ( ) ; 213 214 r e t u r n T; 215 216 } 217 218 // Aloca e r e t o r n a a m a t r i z de a d j a c e n c i a do g r a f o 219 i n t c o n s t ∗∗ Digraph : : adjacency_matr i x ( ) { 220 221 i f ( matrix != NULL) r e t u r n ( c o n s t i n t ∗ ∗ ) matrix ; 222 223 matrix = new i n t ∗ [ n ] ; 89 f o r ( i n t i = 0 ; i < n ; i ++) 224 matrix [ i ] = new i n t [ n ] ; 225 226 f o r ( i n t i = 0 ; i < n ; i ++) 227 f o r ( i n t j = 0 ; j < n ; j ++) 228 matrix [ i ] [ j ] = 0 ; 229 230 f o r ( i n t i = 0 ; i < n ; i ++) 231 f o r ( l i s t <i n t > : : i t e r a t o r j = a d j [ i ] . b e g i n ( ) ; j != a d j [ i ] . end ( ) ; 232 j ++) matrix [ i ] [ ∗ j ] = 1 ; 233 234 r e t u r n ( c o n s t i n t ∗ ∗ ) matrix ; 235 236 } 237 238 #e n d i f /∗ GRAFOS_CPP_ ∗/ codigo/digraph.cpp 1 #pragma once 2 #i f n d e f ARESTA_H_ 3 #d e f i n e ARESTA_H_ 4 5 s t r u c t Edge { 6 int u , v ; 7 }; 8 9 10 #e n d i f /∗ ARESTA_H_ ∗/ codigo/edge.h 1 #pragma once 2 #i f n d e f ISOMORPHISM_H Capítulo 9. Anexo 3 90 #d e f i n e ISOMORPHISM_H 4 5 #i n c l u d e " d i g r a p h . h " 6 #i n c l u d e " matching . h " 7 #i n c l u d e " p a r t i t i o n . h " 8 9 10 c l a s s Isomorphism { public : 11 int n ; 12 Digraph ∗G, ∗H; 13 P a r t i t i o n ∗∗ psiG , ∗∗ psiH ; 14 15 Isomorphism ( Digraph ∗G, Digraph ∗H) { 16 t h i s −>G = G; 17 t h i s −>H = H; 18 n = G−>get_n ( ) ; 19 } 20 21 ~ Isomorphism ( ) { f o r ( i n t p = 0 ; p < n ; p ++) 22 d e l e t e psiG [ p ] ; 23 24 f o r ( i n t q = 0 ; q < n ; q ++) 25 d e l e t e psiH [ q ] ; 26 27 28 delete [ ] psiG ; 29 delete [ ] psiH ; 30 } 31 32 i n t ∗ matching_based_algorithm ( ) ; 33 34 i n t ∗ get_map_from_two_discrete_partitions ( P a r t i t i o n ∗P , P a r t i t i o n ∗Q) ; 91 35 /∗ Supoe | E(G) | = | E(H) | . 36 ∗/ b o o l check_isomorphism ( c o n s t i n t ∗ p e r m u t a t i o n ) ; 37 38 /∗ Remove a r e s t a s de R que não e s t a o c o n t i d a s em c i r c u i t o s . 39 ∗/ Edge trim_edges ( Digraph ∗R) ; 40 41 42 v o i d r e s t r i c t _ m a t c h i n g ( Matching ∗M, i n t p , i n t q ) ; 43 v o i d r e s t r i c t _ c o m p a t i b i l i t y _ g r a p h ( Digraph ∗R, i n t p , i n t q ) ; 44 Digraph ∗ n e w _ c o n s t r a i n e d _ c o m p a t i b i l i t y _ g r a p h ( Digraph ∗R, i n t p , i n t q) ; 45 i n t ∗ enumerate_isomorphism_candidates ( Digraph ∗D, Matching ∗M) ; 46 47 }; 48 49 #e n d i f /∗ ISOMORPHISM_H ∗/ codigo/isomorphism.h 1 #i n c l u d e <c s t d l i b > 2 #i n c l u d e <i o s t r e a m > 3 #i n c l u d e " edge . h " 4 #i n c l u d e " s e a r c h . h " 5 #i n c l u d e " isomorphism . h " 6 #i n c l u d e " s i m u l a t i o n . h " 7 8 i n t ∗ Isomorphism : : get_map_from_two_discrete_partitions ( P a r t i t i o n ∗P , P a r t i t i o n ∗Q) { 9 i n t ∗ p e r m u t a t i o n = new i n t [ n ] ; 10 11 l i s t <C e l l ∗ >:: i t e r a t o r i = P−> c e l l s . b e g i n ( ) , j = Q−> c e l l s . b e g i n ( ) , l a s t _ i = P−> c e l l s . end ( ) ; 12 13 f o r ( ; i != l a s t _ i ; i ++, j ++) p e r m u t a t i o n [ ∗ ( ( ∗ i )−>v e r t i c e s . b e g i n ( ) ) ] = ∗ ( ( ∗ j )−>v e r t i c e s . b e g i n ( ) ) ; Capítulo 9. Anexo 92 14 r et u r n permutation ; 15 16 17 } 18 19 /∗ Recebe d o i s g r a f o s e uma n−permutaçao . Supoe que o s 20 ∗ g r a f o s r e c e b i d o s tem n v e r t i c e s e supoe que o s g r a f o s 21 ∗ r e c e b i d o s possuem o mesmo número de a r e s t a s . 22 ∗/ b o o l Isomorphism : : check_isomorphism ( c o n s t i n t ∗ p e r m u t a t i o n ) { 23 i n t n = G−>get_n ( ) ; 24 c o n s t i n t ∗∗MH = H−>adjacency_matr i x ( ) ; 25 l i s t <i n t > ∗ a d j = G−>a d j ; 26 f o r ( i n t u = 0 ; u < n ; u++) 27 f o r ( l i s t <i n t > : : i t e r a t o r v = a d j [ u ] . b e g i n ( ) ; v != a d j [ u ] . end ( ) ; v 28 ++) if 29 ( !MH[ p e r m u t a t i o n [ u ] ] [ p e r m u t a t i o n [ ∗ v ] ] ) return f a l s e ; 30 31 return true ; 32 33 } 34 35 36 37 38 39 /∗ Devolve um i s o m o r f i s m o de G em H (em forma de um ∗ v e t o r de permutaçao ) ou NULL. ∗/ i n t ∗ Isomorphism : : matching_based_algorithm ( ) { i f (H−>get_n ( ) != G−>get_n ( ) | | H−>get_m ( ) != G−>get_m ( ) ) r e t u r n NULL; 40 41 psiG = new P a r t i t i o n ∗ [ n ] ; 42 psiH = new P a r t i t i o n ∗ [ n ] ; 43 44 // C a l c u l a a p a r t i à § a o da p−simulaçao para todo p em V(G) 45 f o r ( i n t p = 0 ; p < n ; p++) { 93 46 // Faz a p−simulaçao para cada p em V(G) 47 psiG [ p ] = S i m u l a t i o n (G, p ) . g e t _ p a r t i t i o n ( ) ; 48 49 // Se a p a r t i à § a o o b t i d a e d i s c r e t a , s e r a r a p i d o . . . 50 i f ( psiG [ p]−> i s _ d i s c r e t e ( ) ) { 51 52 // Faz a q−simulaçao para cada q em V(H) 53 f o r ( i n t q = 0 ; q < n ; q++) { psiH [ q ] = S i m u l a t i o n (H, q ) . g e t _ p a r t i t i o n ( ) ; 54 55 56 // Se uma p a r t i à § a o o b t i d a e d i s c r e t a , t e s t a por i s o m o r f i s m o 57 i f ( psiH [ q]−> i s _ d i s c r e t e ( ) ) { i n t ∗ p e r m u t a t i o n = get_map_from_two_discrete_partitions ( psiG [ 58 p ] , psiH [ q ] ) ; 59 i f ( check_isomorphism ( p e r m u t a t i o n ) ) 60 r et u r n permutation ; 61 62 delete 63 [ ] permutation ; } 64 } 65 66 67 // Nao e x i s t e i s o m o r f i s m o e n t r e G e H 68 r e t u r n NULL; } 69 70 } 71 72 // C a l c u l a a p a r t i à § a o da q−simulaçao para todo q em V(H) 73 f o r ( i n t q = 0 ; q < n ; q++) { 74 psiH [ q ] = S i m u l a t i o n (H, q ) . g e t _ p a r t i t i o n ( ) ; 75 76 // Se a p a r t i à § a o o b t i d a e d i s c r e t a , nao e x i s t e i s o m o r f i s m o e n t r e G e H Capítulo 9. Anexo i f ( psiH [ q]−> i s _ d i s c r e t e ( ) ) 77 r e t u r n NULL; 78 } 79 80 81 // Gera g r a f o R 82 Digraph ∗R = new Digraph ( 2 ∗ n ) ; 83 f o r ( i n t p = 0 ; p < n ; p++) f o r ( i n t q = 0 ; q < n ; q++) 84 i f ( psiG [ p]−> i s _ c o m p a t i b l e ( psiH [ q ] ) ) 85 R−>i n s e r t _ a r c ( q + n , p ) ; 86 87 88 // R−>p r i n t ( ) ; 89 Matching ∗M = new Matching ( n ) ; 90 M−>grow_matching_in (R) ; 91 i f (M−>i s _ p e r f e c t ( ) ) { i n t ∗ p e r m u t a t i o n = enumerate_isomorphism_candidates (R, M) ; 92 93 94 d e l e t e R; 95 d e l e t e M; 96 r et u r n permutation ; 97 } 98 99 100 d e l e t e M; 101 d e l e t e R; 102 r e t u r n NULL; 103 104 } 105 106 v o i d Isomorphism : : r e s t r i c t _ m a t c h i n g ( Matching ∗M, i n t p , i n t q ) { 107 c o n s t i n t ∗ G_p_cell_of_vertex = psiG [ p]−> c e l l _ o f _ v e r t e x _ a r r a y ( ) ; 108 c o n s t i n t ∗ H_q_cell_of_vertex = psiH [ q]−> c e l l _ o f _ v e r t e x _ a r r a y ( ) ; 109 c o n s t i n t ∗ matched = M−>mapping ( ) ; 94 95 110 f o r ( i n t i = 0 ; i < n ; i ++) { 111 i f ( matched [ i ] == NOONE) 112 continue ; 113 114 115 i n t j = matched [ i ] ; 116 c o n s t i n t ∗ G_i_cell_of_vertex = psiG [ i ]−> c e l l _ o f _ v e r t e x _ a r r a y ( ) ; 117 c o n s t i n t ∗ H_j_cell_of_vertex = psiH [ j ]−> c e l l _ o f _ v e r t e x _ a r r a y ( ) ; 118 i f ( G_p_cell_of_vertex [ i ] != H_q_cell_of_vertex [ j ] ) 119 M−>set_matched ( i , NOONE) ; 120 121 i f ( G_i_cell_of_vertex [ p ] != H_j_cell_of_vertex [ q ] ) 122 M−>set_matched ( i , NOONE) ; 123 } 124 125 } 126 127 128 /∗ p and q must be i n t h e r a n g e 0 . . ( n−1) . ∗/ 129 Digraph ∗ Isomorphism : : n e w _ c o n s t r a i n e d _ c o m p a t i b i l i t y _ g r a p h ( Digraph ∗R, int p , int q) { 130 Digraph ∗S = new Digraph ( 2 ∗ n ) ; 131 c o n s t i n t ∗ G_p_cell_of_vertex = psiG [ p]−> c e l l _ o f _ v e r t e x _ a r r a y ( ) ; 132 c o n s t i n t ∗ H_q_cell_of_vertex = psiH [ q]−> c e l l _ o f _ v e r t e x _ a r r a y ( ) ; 133 134 f o r ( i n t i = 0 ; i < n ; i ++) { 135 c o n s t i n t ∗ G_i_cell_of_vertex = psiG [ i ]−> c e l l _ o f _ v e r t e x _ a r r a y ( ) ; 136 l i s t <i n t > &n e i g h b o r s = R−>a d j [ i ] ; 137 138 f o r ( l i s t <i n t > : : i t e r a t o r j = n e i g h b o r s . b e g i n ( ) ; j != n e i g h b o r s . end ( ) ; j ++) { 139 c o n s t i n t ∗ H_j_cell_of_vertex = psiH [ ∗ j − n]−> cell_of_vertex_array ( ) ; Capítulo 9. Anexo 96 140 i f ( G_p_cell_of_vertex [ i ] == H_q_cell_of_vertex [ ∗ j − n ] ) 141 i f ( G_i_cell_of_vertex [ p ] == H_j_cell_of_vertex [ q ] ) 142 S−>i n s e r t _ a r c ( i , ∗ j ) ; 143 } 144 } 145 146 f o r ( i n t i = 0 ; i < n ; i ++) { 147 148 c o n s t i n t ∗ H_i_cell_of_vertex = psiH [ i ]−> c e l l _ o f _ v e r t e x _ a r r a y ( ) ; 149 l i s t <i n t > &n e i g h b o r s = R−>a d j [ i + n ] ; 150 f o r ( l i s t <i n t > : : i t e r a t o r j = n e i g h b o r s . b e g i n ( ) ; j != n e i g h b o r s . end 151 ( ) ; j ++) { c o n s t i n t ∗ G_j_cell_of_vertex = psiG [ ∗ j ]−> c e l l _ o f _ v e r t e x _ a r r a y ( ) ; 152 153 154 i f ( G_p_cell_of_vertex [ ∗ j ] == H_q_cell_of_vertex [ i ] ) 155 i f ( G_j_cell_of_vertex [ p ] == H_i_cell_of_vertex [ q ] ) S−>i n s e r t _ a r c ( i + n , ∗ j ) ; 156 } 157 } 158 159 return S ; 160 161 } 162 163 164 165 166 /∗ Remove a r e s t a s que nao e s t a o em c i r c u i t o s e r e t o r n a a ∗ última a r e s t a que nao f o i removida . Edge Isomorphism : : trim_edges ( Digraph ∗D) { Edge e = {NOONE, NOONE} ; 167 168 S e a r c h S (D) ; 169 S . strongly_connec te d_co m po nent s _al g o ri t hm ( ) ; 170 i n t const ∗ scc = S . get_scc_array ( ) ; 171 ∗/ 97 f o r ( i n t i = 0 ; i < n ; i ++) { 172 l i s t <i n t > &n e i g h b o r s = D−>a d j [ i ] ; 173 174 f o r ( l i s t <i n t > : : i t e r a t o r j = n e i g h b o r s . b e g i n ( ) ; j != n e i g h b o r s . end 175 ( ) ; j ++) i f ( s c c [ i ] != s c c [ ∗ j ] ) { 176 177 j = D−>r e m o v e _ a r c _ a l t e r n a t i v e ( i , ∗ j ) ; 178 i f ( j == n e i g h b o r s . end ( ) ) break ; 179 180 } 181 else { 182 e .u = i ; 183 e.v = ∗j ; } 184 } 185 186 return e ; 187 188 } 189 190 /∗ Recebe um g r a f o b i p a r t i d o b a l a n c e a d o R e um emp . p e r f e i t o M. 191 i n t ∗ Isomorphism : : enumerate_isomorphism_candidates ( Digraph ∗D, Matching ∗M) { 192 193 i f ( check_isomorphism (M−>mapping ( ) ) ) { 194 i n t ∗ p e r m u t a t i o n = new i n t [ n ] ; 195 c o n s t i n t ∗ mapping = M−>mapping ( ) ; 196 f o r ( i n t i = 0 ; i < n ; i ++) 197 p e r m u t a t i o n [ i ] = mapping [ i ] ; 198 199 r et u r n permutation ; 200 201 202 } ∗/ Capítulo 9. Anexo 203 98 Edge e , e_prime = trim_edges (D) ; 204 205 i f ( e_prime . u == NOONE) r e t u r n NULL; 206 207 208 /∗ Acha c i r c u i t o C . ∗/ 209 S e a r c h S (D) ; 210 S . find_path ( e_prime . v , e_prime . u ) ; 211 i n t c o n s t ∗ path = S . get_parent_array ( ) ; 212 213 /∗ E s c o l h e a a r e s t a e . ∗/ 214 i f ( e_prime . u < e_prime . v ) e = e_prime ; 215 216 else { 217 e . v = e_prime . u ; 218 e . u = path [ e_prime . u ] ; 219 } 220 221 Matching ∗M_prime = new Matching (M, e_prime . u , path ) ; 222 223 Digraph ∗ D_plus = n e w _ c o n s t r a i n e d _ c o m p a t i b i l i t y _ g r a p h (D, e . u , e . v − n ); 224 225 r e s t r i c t _ m a t c h i n g (M, e . u , e . v − n ) ; 226 227 M−>grow_matching_in ( D_plus ) ; 228 229 i f (M−>i s _ p e r f e c t ( ) ) { 230 231 i n t ∗ p e r m u t a t i o n = enumerate_isomorphism_candidates ( D_plus , M) ; 232 233 i f ( permutation ) { 234 d e l e t e D_plus ; 99 d e l e t e M_prime ; 235 236 r et u r n permutation ; 237 } 238 239 } 240 241 d e l e t e D_plus ; 242 243 244 D−>i n v e r t _ p a t h ( e_prime . u , path ) ; 245 D−>i n v e r t _ a r c ( e_prime . u , e_prime . v ) ; 246 247 /∗ Remove o a r c o r e v e r s o de e ( p o i s e f o i r e v e r t i d o ) . 248 D−>r e m o v e _ a r c _ a l t e r n a t i v e ( e . v , e . u ) ; ∗/ 249 i n t ∗ p e r m u t a t i o n = enumerate_isomorphism_candidates (D, M_prime ) ; 250 251 d e l e t e M_prime ; 252 253 i f ( permutation ) 254 r et u r n permutation ; 255 256 r e t u r n NULL; 257 258 } codigo/isomorphism.cpp 1 #i n c l u d e <c s t d l i b > 2 #i n c l u d e <f s t r e a m > 3 #i n c l u d e <i o s t r e a m > 4 #i n c l u d e " isomorphism . h " 5 6 7 u s i n g namespace s t d ; Capítulo 9. Anexo 8 9 100 i n t main ( i n t ac , c h a r ∗∗ av ) { int n ; 10 11 // MABI −− matching based isomorphism 12 i f ( ac != 3 ) { 13 c o u t << " Usage : \ n\n\ tmabi <graph f i l e #1> <graph f i l e #2>\n\n " ; 14 c o u t << " \ t \ t−− t h e format o f a graph f i l e 15 c o u t << " n a_1 b_1 a_2 b_2 . . . a_m b_m EOF, \ n\ t \ t " ; 16 c o u t << " where a_i b_i e n c o d e s an edge , and numbers\n\ t \ t " ; 17 c o u t << " a r e s e p a r a t e d by w h i t e s p a c e s \n\n " ; i s : \ n\ t \ t " ; 18 r e t u r n EXIT_FAILURE ; 19 20 } 21 22 // Entrada dos g r a f o s G e H 23 FILE ∗ graph1 = f o p e n ( av [ 1 ] , " r t " ) ; 24 i f ( graph1 == NULL) { 25 c o u t << " Could not open graph f i l e " << av [ 1 ] << e n d l ; 26 r e t u r n EXIT_FAILURE ; 27 } 28 29 f s c a n f ( graph1 , "%d " , &n ) ; 30 Digraph ∗G = new Digraph ( n ) ; 31 G−>read_graph ( graph1 ) ; 32 f c l o s e ( graph1 ) ; 33 34 35 FILE ∗ graph2 = f o p e n ( av [ 2 ] , " r t " ) ; i f ( graph2 == NULL) { 36 c o u t << " Could not open graph f i l e " << av [ 2 ] << e n d l ; 37 r e t u r n EXIT_FAILURE ; 38 } 39 40 f s c a n f ( graph2 , "%d " , &n ) ; 101 41 Digraph ∗H = new Digraph ( n ) ; 42 H−>read_graph ( graph2 ) ; f c l o s e ( graph2 ) ; 43 44 Isomorphism I (G, H) ; 45 46 47 i n t ∗ p e r m u t a t i o n = I . matching_based_algorithm ( ) ; 48 i f ( permutation ) { f o r ( i n t i = 0 ; i < n − 1 ; i ++) 49 c o u t << p e r m u t a t i o n [ i ] << " " ; 50 c o u t << p e r m u t a t i o n [ n − 1 ] << e n d l ; 51 } 52 53 54 d e l e t e G; 55 d e l e t e H; 56 return 0; 57 58 } codigo/main.cpp 1 2 #pragma once 3 #i f n d e f MATCHING_H 4 #d e f i n e MATCHING_H 5 6 #i f n d e f NOONE 7 #d e f i n e NOONE −1 8 #e n d i f 9 10 #i f n d e f NULL 11 #d e f i n e NULL 0 12 #e n d i f 13 Capítulo 9. Anexo 14 102 #i n c l u d e " edge . h " 15 16 c l a s s Digraph ; 17 18 /∗ C l a s s e r e s p o n s a v e l por e n c o n t r a r um emparelhamento 19 ∗ p e r f e i t o num g r a f o b i p a r t i d o b a l a n c e a d o ( p o s s u i n d o 20 ∗ v e r t i c e s de 0 a ( k − 1 ) de um lado , e de k a ( 2 k − 21 ∗ 1 ) do o u t r o . 22 c l a s s Matching { 23 private : 24 25 /∗ n = 2 k ∗/ ∗/ int k , n ; 26 27 Digraph ∗D; 28 29 30 31 /∗ Vetor com k p o s i à § o e s : matched [ i ] = v e r t i c e ao q u a l ∗ i e s t a emparelhado ou NOONE c a s o c o n t r a r i o . ∗/ i n t ∗ matched ; 32 33 34 35 /∗ Vetor com 2k e l e m e n t o s usado para g u a r d a r ∗ caminhos da busca . ∗/ i n t ∗ parent ; 36 37 /∗ Funçao r e c u r s i v a de busca em p r o f u n d i d a d e : a 38 ∗ busca t e r m i n a quando e n c o n t r a r um v e r t i c e i 39 ∗ nao s a t u r a d o ( sem a r c o s a i n d o ) do l a d o menor 40 ∗ ( i . e . com i < k ) . 41 int dfs_visit ( int i ) ; ∗/ 42 43 44 45 46 public : /∗ Supoe que o g r a f o H e b i p a r t i d o , com v e r t i c e s ∗ de 0 a t e ( k−1) de um dos l a d o s . Matching ( i n t k ) ; ∗/ 103 47 Matching ( c o n s t Matching &M) ; 48 49 Matching ( Matching ∗M, i n t edge_bottom , c o n s t i n t ∗ path ) ; 50 51 ~ Matching ( ) ; 52 53 54 /∗ Encontra um emparelhamento maximo no g r a f o . 55 v o i d grow_matching_in ( Digraph ∗H) ; ∗/ 56 /∗ V e r i f i c a s e o emparelhamento e p e r f e i t o . 57 ∗/ bool is_ p erfect () ; 58 59 /∗ Imprime emparelhamento . 60 ∗/ void p r i n t ( ) ; 61 62 63 /∗ R etorna mapa c o r r e s p o n d e n t e ao emparelhamento . 64 c o n s t i n t ∗ mapping ( ) ; 65 /∗ A j u s t a manualmente um v e r t i c e emparelhado a o u t r o 66 ∗ ( s o no v e t o r matched ) . 67 v o i d set_matched ( i n t i , i n t j ) ; 68 69 ∗/ }; 70 71 72 #e n d i f /∗ MATCHING_H ∗/ codigo/matching.h 1 #i n c l u d e < l i s t > 2 #i n c l u d e <v e c t o r > 3 #i n c l u d e " s e a r c h . h " 4 #i n c l u d e " d i g r a p h . h " 5 #i n c l u d e " matching . h " ∗/ Capítulo 9. Anexo 104 6 7 Matching : : Matching ( i n t k ) { D = NULL; 8 t h i s −>k = k ; 9 10 n = 2 ∗ k; 11 matched = new i n t [ k ] ; 12 p a r e n t = NULL; 13 f o r ( i n t i = 0 ; i < k ; i ++) 14 matched [ i ] = NOONE; 15 16 } 17 18 Matching : : Matching ( c o n s t Matching &M) { 19 D = NULL; 20 k = M. k ; 21 n = M. n ; 22 matched = new i n t [ k ] ; 23 p a r e n t = NULL; 24 f o r ( i n t i = 0 ; i < k ; i ++) 25 matched [ i ] = M. matched [ i ] ; 26 27 } 28 29 /∗ Faz uma c o p i a do emparelhamento M e f a z M = M ds C, onde 30 ∗ ds denota a ’ d i f e r e n c a s i m e t r i c a ’ e C e o c i r c u i t o c o d i − 31 ∗ f i c a d o p e l o v e r t i c e edge_bottom e p e l o caminho c o d i f i c a − 32 ∗ do na a r v o r e de busca ’ path ’ . 33 ∗/ Matching : : Matching ( Matching ∗M, i n t edge_bottom , c o n s t i n t ∗ path ) { 34 D = NULL; 35 k = M−>k ; 36 n = M−>n ; 37 matched = new i n t [ k ] ; 38 p a r e n t = NULL; 105 39 f o r ( i n t i = 0 ; i < k ; i ++) 40 matched [ i ] = M−>matched [ i ] ; 41 42 /∗ ∗∗∗∗∗∗∗∗ ∗∗ ∗ ∗ ∗∗ ∗ ∗ ∗ ∗∗ ∗ ∗ ∗∗ ∗ CHECK CHECK CHECK CHECK ∗∗∗∗∗∗∗∗∗∗ ∗∗ ∗ ∗∗ ∗ ∗ 43 ∗/ 44 i n t x = edge_bottom ; 45 w h i l e ( path [ x ] != x ) { 46 matched [ x ] = path [ x ] − k ; 47 x = path [ path [ x ] ] ; } 48 49 } 50 51 Matching : : ~ Matching ( ) { 52 delete 53 i f ( p a r e n t != NULL) delete 54 55 [ ] matched ; [ ] parent ; } 56 57 /∗ Funcao r e c u r s i v a de busca em p r o f u n d i d a d e . 58 ∗ Busca t e r m i n a quando e n c o n t r a r um v e r t i c e 59 ∗ i < k , sem a r c o de s a à d a . 60 61 62 i n t Matching : : d f s _ v i s i t ( i n t i ) { i f ( i < k && matched [ i ] == NOONE) return i ; 63 64 l i s t <i n t > : : i t e r a t o r j = D−>a d j [ i ] . b e g i n ( ) ; 65 l i s t <i n t > : : i t e r a t o r end = D−>a d j [ i ] . end ( ) ; 66 f o r ( ; j != end ; j ++) 67 i f ( p a r e n t [ ∗ j ] == NOONE) { 68 parent [ ∗ j ] = i ; 69 i n t r = d f s _ v i s i t (∗ j ) ; 70 i f ( r != NOONE) ∗/ Capítulo 9. Anexo 106 return r ; 71 } 72 73 r e t u r n NOONE; 74 75 } 76 77 /∗ Encontra um emparelhamento maximo no g r a f o H dado . Caso o 78 ∗ emparelhamento j a tenha s i d o e n c o n t r a d o a n t e r i o r m e n t e s o b r e o mesmo 79 ∗ g r a f o ou s o b r e um s u p e r g r a f o d e l e , ou ainda , s e o emparelhamento 80 ∗ f o i c r i a d o a p a r t i r de uma c o p i a de o u t r o emparelhamento 81 ∗ ( p o s s i v e l m e n t e d e s a t u a l i z a d o ) , e f e i t a uma v e r i f i c a c a o para 82 ∗ e n c o n t r a r p o s s à v e i s i r r e g u l a r i d a d e s . Por exemplo , s e um v e r t i c e 83 ∗ e s t a emparelhado com um c e r t o v e r t i c e segundo o v e t o r ’ matched ’ , 84 ∗ mas com um v e r t i c e d i f e r e n t e no g r a f o H, i s s o deve s e r c o r r i g i d o 85 ∗ ( a t u a l i a n d o −s e o v e t o r matched ) . Caso um v e r t i c e i e s t e j a 86 ∗ emparelhado com um v e r t i c e j segundo o v e t o r ’ matched ’ mas nao 87 ∗ e s t e j a emparelhado com ninguem no g r a f o H, e n t a o tudo bem , d e s d e 88 ∗ que i e j nao tenham a r c o s i n c i d e n t e s no g r a f o . Essa v e r i f i c a c a o 89 ∗ nao e f e i t a a t u a l m e n t e . Tambem supoe−s e que o g r a f o H nao p o s s u i 90 ∗ v e r t i c e na p a r t e de b a i x o ( de 0 a ( k − 1 ) ) com mais de um a r c o 91 ∗ de s a à d a . 92 93 ∗/ v o i d Matching : : grow_matching_in ( Digraph ∗H) { D = H; 94 95 96 97 f o r ( i n t i = 0 ; i < k ; i ++) { l i s t <i n t > &n e i g h b o r s = D−>a d j [ i ] ; 98 99 100 101 // p e n s a r s e i s s o pode o c o r r e r : i f ( n e i g h b o r s . b e g i n ( ) != n e i g h b o r s . end ( ) ) matched [ i ] = ∗ ( n e i g h b o r s . b e g i n ( ) ) − k ; 102 103 /∗ Se o g r a f o de e n t r a d a nao t i v e r no maximo um a r c o s a i n d o 107 104 ∗ de cada v e r t i c e da p a r t e de ’ b a i x o ’ , e n t a o a l i n h a acima 105 ∗ e onde c o r r e −s e o r i s c o de t e r matched [ i ] = matched [ j ] 106 ∗ com i != j . ∗/ 107 } 108 109 110 111 112 i f ( p a r e n t == NULL) p a r e n t = new i n t [ n ] ; 113 114 b o o l augmented ; 115 116 /∗ Guarda s e um v e r t i c e de k a t e n − 1 e s t a s a t u r a d o ou nao . 117 i n t ∗ s = new i n t [ k ] ; 118 119 120 f o r ( i n t i = 0 ; i < k ; i ++) s [ i ] = NOONE; 121 122 123 f o r ( i n t i = 0 ; i < k ; i ++) i f ( matched [ i ] != NOONE) s [ matched [ i ] ] = i ; 124 125 126 127 do { augmented = f a l s e ; 128 129 130 f o r ( i n t i = 0 ; i < n ; i ++) p a r e n t [ i ] = NOONE; 131 132 133 f o r ( i n t i = 0 ; i < k ; i ++) { int j = i + k ; 134 135 136 i f ( s [ i ] == NOONE) { parent [ j ] = j ; ∗/ Capítulo 9. Anexo s [ i ] = dfs_visit ( j ) ; 137 } 138 } 139 140 f o r ( i n t i = 0 ; i < k ; i ++) { 141 i f ( s [ i ] == NOONE | | matched [ s [ i ] ] == i ) 142 continue ; 143 144 /∗ R e a l i z a a i n v e r s a o da o r i e n t a c a o dos a r c o s num caminho 145 ∗ aumentador de i a t e s [ i ] . ∗/ 146 f o r ( i n t j = s [ i ] ; p a r e n t [ j ] != j ; j = p a r e n t [ j ] ) { 147 i f ( j < k) { 148 149 matched [ j ] = p a r e n t [ j ] − k ; 150 s [ matched [ j ] ] = j ; 151 } 152 D−>i n v e r t _ a r c ( p a r e n t [ j ] , j ) ; } 153 154 augmented = t r u e ; 155 } 156 } w h i l e ( augmented ) ; 157 158 delete 159 160 [] s; } 161 162 v o i d Matching : : p r i n t ( ) { f o r ( i n t i = 0 ; i < k ; i ++) 163 c o u t << i << " : " << matched [ i ] << e n d l ; 164 165 } 166 167 b o o l Matching : : i s _ p e r f e c t ( ) { 168 f o r ( i n t i = 0 ; i < k ; i ++) 169 i f ( matched [ i ] == NOONE) 108 109 return f a l s e ; 170 171 return true ; 172 173 } 174 175 c o n s t i n t ∗ Matching : : mapping ( ) { r e t u r n ( c o n s t i n t ∗ ) matched ; 176 177 } 178 179 v o i d Matching : : set_matched ( i n t i , i n t j ) { matched [ i ] = j ; 180 181 } codigo/matching.cpp 1 #pragma once 2 #i f n d e f PARTICAO_H_ 3 #d e f i n e PARTICAO_H_ 4 5 #i n c l u d e " c e l l . h " 6 #i n c l u d e <map> 7 #i n c l u d e < l i s t > 8 9 u s i n g namespace s t d ; 10 11 // C l a s s e para armazenar dados de uma p a r t i c a o 12 class Partition { 13 private : 14 int n ; 15 int ∗ cell_of_vertex ; 16 b o o l cell_of_vertex_array_up_to_date ; 17 18 19 public : l i s t <C e l l ∗> c e l l s ; // Guarda a l i s t a ordenada de c e l u l a s Capítulo 9. Anexo 110 20 21 const in t ∗ cell_of_vertex_array ( ) ; 22 bool i s _ d i s c r e t e () ; 23 b o o l i s _ c o m p a t i b l e ( P a r t i t i o n ∗P) ; 24 bool r efin e_ or d er ed _ p ar t it ion _ u sin g_ lab els ( i n t ∗) ; 25 void p r i n t ( ) ; 26 int size () ; 27 28 Partition ( int n) ; 29 ~Partition () ; 30 }; 31 32 33 34 #e n d i f /∗ PARTICAO_H_ ∗/ codigo/partition.h 1 #i f n d e f PARTICAO_CPP_ 2 #d e f i n e PARTICAO_CPP_ 3 4 #i f n d e f NULL 5 #d e f i n e NULL 0 6 #e n d i f 7 8 #i n c l u d e < l i s t > 9 #i n c l u d e <s e t > 10 #i n c l u d e <map> 11 #i n c l u d e " p a r t i t i o n . h " 12 13 u s i n g namespace s t d ; 14 15 /∗ I n i c i a l i z a uma p a r t i c a o u n i t a r i a do c o n j u n t o { 0 , . . . , n − 1} ∗/ 16 Partition : : Partition ( int n) { 111 17 C e l l ∗C = new C e l l ( 0 ) ; 18 t h i s −>n = n ; 19 f o r ( i n t i = 0 ; i < n ; i ++) 20 C−>v e r t i c e s . i n s e r t ( i ) ; 21 22 23 c e l l s . push_back (C) ; 24 c e l l _ o f _ v e r t e x = NULL; 25 cell_of_vertex_array_up_to_date = f a l s e ; 26 } 27 28 Partition ::~ Partition () { i f ( cell_of_vertex ) 29 delete 30 [ ] cell_of_vertex ; 31 w h i l e ( ! c e l l s . empty ( ) ) { 32 33 C e l l ∗C = c e l l s . f r o n t ( ) ; 34 c e l l s . pop_front ( ) ; 35 d e l e t e C; 36 } 37 38 } 39 40 41 42 b o o l P a r t i t i o n : : i s _ c o m p a t i b l e ( P a r t i t i o n ∗P) { i f ( c e l l s . s i z e ( ) != P−> c e l l s . s i z e ( ) ) return f a l s e ; 43 44 l i s t <C e l l ∗ >:: i t e r a t o r i , j ; 45 f o r ( i = c e l l s . b e g i n ( ) , j = P−> c e l l s . b e g i n ( ) ; i != c e l l s . end ( ) ; i ++, j ++) { 46 47 48 i f ( ( ∗ i )−>l a b e l != ( ∗ j )−>l a b e l ) return f a l s e ; i f ( ( ∗ i )−>v e r t i c e s . s i z e ( ) != ( ∗ j )−>v e r t i c e s . s i z e ( ) ) Capítulo 9. Anexo 112 return f a l s e ; 49 } 50 51 return true ; 52 53 } 54 55 bool Partition : : i s _ d i s c r e t e () { r e t u r n ( c e l l s . s i z e ( ) == ( u n s i g n e d ) n ) ; 56 57 } 58 59 const in t ∗ Part it ion : : cell_of_vertex_array ( ) { i f ( c e l l _ o f _ v e r t e x == NULL) 60 c e l l _ o f _ v e r t e x = new i n t [ n ] ; 61 62 if 63 ( ! cell_of_vertex_array_up_to_date ) { 64 int k = 0; 65 f o r ( l i s t <C e l l ∗ >:: i t e r a t o r i = c e l l s . b e g i n ( ) ; i != c e l l s . end ( ) ; i ++, k++) f o r ( s e t <i n t > : : i t e r a t o r j = ( ∗ i )−>v e r t i c e s . b e g i n ( ) ; j != ( ∗ 66 i )−>v e r t i c e s . end ( ) ; j ++) cell_of_vertex [∗ j ] = k ; 67 68 cell_of_vertex_array_up_to_date = t r u e ; 69 } 70 71 r et u r n ( const i n t ∗) cell_ of_ v er t ex ; 72 73 } 74 75 76 77 /∗ Devolve ’ t r u e ’ s e o r e f i n a m e n t o i n d u z i d o p e l o v e t o r ’ l a b e l ’ ∗ e nao t r i v i a l e ’ f a l s e ’ c a s o c o n t r a r i o ∗/ bool Partition : : refine_ordered_partition_using_labels ( in t ∗ l a b e l ) { 78 bool r e f i n e d = f a l s e ; 79 l i s t <C e l l ∗> n e w _ c e l l s ; 113 80 81 f o r ( l i s t <C e l l ∗ >:: i t e r a t o r i = c e l l s . b e g i n ( ) ; i != c e l l s . end ( ) ; i ++) { map<i n t , C e l l ∗> c h i l d r e n ; 82 83 84 f o r ( s e t <i n t > : : i t e r a t o r j = ( ∗ i )−>v e r t i c e s . b e g i n ( ) ; j != ( ∗ i )−> v e r t i c e s . end ( ) ; j ++) c h i l d r e n [ l a b e l [ ∗ j ] ] = NULL; 85 86 f o r ( s e t <i n t > : : i t e r a t o r j = ( ∗ i )−>v e r t i c e s . b e g i n ( ) ; j != ( ∗ i )−> 87 v e r t i c e s . end ( ) ; j ++) { // O o p e r a d o r [ ] da c l a s s e 88 ’map ’ c r i a uma ’ C e l l ’ s e a chave nao e s t i v e r l a i f ( c h i l d r e n [ l a b e l [ ∗ j ] ] == NULL) { 89 c h i l d r e n [ l a b e l [ ∗ j ] ] = new C e l l ( l a b e l [ ∗ j ] ) ; 90 } 91 92 c h i l d r e n [ l a b e l [ ∗ j ]]−> v e r t i c e s . i n s e r t ( ∗ j ) ; 93 94 } 95 96 i f ( c h i l d r e n . s i z e ( ) > 1) refined = true ; 97 98 99 delete ∗ i ; 100 101 f o r (map<i n t , C e l l ∗ >:: i t e r a t o r k = c h i l d r e n . b e g i n ( ) ; k != c h i l d r e n . end ( ) ; k ++) n e w _ c e l l s . push_back ( k−>s e c o n d ) ; 102 103 } 104 105 c e l l s = new_cells ; 106 107 i f ( refined ) Capítulo 9. Anexo cell_of_vertex_array_up_to_date = f a l s e ; 108 return refined ; 109 110 114 } 111 112 // R etorna a q u a n t i d a d e de c e l u l a s da p a r t i c a o 113 int Partition : : size () { return c e l l s . siz e () ; 114 115 } 116 117 #e n d i f codigo/partition.cpp 1 #pragma once 2 #i f n d e f DFS_H_ 3 #d e f i n e DFS_H_ 4 5 #i n c l u d e < l i s t > 6 #i n c l u d e " d i g r a p h . h " 7 8 #i f n d e f NOONE 9 #d e f i n e NOONE −1 10 #e n d i f 11 12 c l a s s Search { 13 private : 14 int n ; 15 Digraph ∗G; 16 17 i n t component_counter ; 18 19 20 21 /∗ Vetor contendo o número da componente f o r t e m e n t e ∗ conexa contendo cada v e r t i c e . int ∗ scc ; ∗/ 115 22 23 24 /∗ Guarda a r v o r e de busca . ∗/ i n t ∗ parent ; 25 26 27 /∗ Guarda l i s t a de v e r t i c e s por tempo de f i n a l i z a c a o . ∗/ l i s t <i n t > v e r t i c e s _ s o r t e d _ b y _ f i n i s h _ t i m e ; 28 29 /∗ Funcao r e c u r s i v a da busca em p r o f u n d i d a d e . 30 void v i s i t ( i n t ) ; ∗/ 31 32 /∗ Usada na busca por um caminho ( f u n c a o find_path ) . 33 b o o l visit_u_look_fo r _v ( i n t u , i n t v ) ; ∗/ 34 35 36 37 38 public : /∗ R etorna um v e t o r c com n e l e m e n t o s , onde c [ i ] denota o número ∗ da componente da a r v o r e de busca que contem o v e r t i c e i . ∗/ i n t const ∗ get_scc_array ( ) ; 39 40 41 42 /∗ R etorna um v e t o r p com n e l e m e n t o s , onde p [ i ] denota o v e r t i c e ∗ que e p a i do v e r t i c e i na a r v o r e de busca em p r o f u n d i d a d e . i n t c o n s t ∗ get_parent_array ( ) ; 43 44 S e a r c h ( Digraph ∗G) ; 45 ~ Search ( ) ; 46 47 48 49 /∗ I n i c i a l i z a parâmetros da busca . Limpa a l i s t a de v e r t i c e s ∗ o r d e n a d o s por tempo de f i n a l i z a c a o . ∗/ void r e s e t ( ) ; 50 51 52 53 54 /∗ Executa busca em p r o f u n d i d a d e . P r o c e s s a o s v e r t i c e s no l a c o ∗ p r i n c i p a l segundo a ordem d e s e j a d a . v o i d d f s ( l i s t <i n t > &o r d e r ) ; void d fs ( ) ; ∗/ ∗/ Capítulo 9. Anexo 116 55 /∗ Executa o a l g o r i t m o que e n c o n t r a a s componentes f o r t e m e n t e 56 57 ∗ c o n e x a s do g r a f o . E s t a s s a o armazenadas no v e t o r s c c 58 ∗ que pode s e r a c e s s a d o p e l o metodo g e t _ s c c _ a r r a y ( ) . ∗/ v o i d strongly_connec te d_c om po ne nts _a lg o r it hm ( ) ; 59 60 61 /∗ Encontra um caminho de x a y . R etorna t r u e s e achou caminho . 62 b o o l find_path ( i n t x , i n t y ) ; 63 }; 64 65 66 #e n d i f codigo/search.h 1 #i f n d e f DFS_CPP_ 2 #d e f i n e DFS_CPP_ 3 4 #i n c l u d e " s e a r c h . h " 5 6 u s i n g namespace s t d ; 7 8 i n t const ∗ Search : : get_scc_array ( ) { r et u r n ( i n t const ∗) scc ; 9 10 } 11 12 i n t c o n s t ∗ S e a r c h : : get_parent_array ( ) { r et u r n ( i n t const ∗) parent ; 13 14 } 15 16 v o i d S e a r c h : : strongly_conne ct ed_c o mpo ne nts _a l g o ri thm ( ) { 17 Digraph ∗H = new Digraph (G) ; 18 H−>t r a n s p o s e ( ) ; 19 ∗/ 117 20 S e a r c h S (H) ; 21 S . dfs () ; 22 dfs (S . vertices_sorted_by_finish_time ) ; 23 24 d e l e t e H; 25 26 } 27 28 S e a r c h : : S e a r c h ( Digraph ∗G) { 29 t h i s −>G = G; 30 n = G−>get_n ( ) ; 31 32 s c c = new i n t [ n ] ; 33 p a r e n t = new i n t [ n ] ; 34 } 35 36 Search : : ~ Search ( ) { 37 delete [ ] parent ; 38 delete [ ] scc ; 39 } 40 41 void Search : : d fs ( ) { reset () ; 42 43 f o r ( i n t u = 0 ; u < n ; u ++) 44 45 i f ( p a r e n t [ u ] == NOONE) { 46 component_counter ++; parent [ u ] = u ; 47 v i s i t (u) ; 48 } 49 50 } 51 52 v o i d S e a r c h : : d f s ( l i s t <i n t > &o r d e r ) { Capítulo 9. Anexo 118 reset () ; 53 54 f o r ( l i s t <i n t > : : i t e r a t o r u = o r d e r . b e g i n ( ) ; u != o r d e r . end ( ) ; u ++) 55 i f ( p a r e n t [ ∗ u ] == NOONE) { 56 component_counter ++; 57 58 p a r e n t [ ∗ u ] = ∗u ; 59 v i s i t (∗u) ; } 60 61 } 62 63 void Search : : v i s i t ( i n t u ) { l i s t <i n t > &n e i g h b o r s = G−>a d j [ u ] ; 64 65 66 s c c [ u ] = component_counter ; 67 f o r ( l i s t <i n t > : : i t e r a t o r v = n e i g h b o r s . b e g i n ( ) ; v != n e i g h b o r s . end ( ) ; v ++) { i f ( p a r e n t [ ∗ v ] == NOONE) { 68 69 parent [ ∗ v ] = u ; 70 v i s i t (∗ v) ; } 71 } 72 73 v e r t i c e s _ s o r t e d _ b y _ f i n i s h _ t i m e . push_front ( u ) ; 74 75 } 76 77 78 void Search : : r e s e t ( ) { f o r ( i n t i = 0 ; i < n ; i ++) { 79 p a r e n t [ i ] = NOONE; 80 s c c [ i ] = NOONE; 81 } 82 83 component_counter = 0 ; 84 vertices_sorted_by_finish_time . c l e a r ( ) ; 119 85 } 86 87 b o o l S e a r c h : : find_path ( i n t x , i n t y ) { reset () ; 88 89 90 parent [ x ] = x ; 91 r e t u r n visit_u_look_fo r _v ( x , y ) ; 92 } 93 94 b o o l S e a r c h : : visit_u_look_f o r_v ( i n t u , i n t v ) { i f ( u == v ) 95 return true ; 96 97 l i s t <i n t > &n e i g h b o r s = G−>a d j [ u ] ; 98 99 f o r ( l i s t <i n t > : : i t e r a t o r z = n e i g h b o r s . b e g i n ( ) ; z != n e i g h b o r s . end 100 ( ) ; z ++) { i f ( p a r e n t [ ∗ z ] == NOONE) { 101 102 parent [ ∗ z ] = u ; 103 i f ( visit_u_look_f o r_v ( ∗ z , v ) ) 104 return true ; 105 } } 106 107 return f a l s e ; 108 109 } 110 111 #e n d i f codigo/search.cpp 1 #pragma once 2 #i f n d e f SIMULATION_H 3 #d e f i n e SIMULATION_H Capítulo 9. Anexo 120 4 5 #i n c l u d e " d i g r a p h . h " 6 #i n c l u d e " p a r t i t i o n . h " 7 8 9 10 c l a s s Simulation { public : 11 S i m u l a t i o n ( Digraph ∗G, i n t s e e d ) ; 12 v i r t u a l ~ Simulation () ; 13 Partition ∗ get_partition () ; 14 15 16 private : 17 Digraph ∗ graph ; 18 Partition ∗ partition ; 19 in t seed ; i n t n , n_squared ; 20 21 i n t l a b e l _ f u n c t i o n ( m u l t i s e t <i n t >) ; 22 23 }; 24 25 #e n d i f /∗ SIMULATION_H ∗/ codigo/simulation.h 1 #i n c l u d e <s e t > 2 #i n c l u d e " s i m u l a t i o n . h " 3 4 5 i n t S i m u l a t i o n : : l a b e l _ f u n c t i o n ( m u l t i s e t <i n t > M) { i n t sum = 0 ; 6 7 8 9 f o r ( m u l t i s e t <i n t > : : i t e r a t o r i = M. b e g i n ( ) ; i != M. end ( ) ; i ++) sum += ∗ i ; 121 sum %= n_squared ; 10 11 r e t u r n sum ; 12 13 } 14 15 S i m u l a t i o n : : S i m u l a t i o n ( Digraph ∗G, i n t s ) { 16 graph = G; 17 seed = s ; 18 n = G−>get_n ( ) ; 19 n_squared = n ∗ n ; 20 21 i n t ∗ o l d _ l a b e l = new i n t [ n ] , ∗ l a b e l = new i n t [ n ] ; 22 23 // I n i c i a l i z a t o d o s o s r o t u l o s com 0 e o r o t u l o de ’ s e e d ’ com 1 24 f o r ( i n t i = 0 ; i < n ; i ++) 25 26 old_label [ i ] = 0; old_label [ seed ] = 1; 27 28 // C r i a uma p a r t i c a o ordenada u n i t a r i a contendo números de 1 a n 29 P a r t i t i o n ∗P = new P a r t i t i o n ( n ) ; 30 31 // R e f i n a a p a r t i c a o ordenada P de a c o r d o com o s r o t u l o s 32 P−>r e f i n e _ o r d e r e d _ p a r t i t i o n _ u s i n g _ l a b e l s ( o l d _ l a b e l ) ; iniciais 33 34 f o r ( bool s t a b i l i z e d = f a l s e ; ! s t a b i l i z e d ; ) { 35 st a bi li zed = true ; 36 f o r ( i n t i = 0 ; i < n ; i ++) { 37 m u l t i s e t <i n t > n e i g h b o r h o o d ; 38 neighborhood . i n s e r t ( old _ lab el [ i ] ) ; 39 l i s t <i n t > &n e i g h b o r s = G−>a d j [ i ] ; 40 41 f o r ( l i s t <i n t > : : i t e r a t o r j = n e i g h b o r s . b e g i n ( ) ; j != n e i g h b o r s . end ( ) ; j ++) Capítulo 9. Anexo 122 neighborhood . i n s e r t ( old _ lab el [ ∗ j ] ) ; 42 l a b e l [ i ] = lab el_ fu n ct ion ( neighborhood ) ; 43 } 44 45 46 // R e f i n a a p a r t i c a o ordenada P de a c o r d o com o s novos r o t u l o s 47 s t a b i l i z e d = ! ( P−>r e f i n e _ o r d e r e d _ p a r t i t i o n _ u s i n g _ l a b e l s ( l a b e l ) ) ; 48 49 i n t ∗tmp = o l d _ l a b e l ; 50 old_label = label ; 51 l a b e l = tmp ; } 52 53 54 delete [ ] old_label ; 55 delete [ ] label ; 56 p a r t i t i o n = P; 57 58 } 59 60 61 Simulation : : ~ Simulation () { /∗ Ver porque e s t a assim , e pq e ’ v i r t u a l ’ . . . ∗/ 62 63 } 64 65 Partition ∗ Simulation : : get_partition () { return partition ; 66 67 } codigo/simulation.cpp