1.4 Organiza¸c˜ao dessa disserta¸c˜ao
2.1.4 Estrutura de dados e algoritmos
A seguir s˜ao apresentadas duas estruturas de dados b´asicas para representa¸c˜ao de gra- fos em geral: (1) matriz de adjacˆencia e (2) listas de adjacˆencia. O emprego dessas
2.1. Princ´ıpios b´asicos da Teoria de Grafos 11
estruturas interfere diretamente no tempo de processamento dos m´etodos de compara- ¸c˜ao entre grafos. Posteriormente, algoritmos para pesquisa de v´ertices e computa¸c˜ao de caminhos m´ınimos no grafo s˜ao descritos e analisados. Esses algoritmos s˜ao extre-
mamente ´uteis e s˜ao utilizados por diversos m´etodos para c´alculo de similaridade entre
estruturas. 2.1.4.1 Estruturas
0 1 0 0 1
1 0 1 1 0
0
1
0
1
0
0
1
1
0
1
1
0
0
1
0
(a) Matriz de adjacˆencia
2 1 2 2 1 5 3 4 4 3 3 4 (b) Lista de adjacˆencia Figura 2.2: Estrutura de dados para grafos
Matriz de adjacˆencia e listas de adjacˆencia s˜ao estruturas de dados b´asicas usadas para representar grafos. A figura 2.2 ilustra como seriam as representa¸c˜oes para um mesmo grafo utilizando tais estruturas.
Como podemos perceber, a matriz de adjacˆencia (figura 2.2a) ´e uma estrutura
bastante simples. A estrutura consiste numa matriz N × N, onde N ´e um n´umero de
v´ertices do grafo. O valor 1 na posi¸c˜ao (i, j) da matriz nos diz que os v´ertices i e j est˜ao ligados. Note que, alternativamente, podemos usar as posi¸c˜oes da matriz para alocarmos os pesos ou r´otulos das arestas. S˜ao algumas das vantagens dessa estrutura de dados: verificar se dois v´ertices s˜ao adjacentes, adicionar ou remover liga¸c˜oes – complexidade O(1). Todavia, temos um desperd´ıcio de mem´oria, especialmente em grafos esparsos, pois, a matriz mant´em informa¸c˜oes sobre as arestas existentes e n˜ao existentes do grafo (valores 1 e 0, respectivamente). Al´em disso, para listar as arestas incidentes a um dado v´ertice devemos percorrer todos os v´ertices – complexidade O(N). Por outro lado, as listas de adjacˆencia limitam-se a guardar somente as informa- ¸c˜oes das arestas existentes do grafo, como mostrado na figura 2.2b. Duas vantagens importantes da representa¸c˜ao baseada em listas s˜ao: (1) verificar todas as arestas in-
cidentes num dado v´ertice pode ser feito em tempo linear em rela¸c˜ao ao n´umero de
arestas1 e (2) baixo desperd´ıcio de mem´oria. Apesar disso, representar um grafo denso
1
12 Cap´ıtulo 2. Conceitos preliminares e defini¸c˜oes
com o uso de listas de adjacˆencia ´e invi´avel, pois, pode consumir uma quantidade maior de mem´oria principal comparada ao uso de matriz. Ademais, o uso dessa representa¸c˜ao ´e limitado em cen´arios onde as estruturas dos grafos n˜ao s˜ao alteradas frequentemente – remover arestas do grafo possui custo linear.
2.1.4.2 Algoritmos
Descrevemos, a seguir, trˆes algoritmos fundamentais para an´alise de grafos. S˜ao eles : (1) busca em largura , (2) busca em profundidade e (3) algoritmo de Dijkstra.
Algoritmo 1: Busca em largura
Entrada: Um grafo G e o v´ertice v a ser examinado
Sa´ıda: Os v´ertices alcan¸cados a partir de v in´ıcio
para cada u ∈ V (G) − v fa¸ca
verif [u] = −1; verif [v] = 0; S = S ∪ v;
1 enquanto S 6= ∅ fa¸ca
Selecione u ∈ S de acordo com a ordem de inser¸c˜ao;
2 S = S − u para cada n ∈ N(u) fa¸ca
se verif [n] < 0 ent˜ao verif [n] = verif [u] + 1;
3 S = S ∪ n
O algoritmo 3 descreve o m´etodo de busca em largura (breadth-first search), utilizado para realizar buscas num grafo. Nesse m´etodo, a busca ´e iniciada a partir de um v´ertice raiz e expandida, incrementalmente, para a vizinhan¸ca. Por exemplo, dado um v´ertice v como raiz, o algoritmo explora primeiro os v´ertices mais pr´oximos de v, N(v), e, posteriormente, os vizinhos de N(v) (caso eles n˜ao tenham sido examinados). Esse processo ´e repetido at´e que o v´ertice alvo seja atingido ou todos os v´ertices do grafo sejam examinados. O algoritmo apresentado visita cada v´ertice e cada aresta no m´aximo uma vez – considerando a representa¸c˜ao baseada em listas de adjacˆencia. Assim, o algoritmo executa a busca num tempo de O(V + E), al´em da complexidade de espa¸co de O(V ).
Um m´etodo recursivo para busca de profundidade (depth-first search) ´e descrito no algoritmo 2. Tal como o algoritmo de busca em largura, o m´etodo de busca em
profundidade ´e usado para pesquisar v´ertices num grafo. Esse ´ultimo, por´em, emprega
2.1. Princ´ıpios b´asicos da Teoria de Grafos 13
Algoritmo 2:Busca em profundidade
Entrada: Um grafo G e o v´ertice v a ser examinado
Sa´ıda: Os v´ertices alcan¸cados a partir de v
Verifica(v´ertice u)
1 in´ıcio
para cada n ∈ N(u) fa¸ca
se verif [n] < 0 ent˜ao verif [n] = verif [u] + 1;
Verifica(n);
Busca ()
2 in´ıcio
para cada u ∈ V (G) − v fa¸ca
verif [u] = −1; verif [v] = 0;
Verifica(v);
tices mais pr´oximos. O algoritmo inicia-se examinando um v´ertice vizinho u de v, onde v ´e o v´ertice raiz. A seguir, um v´ertice vizinho de u, ainda n˜ao explorado, ´e selecionado. Caso o v´ertice corrente n˜ao possua vizinhos ou tenha toda sua vizinhan¸ca
analisada, voltamos para o ´ultimo v´ertice examinado com vizinhos inexplorados (pro-
cedimento conhecido como backtracking), sen˜ao continuamos a busca escolhendo um de seus vizinhos. Como no caso anterior, o algoritmo de busca em profundidade tem complexidades de espa¸co e tempo de O(V ) e O(V + E), respectivamente.
Algoritmo 3:Algoritmo de Dijkstra
Entrada: Um grafo G e o v´ertice v a ser examinado
Sa´ıda: Distancia m´ınima do v´ertice v ao demais v´ertices de G
in´ıcio
para cada u ∈ V (G) − v fa¸ca
dist[u] = ∞; dist[v] = 0; S = S ∪ v;
1 enquanto S 6= ∅ fa¸ca
Selecione u ∈ S com o menor dist;
2 S = S − u para cada n ∈ N(u) fa¸ca
se dist[n] > dist[u] + (u, n) ent˜ao dist[n] = dist[u] + (u, n);
14 Cap´ıtulo 2. Conceitos preliminares e defini¸c˜oes
O algoritmo de Dijkstra ´e um dos m´etodos mais famosos para calcular os caminhos de custo m´ınimo entre v´ertices de um grafo. Escolhido um v´ertice como raiz da busca, este algoritmo calcula o custo m´ınimo (ou distˆancia m´ınima) deste para todos os demais v´ertices do grafo. Inicialmente, todos os nodos tem um custo infinito, exceto v (a raiz da busca) que tem valor 0. A seguir, os vizinhos de v s˜ao examinados e, caso suas distˆancias para v sejam atualizadas (ou seja, distˆancias menores para v foram encontradas), eles s˜ao colocados em uma fila. O pr´oximo v´ertice a ser expandido ´e aquele cuja distˆancia para v ´e a menor poss´ıvel, dentre os v´ertices da fila. Uma vez expandido, o v´ertice ´e removido da fila. Esse processo continua enquanto existir v´ertice na fila. Note que o algoritmo de Dijkstra ´e similar `a estrat´egia de busca em largura,
por´em, ele emprega uma estrat´egia para selecionar o pr´oximo v´ertice a ser expandido2.
Dijkstra pode ser implementado de forma eficiente usando uma estrutura heap, sendo executado em O(|E|log|V |).