Sie sind auf Seite 1von 26

Parte 4: Grafos

GA-024

Antônio Tadeu A. Gomes, D.Sc.

atagomes@gmail.com
http://wiki.martin.lncc.br/atagomes-cursos-lncc-ga024-20102
Sala 2C-01

março-maio/2010

L
N
C
C
Grafos

● Definições básicas

● Formas de representação

● Busca em profundidade

● Busca em largura

● Problemas clássicos

L
N
C
C
Definições básicas (I)
● Muitas aplicações necessitam considerar conjuntos
de conexões entre pares de objetos:
– Máquinas de busca (existe um caminho para ir de uma
página Web a outra seguindo os links?)
– Alocação de tarefas (qual a melhor forma de distribuir
tarefas computacionais intercomunicantes entre
diferentes recursos computacionais?)
– Determinação de propriedades topológicas de sistemas
complexos (qual o padrão de interconexão das
máquinas na Internet?)

● Todos esses problemas podem ser modelados


como grafos
L
N
C
C
Definições básicas (II)
● Notação: G=(V,A)
– V: conjunto de vértices
– A: conjunto de arestas

L
N
C
C
Definições básicas (III)
● Grafos direcionados:
– Uma aresta do conjunto A
é representada como (u,v)
● Diz-se que
v é adjacente a u
– Arestas do tipo (u,u) são
chamadas de self-loops

● Grafos não direcionados:


– Arestas (u,v) e (v,u) são
consideradas a mesma
aresta (relação simétrica)
– Não há self-loops
L
N
C
C
Definições básicas (IV)
● Grau de um vértice:
– Grafos não direcionados: número de arestas que
incidem sobre o vértice
● Vértice de grau zero é dito isolado ou não conectado
● Ex. (slide anterior): vértice 1, grau 2; vértice 3, grau 0 (isolado)

– Grafos direcionados: número de arestas que saem do


vértice (out-degree) mais número de arestas que
chegam nele (in-degree)
● Ex.(slide anterior): vértice 2 tem in-degree 2, out-degree 2 e
grau 4

L
N
C
C
Definições básicas (V)
● Caminho entre vértices:
– Um caminho c de comprimento k de um vértice x a um
vértice y em um grafo G=(V,A) é uma sequência de
vértices (v0,v1,v2,...,vk) tal que x=v0 e y=vk, e (vi-1,vi) A
para i=1,2,...,k
● Comprimento determinado pelo no. de arestas do caminho
– Se existir um caminho c de x a y então y é alcançável a
partir de x via c
– Um caminho c é simples se todos os vértices de c são
distintos

L
N
C
C
Definições básicas (VI)
● Ciclos em um grafo direcionado:
– Um caminho c = (v0,v1,v2,...,vk) forma um ciclo se v0 = vk
e c contém ao menos uma aresta
– Um ciclo é simples se v0,v1,v2,...,vk-1 são distintos
– O self-loop é um ciclo de comprimento 1
– Dois caminhos (v0,v1,v2,...,vk) e (v'0,v'1,v'2,...,v'k)
formam o mesmo ciclo se
existir um inteiro j tal que
v'i = v(i+j)mod k para i=0,1,...,k-1
● Ex.: o caminho (0,1,3,0) forma
o mesmo ciclo que (1,3,0,1)
L
N
C
C
Definições básicas (VII)
● Ciclos em um grafo não direcionado:
– Um caminho c = (v0,v1,v2,...,vk) forma um ciclo se v0 = vk
e c contém ao menos 3 arestas
– Um ciclo é simples se v0,v1,v2,...,vk-1 são distintos
● Ex.: o caminho (0,1,2,0) é um ciclo (simples)

L
N
C
C
Definições básicas (VIII)
● Componentes conectados:
– Um grafo não direcionado é conectado se cada par de
vértices está conectado por um caminho
– Os componentes conectados são as porções
conectadas de um grafo
– Um grafo não direcionado é conectado se ele tem
exatamente um componente conectado
– Ex.: os componentes
são: {0, 1, 2}, {4, 5} e {3}.

L
N
C
C
Definições básicas (IX)
● Componentes fortemente conectados
– Os componentes fortemente conectados de um grafo
direcionado são conjuntos de vértices sob a relação
“são mutuamente alcançáveis”
– Um grafo direcionado fortemente conectado tem
apenas um componente fortemente conectado
● Ex.: {0, 1, 2, 3}, {4} e {5}
são os componentes
fortemente conectados,
mas {4, 5} não o é pois
o vértice 5 não é
alcançável a partir do
vértice 4
L
N
C
C
Definições básicas (X)
● Outras definições:
– Grafos isomorfos
– Subgrafos
– Grafos ponderados (pesos associados a arestas)
– Hipergrafos
– Árvores (grafos acíclicos!)
– Árvores geradoras
...

