Algoritmo de Dijkstra
-
Upload
arnaldo-gomes -
Category
Documents
-
view
228 -
download
3
description
Transcript of Algoritmo de Dijkstra
1
ALGORITMO Dijkstra: implementação utilizando matriz de adjacências
Para esta primeira implementação assumimos um grafo G(veja figura abaixo)
representado por uma matriz de adjacências.
Representação Gráfica de G
Estrutura de dados para Matriz de Adjacências do Grafo G
#define INFINITY 32768
#define MEMBER 1
#define NONMEMBER 0
#define MAXNODES 50
typedef struct graph GRAPH;
struct arc {
// se adj=1 então ADJACENTE;
se adj=0 então NÃO ADJACENTE
int adj;
// peso da aresta
int weight;
};
struct graph {
// matriz de adjacências
struct arc
arcs[MAXNODES][MAXNODES];
};
Matriz de Adjacências de G
Cada vértice do grafo é representado por um valor inteiro entre 0 a
MAXNODES - 1. Um array bi-dimensional do tipo arc (encapsulado na struct
graph) define a matriz de adjacências do grafo. O campo adj na struct arc,
informa (armazena) a existência de uma relação de adjacência (adj = 1), ou
não (adj = 0) entre dois vértices da matriz. O campo weight armazena o peso
2
da aresta que estes mesmos dois vértices, quando adj = 1, ou um valor
bastante grande ("infinito") quando não há esta relação (adj = 0).
Procedimentos inicialização do grafo
// inicializa matriz de adjacência que representa o grafo
// retorna ponteiro para esta matriz (tipo GRAPH)
GRAPH* init_graph () {
GRAPH *gTemp = (GRAPH *) malloc(sizeof(GRAPH));
for (int i = 0; i < MAXNODES; i++) {
for (int j = 0; j < MAXNODES; j ++) {
gTemp->arcs[i][j].adj = 0;
gTemp->arcs[i][j].weight = INFINITY;
}
}
return gTemp;
}
A função init_graph deve ser chamada pelo usuário do tipo abstrato de
dados GRAPH para a inicialização da matriz de adjacências do grafo. Além de
alocar memória, a função inicializa todos os campos adj e weight do grafo
como 0 e "infinito" respectivamente.
Procedimentos para manutenção do grafo (matriz de adjacência)
// cria uma aresta que "liga" (incide) em dois nós e atribui o
respectivo peso;
// recebe o grafo, dois nós (node1 e node2) e o peso (wt) da aresta
void joinwt (GRAPH *g, int node1, int node2, int wt) {
g->arcs[node1][node2].adj = 1;
g->arcs[node1][node2].weight = wt;
}
// remove uma aresta do grafo;
// recebe o grafo e os dois nós (node1 e node2);
void remove (GRAPH *g, int node1, int node2) {
g->arcs[node1][node2].adj = 0;
g->arcs[node1][node2].weight = INFINITY;
}
Os procedimentos acima são utilizados para manutenção do grafo, ou seja,
criação e atribuição de pesos às arestas ou remoção de arestas.
3
Algoritmo de Dijkstra
void dijkstra (GRAPH *g, int s, int t)
{
int dist[MAXNODES], perm[MAXNODES], path[MAXNODES];
int current, i, k, dc;
int smalldist, newdist;
/* Inicializa todos os índices de 'perm' como 0 e de 'dist' como
INFINITY */
for (i = 0; i < MAXNODES; i++) {
perm[i] = NONMEMBER;
dist[i] = INFINITY;
}
/* Inclui 's' em perm (perm[s]=1) e configura(armazena) sua
distancia como sendo zero */
perm[s] = MEMBER;
dist[s] = 0;
/* define 's' como origem (fonte) da busca */
current = s;
k = current;
while (current != t) {
smalldist = INFINITY;
dc = dist[current];
for (i = 0; i < MAXNODES; i++) {
//se o elemento NÃO está em perm
if (perm[i] == NONMEMBER) {
//calcula distância a partir do vértice corrente ao
vértice adjacente i
newdist = dc + g->arcs[current][i].weight;
//se a distância partindo do vértice corrente for
menor, atualiza o vetor
//de distâncias e de precedência
if (newdist < dist[i]) {
dist[i] = newdist;
path[i] = current;
}
//determina o vértice (entre todos os não pertencentes
a perm) com menor distância
if (dist[i] < smalldist) {
smalldist = dist[i];
k = i;
}
}
} /* fim for */
/* embora estamos assumindo grafos ponderados e conexos, este
4
if garante que
em caso de não existência de um caminho o programa não entre
em loop infinito */
if (current == k) {
printf("\n\nCAMINHO NAO EXISTE\n\n");
return;
}
current = k;
perm[current] = MEMBER;
} /* fim while */
/* impressao do resultado ****************/
printf("\n\nRESULTADO: ");
int caminho = t;
printf("%d <- ", t);
while (caminho != s)
{
printf("%d", path[caminho]);
caminho = path[caminho];
if (caminho != s)
printf (" <- ");
}
printf("\n\ncusto: %d\n\n", dist[t]);
/****************************************/
} /* fim dijkstra */
O algoritmo aqui demonstrado recebe como parâmetro um grafo
(tipo GRAPH), um vértice de origem s e o um vértice de destino t. Note que
esta implementação não irá necessariamente calcular o custo mínimo a partir
de s para todo vértice do grafo, porque uma vez encontrado o caminho de
custo mínimo para t (vértice de interesse) o algoritmo é finalizado. Uma
implementação que calcularia a distância mínima para todos os vértices poder
ser implementada facilmente com pouquíssimas alterações sobre o algoritmo
aqui dado.
O procedimento dijkstra utiliza-se de 3 arrays, dist[i], perm[i] e path[i], que
armazenam para todo vértice i do grafo suas respectivas distâncias, situação
com relação ao conjunto PERM e o vértice precedente a i no caminho de custo
mínimo. Veja que este último array é necessário para imprimir o caminho
completo do vértice s a t.
Exemplo: uma situação onde tenhamos distance[i] = 5, perm[i] =
1 e path=[i] = 2, temos que o vértice i já teve seu caminho mínimo
encontrado (perm[i]=1), que a distância se s a i é 5 e que o vértice que o
precede neste caminho é o vértice de índice 2.
5
A variável current armazena o índice do vértice incluído mais recentemente
em PERM (perm[current]=1). A medida que um vértice se torna current este
já teve sua distância mínima calculada. Então, todos os outros vértices
adjacentes a ele devem ter suas distâncias recalculadas e atualizadas se
necessário.
Análise de complexidade de tempo do procedimento Dijkstra
Veja que o laço while será executado no pior caso n vezes (n é o número de
vértices do grafo). Além disso, cada iteração neste laço envolve uma pesquisa
em todos os nós do grafo (for (i = 0; i < MAXNODES; i++) para atualização
de distâncias e descoberta do elemento com menor distância não pertencente
a PERM. Não é dificil perceber que este algoritmo tem complexidade O(n2).
6
Algoritmo de Dijkstra
O algoritmo de Dijkstra é o mais famoso dos algoritmos para cálculo de
caminho de custo mínimo entre vértices de um grafo e, na prática, o mais
empregado.
Escolhido um vértice como raiz da busca, este algoritmo calcula o custo
mínimo deste vértice para todos os demais vértices do grafo. O algoritmo
pode ser usado sobre grafos orientados (dígrafos), ou não, e admite que todas
as arestas possuem pesos não negativos (nulo é possível). Esta restrição é
perfeitamente possível no contexto de redes de transportes, onde as arestas
representam normalmente distâncias ou tempos médios de percurso; poderão
existir, no entanto, aplicações onde as arestas apresentam pesos negativos,
nestes casos o algoritmo não funcionará corretamente.
Funcionamento do algoritmo
Assumiremos um conjunto, chama-lo-emos PERM, que contém inicialmente
apenas o vértice fonte (raiz da busca) s. A qualquer momento PERM contém
todos os vértices para os quais já foram determinados os menores caminhos
usando apenas vértices em PERMa partir de s. Para cada vértice z fora
de PERM matemos a menor distância dist[z] de s a z usando caminhos onde o
único vértice que não está em PERM seja z. É necesssário também armazenar
o vértice adjacente (precedente) a z neste caminho em path[z].
Como fazer com que PERM cresça, ou seja, qual vértice deve ser incluído
em PERM a seguir ? Tomamos o vértice, entre todos os que ainda não
pertencem a PERM, com menor distância dist. Acrescentamos então este
vértice, chamemo-lo de current, a PERM, e recalculamos as distâncias (dist)
para todos os vértices adjacentes a ele que não estejam em PERM, pois pode
haver um caminho menor a partir de s, passando por current, do que aquele
que havia antes de current ser agregado a PERM. Se houver um caminho mais
curto precisamos também atualizar path[z] de forma a indicar que current é o
vértice adjacente a z pelo novo caminho mínimo.
Vejamos o funcionamento do algoritmo sob uma outra representação:
1) Defini-se inicialmente o nó de origem (raiz), neste caso s, e inclui-se este
nó em PERM. Atribui-se zero a sua distância (dist[s]) porque o custo de ir
7
de s a s é obviamente 0. Todos os outros nós i tem suas distâncias (dist[i])
inicializadas com um valor bastante grande ("infinito").
2) A partir de s consulta-se os vértices adjacentes a ele, que no
grafo G são u e x. Para todos os vértices adjacentes, que chamaremos z,
calcula-se:
Se dist[z] > dist[s] + peso(s, z)
dist[z] = dist[s] + peso(s, z)
path[z] = s
Fim Se
3) Dentre todos os vértices não pertencentes a PERM escolhe-se aquele com a
menor distância. Neste caso é o vértice x, pois dist[x] = 5.
8
4) Então, inclui-se x em PERM e a partir de x consulta-se os vértices
adjacentes a ele que não estão em PERM, que no grafo G são u, v e y. Para
todos os vértices adjacentes, que chamaremos z, calcula-se:
Se dist[z] > dist[x] + peso(x, z)
dist[z] = dist[x] + peso(x, z)
path[z] = x
Fim Se
5) Dentre todos os vértices não pertencentes a PERM escolhe-se aquele com a
menor distância. Neste caso é o vértice y, pois dist[y] = 7.
9
6) Inclui-se então y em PERM e a partir de y consulta-se os vértices adjacentes
a ele que não estão em PERM, que no grafo G é apenas o vértice v. Se dist[v]
> dist[y] + peso(y, v)
dist[v] = dist[y] + peso(y, v)
path[v] = y
Fim Se
7) Dentre todos os vértices não pertencentes a PERM escolhe-se aquele com a
menor distância. Neste caso é o vértice u, pois dist[u] = 8.
10
8) Inclui-se então u em PERM e a partir de u consulta-se os vértices
adjacentes a ele que não estão em PERM, que no grafo G é apenas o vértice v.
Se dist[v] > dist[u] + peso(u, v)
dist[v] = dist[u] + peso(u, v)
path[v] = u
Fim Se
9) Dentre todos os vértices não pertencentes a PERM escolhe-se aquele com a
menor distância. Neste caso é o único vértice restante v e dist[v] = 9.
11
10) Por fim faz-se v pertencer a PERM. Neste ponto, todos os vértices já estão
em PERM e a busca é finalizada.
Subsections
Analisaremos agora, nos links abaixo, implementações do algoritmo de
Dijkstra em grafos representados por matriz e lista de adjacências.
Matriz de Adjacências Lista de Adjacências