Análise da complexidade temporal de BFS (1)
Grafo implementado através de listas de adjacências
BFS(G, s)
1 for each vertex u in G.V - {s} do
2 [Link] <- WHITE
3 u.d <- INFINITY // dist^
ancia para s
4 u.p <- NIL // antecessor no caminho
• Ciclo das linhas 1–4 é executado |V | − 1 vezes
5 [Link] <- GREY
6 s.d <- 0
7 s.p <- NIL
8 Q <- EMPTY // fila (FIFO)
9 ENQUEUE(Q, s)
• Linhas 5–9 com custo constante
Análise da complexidade temporal de BFS (2)
• Ciclo das linhas 10–18 é executado |V | vezes, no pior caso
10 while Q != EMPTY do
11 u <- DEQUEUE(Q) // próximo nó a explorar
12 for each vertex v in [Link][u] do
13 if [Link] = WHITE then
14 [Link] <- GREY
15 v.d <- u.d + 1
16 v.p <- u
17 ENQUEUE(Q, v)
18 [Link] <- BLACK // u foi explorado
• Mas o ciclo das linhas 12–17 é executado, no pior caso
X
| [Link][u] | = |E | (orientado) ou 2 × |E | (não orientado) vezes
u∈V
porque cada vértice só pode entrar na fila uma vez
Análise da complexidade temporal de BFS (3)
Considerando que todas as operações, incluindo a criação de uma
fila vazia, ENQUEUE e DEQUEUE, têm custo Θ(1)
▶ O ciclo das linhas 1–4 tem custo Θ(V )
▶ Conjuntamente, os ciclos das linhas 10–18 e 12–17 têm
custo O(E ) (pior caso)
Logo, a complexidade temporal de BFS é O(V + E )
Análise da complexidade temporal de BFS (4)
Grafo implementado através da matriz de adjacências
Na linha 12, é necessário percorrer uma linha da matriz, com |V |
elementos
12’ for each vertex v in G.V do
13’ if [Link][u,v] and [Link] = WHITE then
Como o ciclo das linhas 10–18 é executado |V | vezes, no pior caso,
o custo combinado dos dois ciclos é O(V 2 )
▶ Corresponde a aceder a todas as posições de uma matriz |V | × |V |
Neste caso, a complexidade temporal de BFS será O(V 2 )
Complexidade espacial de BFS
Memória usada pelo algoritmo
▶ 2 variáveis para vértices (u e v)
O(1)
▶ 3 valores escalares (color, d e p) por cada vértice
Θ(V )
▶ Uma fila, que poderá ter, no pior caso, |V | − 1 vértices
O(V )
A complexidade espacial de BFS é Θ(V )
BFS em Java (1)
public static final int INFINITY = Integer.MAX_VALUE;
public static final int NONE = -1;
private static enum Colour { WHITE, GREY, BLACK };
public int[] bfs(int s)
{
Colour[] colour = new Colour[nodes];
int[] d = new int[nodes]; // dist^
ancia para S
int[] p = new int[nodes]; // predecessor no caminho de S
for (int u = 0; u < nodes; ++u)
{
colour[u] = [Link];
d[u] = INFINITY;
p[u] = NONE;
}
colour[s] = [Link];
d[s] = 0;
Queue<Integer> Q = new LinkedList<>();
[Link](s);
...
BFS em Java (2)
...
while (![Link]())
{
int u = [Link](); // visita nó U
for (Integer v : adjacents[u])
if (colour[v] == [Link])
{
colour[v] = [Link]; // V é um novo nó
d[v] = d[u] + 1;
p[v] = u;
[Link](v);
}
colour[u] = [Link]; // nó U está tratado
}
return d ou p ou ...
}
Percurso em profundidade
DFS(G)
1 for each vertex u in G.V do
2 [Link] <- WHITE
3 u.p <- NIL
4 time <- 0 // variável global
5 for each vertex u in G.V do // explora todos os nós
6 if [Link] = WHITE then
7 DFS-VISIT(G, u)
DFS-VISIT(G, u)
1 time <- time + 1 // instante da descoberta do
2 u.d <- time // vértice u
3 [Link] <- GREY
4 for each vertex v in [Link][u] do // explora arco (u, v)
5 if [Link] = WHITE then
6 v.p <- u
7 DFS-VISIT(G, v)
8 [Link] <- BLACK // u foi explorado
9 time <- time + 1 // instante em que termina
10 u.f <- time // a exploraç~
ao de u
Percurso em profundidade
DFS(G)
1 for each vertex u in G.V do
2 [Link] <- WHITE
3 u.p <- NIL
4 time <- 0 // variável global
5 for each vertex u in G.V do // explora todos os nós
6 if [Link] = WHITE then
7 DFS-VISIT(G, u)
DFS-VISIT(G, u)
1 time <- time + 1 // instante da descoberta do
2 u.d <- time // vértice u
3 [Link] <- GREY
4 for each vertex v in [Link][u] do // explora arco (u, v)
5 if [Link] = WHITE then
6 v.p <- u
7 DFS-VISIT(G, v)
8 [Link] <- BLACK // u foi explorado
9 time <- time + 1 // instante em que termina
10 u.f <- time // a exploraç~
ao de u
Percurso em profundidade
Depth-first search
Constrói a floresta da pesquisa em profundidade (linhas 3 [DFS]
e 6 [DFS-VISIT])
Atributos dos vértices
color WHITE não descoberto
GREY descoberto e em processamento
BLACK processado
d instante em que foi descoberto
f instante em que terminou de ser processado
p antecessor do nó no caminho que levou à sua
descoberta
Análise da complexidade temporal de DFS
O ciclo das linhas 1–3 [DFS] é executado |V | vezes
DFS-VISIT é chamada para cada um dos |V | vértices
Para cada vértice u (e considerando a implementação através
de listas de adjacências), o ciclo das linhas 4–7 [DFS-VISIT] é
executado
| [Link][u] | vezes
Tendo todas as operações custo constante, considerando todas as
chamadas a DFS-VISIT, DFS corre em tempo
X
Θ(V + | [Link][u] |) = Θ(V + E )
u∈V
Complexidade espacial de DFS
Memória usada pelo algoritmo
▶ 4 valores escalares (color, d, f e p) por cada vértice
Θ(V )
▶ Uma pilha, que poderá ter, no pior caso, |V | vértices
O(V )
A pilha será implı́cita, quando algoritmo for implementado
recursivamente, ou explı́cita, quando for implementado
iterativamente
A complexidade espacial de DFS é Θ(V )
DFS em Java (1)
public int[] dfs()
{
Colour[] colour = new Colour[nodes];
int[] p = new int[nodes]; // predecessor no caminho de S
int[] d = new int[nodes]; // instante da descoberta
int[] f = new int[nodes]; // fim do processamento
int[] time = new int[1]; // instante DFS
for (int u = 0; u < nodes; ++u)
{
colour[u] = [Link];
p[u] = NONE;
}
time[0] = 0;
for (int u = 0; u < nodes; ++u)
if (colour[u] == [Link])
dfsVisit(u, colour, p, d, f, time);
return p ou { d, f } ou ...
}
DFS em Java (2)
private void dfsVisit(int u, Colour[] colour, int[] p, int[] d,
int[] f, int[] time)
{
time[0]++;
d[u] = time[0]; // descoberta de U
colour[u] = [Link];
for (Integer v : adjacents[u])
if (colour[v] == [Link])
{
p[v] = u; // U é o predecessor de V
dfsVisit(v, colour, p, d, f, time);
}
colour[u] = [Link]; // fim do processamento de U
time[0]++;
f[u] = time[0];
}
Complexidades dos percursos
Resumo
G = (V , E )
Complexidade
Temporal Espacial
Percurso em largura O(V + E ) O(V 2 ) Θ(V )
Percurso em profundidade Θ(V + E ) Θ(V 2 ) Θ(V )
Listas de Matriz de
adjacências adjacências
Se, no percurso em largura, for percorrido todo o grafo (como é feito no
percurso em profundidade), as complexidades temporais também serão
Θ(V + E ) e Θ(V 2 ), respectivamente
Se o percurso em profundidade for feito a partir de um único nó (como
no percurso em largura), as complexidades temporais também serão
O(V + E ) e O(V 2 ), respectivamente
Ordenação topológica
Seja G = (V , E ) um grafo orientado acı́clico (DAG, de directed
acyclic graph)
Ordem topológica
Se existe um arco de u para v , u está antes de v na ordem dos
vértices
(u, v ) ∈ E ⇒ u < v
Exemplo
Ordem
A A<B A<D B<D C <D
Ordenações possı́veis
B C
▶ ABCD
D ▶ ACBD
▶ CABD
Ordenação topológica
Algoritmo baseado no percurso em profundidade
Aplicação do percurso em profundidade
▶ Se não há caminho de u para nenhum outro vértice, u poderá
ser o último vértice da ordenação topológica
▶ Se há um caminho de u para outro vértice v , então u estará
antes de v , em qualquer ordenação topológica
TOPOLOGICAL-SORT(G)
1 Aplicar DFS(G)
2 Durante o percurso, quando termina o processamento de um
vértice, inseri-lo à cabeça de uma lista
3 Devolver a lista, que contém os vértices por (alguma) ordem
topológica
Ordenação topológica
Adaptação de DFS
G = (V , E ) – grafo orientado acı́clico (DAG)
TOPOLOGICAL-SORT(G)
1 for each vertex u in G.V do
2 [Link] <- WHITE
3 L <- EMPTY // lista, global
4 for each vertex u in G.V do
5 if [Link] = WHITE then
6 DFS-VISIT’(G, u)
7 return L
DFS-VISIT’(G, u)
1 [Link] <- GREY
2 for each vertex v in [Link][u] do
3 if [Link] = WHITE then
4 DFS-VISIT’(G, v)
5 [Link] <- BLACK
6 LIST-INSERT-HEAD(L, u) // insere nó na ordenaç~
ao
Ordenação topológica
Algoritmo de Kahn (1)
▶ Se u não é o destino de nenhum arco, u pode ser o primeiro
nó da ordenação topológica
▶ Uma vez ordenados todos os vértices u tais que (u, v ) ∈ E , o
vértice v pode ser colocado a seguir
Ordenação topológica
Algoritmo de Kahn (2)
TOPOLOGICAL-SORT-KAHN(G)
1 for each vertex u in G.V do
2 u.i <- 0
3 for each edge (u,v) in G.E do
4 v.i <- v.i + 1 // no. de arcos incidentes em v
5 L <- EMPTY // nós já ordenados, lista
6 S <- EMPTY // possı́veis próximos nós, conjunto
7 for each vertex u in G.V do
8 if u.i = 0 then
9 SET-INSERT(S, u)
10 while S != EMPTY do
11 u <- SET-DELETE(S) // retira um nó qualquer de S
12 for each vertex v in [Link][u] do
13 v.i <- v.i - 1
14 if v.i = 0 then
15 SET-INSERT(S, v)
16 LIST-INSERT-TAIL(L, u) // acrescenta vértice à ordem
17 return L
Complexidade dos algoritmos
G = (V , E )
Compl. Temporal
Percurso em largura O(V + E )
Percurso em profundidade Θ(V + E )
Ordenação topológica (ambos os algoritmos) Θ(V + E )
Pressupostos
Grafo representado através de listas de adjacências
Se, no percurso em largura, for percorrido todo o grafo (como é feito
no percurso em profundidade), a complexidade temporal também será
Θ(V + E )
Conectividade (1)
Seja G = (V , E ) um grafo não orientado
G é conexo se existe algum caminho entre quaisquer dois nós:
u, v ∈ V ⇒ existe (pelo menos) um caminho v0 v1 . . . vk , k ≥ 0,
com v0 = u e vk = v
V ′ ⊆ V é uma componente conexa de G se
▶ existe algum caminho entre quaisquer dois nós de V ′ e
▶ não existe qualquer caminho entre algum nó de V ′ e algum nó
de V \ V ′
Conectividade (2)
Seja G = (V , E ) um grafo orientado
G é fortemente conexo se existe algum caminho de qualquer nó
para qualquer nó
V ′ ⊆ V é uma componente fortemente conexa de G se
▶ existe algum caminho de qualquer nó de V ′ para qualquer nó
de V ′ e
▶ se, qualquer que seja o nó u ∈ V \ V ′ ,
▶ não existe qualquer caminho de algum nó de V ′ para u ou
▶ não existe qualquer caminho de u para algum nó de V ′
G é simplesmente conexo se, substituindo todos os arcos por arcos
não orientados, se obtém um grafo conexo