L
N
C
C
Formas de representação (I)
● TAD “Graph” (vértices identificados por no. inteiro)
– Estrutura:
● Matrizes de adjacência
● Listas de adjacências usando listas encadeadas
● Listas de adjacências usando vetores
– Operações principais:
● Graph *graph_createEmpty(): cria grafo vazio
● Graph *graph_create( int n ): cria com n vértices (ids 0 a n-1) e 0 arestas
● void graph_addVertex( Graph *g, int v )
● void graph_removeVertex( Graph *g, int v ): remove tb arestas associadas
● int graph_hasVertex( Graph *g, int v )
● int graph_getVertexCount()
● void graph_addEdge( Graph *g, int v1, int v2, int weight )
● void graph_removeEdge( Graph *g, int v1, int v2 )
● int graph_hasEdge( Graph *g, int v1, int v2 )
● int graph_getEdgeWeight( Graph *g, int v1, int v2 )
● int graph_getAdjacentVertices( Graph *g, int v, int **a ): lista de vértices
adjacentes a v devolvida em a (alocada dinamicamente) ou NULL, e
retorno é o tamanho dessa lista L
N
C
C
Formas de representação (II)
● Representação por matrizes de adjacência:
– OBS: operações addVertex() e removeVertex() não se aplicam
– Código void graph_addEdge( Graph *g, int v1, int v2, int w ) {
struct graph {
int *edges;
p/ grafo int k = v1*g->num_vertices+v2;
int num_vertices;
g->edges[k] = weight;
orientado: } }

typedef struct graph Graph;


void graph_removeEdge( Graph *g, int v1, int v2 ) {
int k = v1*g->num_vertices+v2;
g->edges[k] = 0;
}

Graph *graph_create( int n ) {


Graph *g = (Graph*)malloc(sizeof(Graph));
g->edges = (int*)malloc(n*n*sizeof(int));
for( int i=0; i<n; i++)
for( int j=0; j<n; j++) {
int k = i*n+j;
g->edges[k] = 0; /* ausência de aresta */
};
g->num_vertices = n;
return g;
}
L
N
C
C
Formas de representação (III)
● Representação por listas de adjacências usando listas
encadeadas:
– OBS: implementação das operações addVertex() e
removeVertex() demanda substituição do vetor de vértices
por outra lista encadeada

L
N
C
C
Busca em profundidade (I)
● DFS (Depth-first search): explora arestas a partir
do vértice v mais recentemente descoberto que
ainda possui arestas não exploradas saindo dele
– Quando todas as arestas adjacentes a v tiverem sido
exploradas, a busca anda para trás (backtracking) para
explorar vértices que saem do vértice a partir do qual v
foi descoberto

● DFS é a base para vários algoritmos importantes,


como verificação de ciclos, ordenação topológica e
detecção de componentes fortemente conectados
L
N
C
C
Busca em profundidade (II)
● DFS se baseia na “coloração” dos vértices
conforme eles vão sendo explorados
– Branco: vértice nunca explorado
– Cinza: vértice descoberto
– Preto: vértice cuja lista de adjacentes foi completamente
examinada

● Registros auxiliares de tempo de descoberta (dv) e


de tempo de término do exame da lista de
adjacentes (tv) são associados ao vértice v
– Necessários para alguns algoritmos baseados no DFS
L
N
C
C
Busca em
profundidade (III)
● Ex.:

L
N
C
C
Busca em profundidade (IV)
struct dfs_graph {
● Código em C da DFS: int *vertices;
int num_vertices;
– Quatro novas operações: int *d;
● {get,set}DfsEndTime(Graph *g, int v) int *t;
● {get,set}DfsDiscoveryTime(Graph *g, int v) }
typedef struct dfs_graph Graph;
void dfsVisit( Graph *g, int u, int total_t, int *color ) {
color[u] = GRAY;
#define WHITE 0 graph_setDfsDiscoveryTime(g, u, ++total_t);
#define GRAY 1 int *a = NULL;
#define BLACK 2 int n = graph_getAdjacentVertices(g, u, &a);
if(a != NULL ) {
void graph_dfs( Graph *g ) { for(int i=0; i<n; i++) {
int total_t = 0; int v = a[i]; /* i-ésimo adjacente de u */
int n = graph_getVerticesCount(g); if(color[v] == WHITE)
int *color = (int*)malloc(n*sizeof(int)); total_t = dfsVisit(g, v, total_t, color);
for(int i=0; i<n; i++) }
color[i] = WHITE; free(a);
for(int u=0; u<n; u++) }
if(color[u] == WHITE) color[u] = BLACK;
total_t = dfsVisit(g, u, total_t, color); graph_setDfsEndTime(g, u, ++total_t);
free(color); return total_t;
} }
L
N
C
C
Busca em profundidade (V)
● Como posso usar DFS para verificar se um grafo é
acíclico?

