Algoritmo de Dijkstra

11
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

description

Algoritmo de Dijkstra

Transcript of Algoritmo de Dijkstra

Page 1: 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

Page 2: Algoritmo de Dijkstra

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.

Page 3: Algoritmo de Dijkstra

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

Page 4: Algoritmo de Dijkstra

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.

Page 5: Algoritmo de Dijkstra

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).

Page 6: Algoritmo de Dijkstra

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

Page 7: Algoritmo de Dijkstra

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.

Page 8: Algoritmo de Dijkstra

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.

Page 9: Algoritmo de Dijkstra

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.

Page 10: Algoritmo de Dijkstra

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.

Page 11: Algoritmo de Dijkstra

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