grafos - 1 grafos ografo g = (v, e) v conjunto de vértices e conjunto de arestas (ou arcos) - cada...
TRANSCRIPT
Grafos - 1
Grafos
Grafo G = (V, E)
• V — conjunto de vértices
• E — conjunto de arestas (ou arcos)
- cada aresta é um par de vértices (v, w), em que v, w V
- se o par for ordenado, o grafo é dirigido, ou digrafo
- um vértice w é adjacente a um vértice v se e só se (v, w) E
- num grafo não dirigido com aresta (v, w) e, logo, (w, v) w é adjacente a v e v adjacente a w
- as arestas têm por vezes associado um custo ou peso
1 2
3 4 5
6 7
1 2
3 4 5
6 7
G1= (Cruzamentos, Ruas) G2 = (Cidades, Estradas)
Grafos - 2
Mais definições
caminho — sequência de vértices v1, v2, …, vn tais que (vi, vi+1) E, 1 i <n• comprimento do caminho é o número de arestas, n-1
- se n = 1, o caminho reduz-se a um vértice v1; comprimento = 0• anel — caminho v, v (v, v) E , comprimento 1; raro
• caminho simples — todos os vértices distintos excepto possivelmente o primeiro e o último
ciclo — caminho de comprimento 1 com v1 = vn• num grafo não dirigido requer-se que as arestas sejam diferentes
• DAG — grafo dirigido acíclico conectividade
• grafo não dirigido é conexo sse houver um caminho a ligar qualquer par de vértices• digrafo com a mesma propriedade — fortemente conexo
• digrafo fracamente conexo — não fortemente conexo; grafo subjacente conexo densidade
• grafo completo — existe uma aresta entre qualquer par de nós• grafo denso — |E| = (V2)
• grafo esparso — |E| = (V)
Grafos - 3
Representação
matriz de adjacências
• a[u][v] = 1 sse (u, v) E• elementos da matriz podem ser os pesos (sentinelas indicam não aresta)• apropriada para grafos densos• 3000 cruzamentos e 12 000 troços de ruas (4 por cruzamento)
- 9 000 000 de elementos na matriz!
1 2 3 4 5 6 7
1 0 1 1 1 0 0 0
2 0 0 0 1 1 0 0
3 0 0 0 0 0 1 0
4 0 0 1 0 0 1 1
5 0 0 0 1 0 0 1
6 0 0 0 0 0 0 0
7 0 0 0 0 0 1 0
Grafos - 4
Lista de adjacências
estrutura típica para grafos esparsos• para cada vértice, mantém-se a lista dos vértices adjacentes• vector de cabeças de lista, indexado pelos vértices• espaço é O(|E| + |V|)• pesquisa dos adjacentes em tempo proporcional ao número destes
grafo não dirigido: matriz simétrica; lista com o dobro do espaço
21
2
3
4
5
6
7
34
54
6
6 37
74
6
Grafos - 5
Arestas
class Edge{ public Vertex dest; // Second vertex in Edge public double cost; // Edge cost public Edge( Vertex d, double c ) { dest = d; cost = c; }}
Grafos - 6
Vérticesclass Vertex
{
public String name; // Vertex name
public List adj; // Adjacent vertices
public double dist; // Cost
public Vertex prev; // Previous vertex on shortest path
public int scratch;// Extra variable used in algorithm
public Vertex( String nm )
{ name = nm; adj = new LinkedList( ); reset( ); }
public void reset( )
{ dist = Graph.INFINITY; prev = null; pos = null; scratch = 0; }
public PriorityQueue.Position pos; // Used for dijkstra2 (Chapter 23)
}
Grafos - 7
Ordenação topológica
• impossível se o grafo for cíclico
• não é necessariamente única
(1 2 5 4 3 7 6) ou (1 2 5 4 7 3 6) no exemplo anterior
algoritmo simples:
- descobrir um vértice sem arestas de chegada
- imprimir o vértice
- eliminá-lo e às arestas que dele saem
- repetir o processo no grafo restante
• Indegree(v) — é o número de arestas (w, v)
• passagem sequencial do vector é O(|V|); com |V| chamadas: tempo é O( |V|2 )
Ordenação dos vértices de um DAG tal que, se existe um caminho de v para w, então v aparece antes de w
Ordenação dos vértices de um DAG tal que, se existe um caminho de v para w, então v aparece antes de w
Grafos - 8
Versão ineficientevoid topsort()throws CycleFound
{Vertex v, w;for(int conta = 0; conta <= NUM_VERTEX; conta ++){
v = novo_Vertice_Indegree_Zero();if( v == null )
throw new CycleFound();v.topNum = conta;for each w adjacent to v
w.indegree--;}
}
Grafos - 9
Refinamento da ordenação topológica
melhoria: em cada iteração, colocar numa fila (ou pilha) os vértices com indegree=0
• em cada passo, é retirado da fila um qualquer dos vértices presentes
• ao actualizar o indegree na lista de adjacências do vértice a eliminar colocam-se na fila os que passam a ter indegree=0
• inicialização põe na fila os vértices com indegree=0 à partida
• tempo de execução O(|E| + |V|)
- o corpo do ciclo de actualização do indegree é executado no máximo uma vez por aresta
- as operações na fila são executadas no máximo uma vez por vértice
- a inicialização leva um tempo proporcional ao tamanho do grafo
Grafos - 10
Algoritmo refinado
void topsort ()throws CycleFound{
int counter = 0;Vertex v, w;Queue q;
q= new Queue();for each vertex vif ( v.indegree == 0 )
q.enqueue( v );
while( !q.isEmpty() ){
v = q.dequeue();v.topNum = ++counter;
for each w adjacent to vif( --w.indegree == 0 )q.enqueue( w );
}if( counter != NUM_VERTEX )
throw new CycleFound(); }
Grafos - 11
Execução no grafo de exemplo
indegree anterior a cada operação dequeue
Vértice 1 2 3 4 5 6 7
v1 0 0 0 0 0 0 0
v2 1 0 0 0 0 0 0
v3 2 1 1 1 0 0 0
v4 3 2 1 0 0 0 0
v5 1 1 0 0 0 0 0
v6 3 3 3 3 2 1 0
v7 2 2 2 1 0 0 0
enqueue v1 v2 v5 v4 v3,v7 v6
dequeue v1 v2 v5 v4 v3 v7 v6
Grafos - 12
Caminho mais curto
Dado um grafo pesado G = (V, E) e um vértice s, obter o caminho pesado mais curto de s para cada um dos outros vértices em G
Dado um grafo pesado G = (V, E) e um vértice s, obter o caminho pesado mais curto de s para cada um dos outros vértices em G
Exemplo: rede de computadores, com custo de comunicação e de atraso dependente do encaminhamento (o caminho mais curto de v7 para v6 tem custo 1)
• arestas com custo negativo complicam o problema
• ciclos com custo negativo tornam o caminho mais curto indefinido (de v4 a v7 o custo pode ser 2 ou -1 ou -7 ou …)
Outro exemplo: se o grafo representar ligações aéreas, o problema típico poderá ser: Dado um aeroporto de partida obter o caminho mais curto para um destino
• não há algoritmo que seja mais eficiente
a resolver este problema do que a resolver
o mais geral
1 2
3 4 5
6 7
1
2
34
5
6
-101
6
2
21
Grafos - 13
1 - Caminho não pesado
pretende-se o comprimento dos caminhos: pode ser visto como um caso particular em que o peso de cada aresta é unitário
• começa-se por marcar o vértice inicial s com comprimento 0
• sucessivamente, passa-se aos que lhe estão adjacentes e marcam-se com mais 1 do que o valor do caminho até ao antecedente
• progride-se por níveis, passando ao nível seguinte só depois de ter esgotado o anterior
este tipo de pesquisa em grafos designa-se por pesquisa em largura
• semelhante à travessia por níveis de uma árvore
código usa uma tabela em que regista, para cada vértice v
- a distância de cada vértice ao inicial (dist)
- se o vértice já foi processado (known)
- qual o antecessor no caminho mais curto (path)
Grafos - 14
Evolução da marcação do grafo
v1 v2
v3 v4 v5
v6 v7
v1 v2
v3 v4 v5
v6 v7
v1 v2
v3 v4 v5
v6 v7
0 0
1
1
0
1
1
2
2 v1 v2
v3 v4 v5
v6 v7
0
1
1
2
2
3
3
Grafos - 15
Algoritmo básico
void unweighted( Vertex s){
Vertex v, w;
s.dist = 0;for(int currDist = 0; currDist < NUM_VERTEX; currDist++)
for each vertex vif( !v.known && v.dist == currDist ){
v.known = true;for each w adjacent to v
if( w.dist == INFINITY ){
w.dist = currDist + 1;w.path = v;
}}
}
Grafos - 16
Eficiência do algoritmo básico
tempo de execução O(|V|^2), devido aos ciclos for encaixados remoção da ineficiência semelhante à da ordenação topológica
• em cada momento, só existem dois tipos de vértices não processados com
Dist - os do nível corrente (dist = currDist) ainda não processados e os
adjacentes a estes já marcados no nível seguinte
(dist=currDist+1)
• podiam guardar-se em duas caixas diferentes mas, como só se marca o
primeiro do nível seguinte depois de ter todos os do nível corrente, basta
usar uma fila
• o atributo known não é usado nesta solução
Grafos - 17
Algoritmo refinado
tempo de execução é O(|E| + |V|), com grafo representado por lista de adjacências
void unweighted( Vertex s){
Vertex v, w;Queue q;
q= new Queue();q.enqueue (s); s.dist = 0;
while( !q.isEmpty() ){
v = q.dequeue();v.known = true; //agora desnecessáriofor each w adjacent to v
if( w.dist == INFINITY ){
w.dist = v.dist + 1;w.path = v;q.enqueue( w );
}}}
Grafos - 18
Evolução da estrutura de dados
v
v1 0 0 0 1 v3 1 1 v3 1 1 v3
v2 0 0 0 0 0 2 v1 0 2 v1
v3 0 0 0 1 0 0 1 0 0 1 0 0
v4 0 0 0 0 0 2 v1 0 2 v1
v5 0 0 0 0 0 0 0 0
v6 0 0 0 1 v3 0 1 v3 1 1 v3
v7 0 0 0 0 0 0 0 0 Q v3 v6, v2, v4 v2, v4v1, v6
Início Visita v3 Visita v1 Visita v6 known dv pv known dv pv known dv pv known dv pv
Grafos - 19
Evolução da estrutura de dados
v
v1 1 1 v3 1 1 v3 1 1 v3 1 1 v3
v2 1 2 v1 1 2 v1 1 2 v1 1 2 v1
v3 1 0 0 1 0 0 1 0 0 1 0 0
v4 0 2 v1 1 2 v1 1 2 v1 1 2 v1
v5 0 3 v2 0 3 v2 1 3 v2 1 3 v2
v6 1 1 v3 1 1 v3 1 1 v3 1 1 v3
v7 0 0 0 3 v4 0 3 v4 0 3 v4 Q v7 (vazia)v5, v7
Visita v4 Visita v5 Visita v7 Known dv pv Known dv pv Known dv pv Known dv pv
v4, v5
Visita v2
Grafos - 20
2 - Caminho pesado a solução é uma modificação da anterior
• cada vértice mantém uma distância ao inicial, obtida somando pesos nos caminhos
• quando se declara um vértice known , exploram-se os seus adjacentes; se o caminho através deste nó é melhor que o já registado, modifica-se este
• distância corrente em cada vértice: a melhor usando apenas vértices já processados
• o ponto crucial: escolher para declarar known o vértice que tiver o menor custo até ao momento
• é o único cujo custo não pode diminuir
• todas as melhorias de caminhos que usam este vértice são exploradas
este é um exemplo de um algoritmo ganancioso: em cada passo faz o que melhora o ganho imediato
restrição: só é válido se não existirem custos negativos
regista-se o vértice antecedente, responsável directo pelo custo estimado; seguindo a sequência recupera-se o caminho mínimo
Grafos - 21
Evolução do algoritmo de Dijkstra
v1 v2
v3v4 v5
v6 v7
4
2
10
6
1
5
1
3
2
4
8
2
0
1
2
3
59
3
9, 89, 8, 6
Grafos - 22
Estádios do algoritmo de Dijkstra
v1 v2
v3 v4 v5
v6 v7
4
2
10
6
1
5
13
24
8
2
v1 v2
v3 v4 v5
v6 v7
4
2
10
6
1
5
13
24
8
2
v1 v2
v3 v4 v5
v6 v7
4
2
10
6
1
5
13
24
8
2
v1 v2
v3 v4 v5
v6 v7
4
2
10
6
1
5
13
24
8
2
Grafos - 23
Estádios do algoritmo de Dijkstra
v1 v2
v3 v4 v5
v6 v7
4
2
10
6
1
5
13
24
8
2
v1 v2
v3 v4 v5
v6 v7
4
2
10
6
1
5
13
24
8
2
v1 v2
v3 v4 v5
v6 v7
4
2
10
6
1
5
13
24
8
2
v1 v2
v3 v4 v5
v6 v7
4
2
10
6
1
5
13
24
8
2
Grafos - 24
Evolução da estrutura de dados
v
v1 0 0 0 1 0 0 1 0 0 1 0 0
v2 0 0 0 2 v1 0 2 v1 1 2 v1
v3 0 0 0 0 0 3 v4 0 3 v4
v4 0 0 0 1 v1 1 1 v1 1 1 v1
v5 0 0 0 0 0 3 v4 0 3 v4
v6 0 0 0 0 0 9 v4 0 9 v4
v7 0 0 0 0 0 5 v4 0 5 v4
Início Visita v1 Visita v4 Visita v2 known dv pv known dv pv known dv pv known dv pv
Grafos - 25
Evolução da estrutura de dados
v
v1 1 0 0 1 0 0 1 0 0 1 0 0
v2 1 2 v1 1 2 v1 1 2 v1 1 2 v1
v3 0 3 v4 1 3 v4 1 3 v4 1 3 v4
v4 1 1 v1 1 1 v1 1 1 v1 1 1 v1
v5 1 3 v4 1 3 v4 1 3 v4 1 3 v4
v6 0 9 v4 0 8 v3 0 6 v7 1 6 v7
v7 0 5 v4 0 5 v4 1 5 v4 1 5 v4
Visita v3 Visita v7 Visita v6 known dv pv known dv pv known dv pv known dv pv
Visita v5
Grafos - 26
Algoritmo de Dijkstra
void Dijkstra( Vertex s){
Vertex v, w;
s.dist = 0;for( ; ; ){
v = vertice_a_menor_distancia;if( v == null )
break;v.known = true;for each w adjacent to v
if( !w.known )if v.dist + c(v,w) < w.dist ){ w.dist = v.dist + c(v,w);w.path = v;}
}}
Grafos - 27
Análise do algoritmo problema: pesquisa do mínimo
• método de percorrer a tabela até encontrar o mínimo é O(|V|) em cada fase; gasta-se O(|V|2) tempo ao longo do processo
• tempo de corrigir a distância é constante por actualização e há no máximo uma por aresta, num total de O(|E|)
• tempo de execução fica O(|E| + |V|2) = O(|V|2)
• se o grafo for denso |E| = (|V|2) e o resultado é satisfatório pois corre em tempo linear no número de arestas
• se o grafo fôr esparso |E| = (|V|), o algoritmo é demasiado lento
melhoria: manter as distâncias numa fila de prioridade para obter o mínimo eficientemente O(log |V|), com uma operação deleteMin
• como as distâncias vão sendo alteradas no processo e a operação de Busca é ineficiente nas filas de prioridade, pode-se meter na fila mais do que um elemento para o mesmo vértice, com distâncias diferentes, e ter o cuidado, ao apagar o mínimo, de verificar se o vértice já está processado
O(|E| log |V|) actualização dos pesos com operação decreaseKey na fila
O(|V| log |V|) percorrer os vértices com operação deleteMin para cadaTempo de execução total: O(|E| log |V|)
Grafos - 28
3 - Arestas com custos negativos
Algoritmo de Dijkstra não funciona
• custo ao longo de um caminho não é monótono
• depois de se marcar um vértice como processado pode aparecer um caminho mais longo mas com custo inferior
Combinar os algoritmos para os caminhos pesado e sem peso
• usar uma fila; colocar o vértice inicial
• em cada passo
- retirar um vértice v da fila
- para cada vértice w adjacente a v tal que dist(w) dist(v) + cost(v, w) actualizar dist(w), path(w) e colocar w na fila, se lá não estiver
- manter uma indicação de presença na fila
Grafos - 29
Exemplo: custos negativos
1 2
3 4 5
6 7
2
5
-24
2
6
101
8
2
41
Achar os caminhosde menor custo acomeçar em 1.
0
1
21 2
3 4 5
6 7
2
5
-24
2
6
101
8
2
41
0
Dijkstra 0
1
21 2
3 4 5
6 7
2
5
-24
2
6
101
8
2
41
3
9 5
3
2
vértice 2 nãoaltera nada …
seria necessário rever 4e propagar as alterações;piora o tempo …
01 2
3 4 5
6 7
2
5
-24
2
6
101
8
2
41
2
8 4
2pretendido:
Grafos - 30
Algoritmo com custo negativo
• pode ser necessário processar cada vértice mais do que uma vez (max: |V|)
• actualização pode ser executada O(|E|.|V|), usando listas de adjacência
void weightedNegative( Vertex s){
Vertex v, w;Queue q;
q = new Queue();q.enqueue (s);while( !q.isEmpty() ){
v = q.dequeue();
for each w adjacent to vif v.dist + c(v,w) < w.dist )
{ w.dist = v.dist + c(v,w);w.path = v;
if(w not in q) )q.enqueue(w);
}}
}
• ciclo de custo negativo algoritmo não termina
• teste de terminação: algum vértice sai da fila mais do que |V|+1 vezes
Grafos - 31
4 - Grafos acíclicos simplificação do algoritmo de Dijkstra
• exigência de selecção, em cada passo, do vértice mínimo é dispensável
• nova regra de selecção: usar a ordem topológica
• um vértice processado jamais pode vir a ser alterado: não há ramos a entrar
• não é necessária a fila de prioridade
• ordenação topológica e actualização das distâncias combinadas numa só passagem
aplicações em processos não reversíveis
• não se pode regressar a um estado passado (certas reacções químicas)
• deslocação entre dois pontos em esqui (sempre descendente)
aplicações de Investigação Operacional
• modelar sequências de actividades em projectos• grafos nó-actividade
- nós representam actividades e respectiva duração
- arcos representam precedência (um arco de v para w significa que a actividade em w só pode ser iniciada após a conclusão da de v) acíclico
Grafos - 32
Grafos Nó-Actividade
Qual a duração total mínima do projecto?
Quais as actividades que podem ser atrasadas e por quanto tempo
(sem aumentar a duração do projecto)?
A(3)
D(2)
E(1)
C(3)
B(2)
F(3)
G(2)
K(4)
H(1)início fim
Nó: actividade e tempo associadoArco: precedência
Grafos - 33
Reformulação em Grafo Nó-Evento
Nó: evento - completar actividadeArco: actividade
• reformulação introduz
nós e arcos extra para garantir precedências
6
5
4
7’
8’
9
10’1 6’
3
2
10
7
8
A/3
B/2
C/3
E/1
D/20
0
0
0
0
0
F/3
G/2
0
00
H/1
K/4
Grafos - 34
Menor Tempo de Conclusão
• menor tempo de conclusão de uma actividade caminho mais comprido do evento inicial ao nó de conclusão da
actividade• problema (se grafo não fosse acíclico): ciclos de custo positivo• adaptar algoritmo de caminho mais curto• MTC(1) = 0
MTC(w) = max( MTC(v) + c(v,w) ) (v, w) E
6
5
4
7’
8’
9
10’1 6’
3
2
10
7
8
A/3
B/2
C/3
E/1
D/20
0
0
0
0
0
F/3
G/2
0
00
H/1
MTC : usar ordem topológica
0
3
2 3 7
6
5
6
5
9
7
9 10
K/4
3
Grafos - 35
Último Tempo de Conclusão
• último tempo de conclusão: mais tarde que uma actividade pode terminar sem comprometer as que se lhe seguem
• UTC(n) = MTC(n)UTC(v) = min( UTC(w) - c(v, w) ) (v, w) E
UTC : usar ordem topológica inversa
6
5
4
7’
8’
9
10’1 6’
3
2
10
7
8
A/3
B/2
C/3
E/1
D/20
0
0
0
0
0
F/3
G/2
0
00
H/10
3
2 3 7
6
5
6
5
9
7
9 10
K/4
3
0
3
4
6
6
5
6
7
9
9
9
9 104
valores calculados em tempo linear mantendo listas de adjacentes e de precedentes dos nós
Grafos - 36
Folgas nas actividades
• folga da actividadefolga(v,w) = UTC(w)-MTC(v)-c(v,w)
6
5
4
7’
8’
9
10’1 6’
3
2
10
7
8
A/3/0
B/2/2
C/3/0
E/1/2
D/2/1
F/3/0
G/2/2
H/1/00
3
2 3 7
6
5
6
5
9
7
9 10
K/4/2
3
0
3
4
6
6
5
6
7
9
9
9
9 104
Caminho crítico: só actividades de folga nula (há pelo menos 1)