– Para grafos direcionados, basta verificar se um vértice v


adjacente a um vértice u apresenta a cor cinza na
primeira vez que (u,v) é percorrida

– E no caso de grafos não-direcionados?

● E para ordenar topologicamente um grafo


direcionado acíclico?
L
N
C
C
Busca em largura (I)
● BFS (Breadth-first search): explora arestas
descobrindo todos os vértices a uma distância k do
vértice origem (“fronteira”) antes de descobrir
qualquer vértice a uma distância k + 1

● BFS é base para vários outros algoritmos


importantes, como obtenção da árvore geradora
mínima (algoritmos de Prim e de Kruskal) e do
caminho mais curto de um vértice a todos os outros
(algoritmo de Dijkstra)

L
N
C
C
Busca em largura (II)
● BFS também se baseia na “coloração” dos vértices
– Branco: vértice nunca explorado
– Cinza: vértice descoberto
– Preto: vértice descoberto cujos adjacentes são todos
pretos ou cinzas
● Vértices cinzas podem ter adjacentes brancos, que
determinam a “fronteira” atual da descoberta
● Demanda uso de uma estrutura auxiliar de fila
(TAD “Queue”) para gerenciar vértices cinza
● Registros auxiliares de tempo de descoberta (dv) e
de antecessor (pv) são associados ao vértice v
– Necessários para alguns algoritmos baseados no BFS
L
N
C
C
Busca em largura (III)
● Ex.:

L
N
C
C
Busca em largura (IV)
● Código em C da BFS:
void bfsVisit( Graph *g, int u, int *color ) {
– Quatro novas operações: color[u] = GRAY;
● {get,set}BfsDiscoveryTime(Graph *g, int v) graph_setBfsDiscoveryTime(g, u, 0);
● {get,set}Predecessor(Graph *g, int v) Queue *q = queue_create();
queue_enqueue(q, u);
struct bfs_graph { while( !queue_empty(q) ) {
int *vertices; u = queue_dequeue(q);
int num_vertices; int *a = NULL;
int *d; int n = graph_getAdjacentVertices(g, u, &a);
int *p; if(a != NULL ) {
} for(int i=0; i<n; i++) {
typedef struct bfs_graph Graph; int v = a[i]; /* i-ésimo adjacente de u */
if(color[v] == WHITE) {
void graph_bfs( Graph *g ) { color[v] = GRAY;
int n = graph_getVerticesCount(g); graph_setBfsDiscoveryTime(g, v,
int *color = (int*)malloc(n*sizeof(int)); graph_getBfsDiscoveryTime(g, u) + 1);
for(int i=0; i<n; i++) { graph_setPredecessor(g, v, u);
color[i] = WHITE; queue_enqueue(g, v);
graph_setBfsDiscoveryTime(g, v, -1); }
graph_setPredecessor(g, v, -1); }
} free(a);
for(int u=0; u<n; u++) }
if(color[u] == WHITE) color[u] = BLACK;
bfsVisit(g, u, color); }
free(color); queue_destroy(q);
} }
L
N
C
C
Busca em largura (V)
● Como posso usar BFS para determinar o caminho
mais curto entre dois vértices?
– A função bfsVisit() contrói uma árvore de busca em
largura que é armazenada no vetor p
– O código abaixo imprime o caminho mais curto entre
dois vértices a partir do vetor p:
void print_shortest_path( Graph *g, int src, int dst ) {
if( src == dst )
printf(“%d”\n, src);
else if( graph_getPredecessor(g, dst) == -1 )
printf(“Nao existe caminho entre %d e %d\n”, src, dst);
else {
print_shortest_path( g, src, graph_getPredecessor(g, dst) );
printf(“%d\n”, dst);
}

L
N
C
C
Busca em largura (VI)
● Algoritmo de
Dijkstra (1959):
determinação do
caminho mais curto
entre um vértice de
origem e todos os
outros vértices do
grafo
– Implementação da
estrutura auxiliar
de fila como uma
fila de prioridades
(heap)

L
N
C
C

Das könnte Ihnen auch gefallen