Buscar

Grafos algoritmos em c (detalhamento)

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 6 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 6 páginas

Prévia do material em texto

Voltar para o índice de grafos
Voltar para o menu principal
5.4 Algoritmos 
A estrutura de dados utilizada para a representação de um grafo é formada por listas ligadas a 
listas, que já foi estudada como aplicação no capítulo referente a listas. Por este motivo, não há 
necessidade de se estudar aqui os algoritmos mais simples para grafos, como por exemplo: 
construção de um grafo através de um; remoção de um grafo inteiro, inserção ou remoção de um 
nó, etc.
5.4.1 Construção de um grafo 
A construção dispensa explicações, pois já foi visto um algoritmo muito parecido em listas ligadas 
a listas.
void ConstroiGrafo2(no **eginicio) { FILE *arq;
 no *pno1, *pno2;
 char c, lixo1;
 int i, n, valor, lixo2;
 *eginicio = NULL;
 arq = fopen ("t1.txt", "r");
 while ((c = getc (arq)) != EOF)
 if (c != '\n'){
 pno1 = malloc (sizeof (no));
 pno1->dado = c;
 fscanf (arq, "%d ", &valor);
 pno1->valorno = valor;
 pno1->usado = 0;
 pno1->pacessoarco = NULL;
 pno1->proxno = NULL;
 if (*eginicio == NULL)
 *eginicio = pno1;
 else
 pno2->proxno = pno1;
 pno2 = pno1;
 while ((c = getc (arq)) != '\n');
 }
 fclose (arq);
 pno1 = *eginicio;
 arq = fopen ("t1.txt", "r");
 while ((lixo1 = getc(arq)) != EOF)
 if (lixo1 != '\n'){
 fscanf (arq,"%d %d", &lixo2, &n);
 lixo1 = getc(arq);
 for (i=1; i !=n+1; i++) {
 c = getc (arq);
 fscanf (arq, "%d ", &valor);
 pa1 = malloc (sizeof (arco));
 pa1->valorarco = valor;
 pa1->usado = 0;
 pa1->padj = *eginicio;
 while (pa1->padj->dado != c)
 pa1->padj = pa1->padj->proxno;
 pa1->proxarco = NULL;
 if (pno1->pacessoarco == NULL)
 pno1->pacessoarco = pa1;
 else pa2->proxarco = pa1;
 pa2 = pa1;
 }
 pno1 = pno1->proxno;
 }
 fclose (arq);
}
}
5.4.2 Caminhamento de um grafo 
Um problema comum no uso de grafos é caminhar os seus nós seguindo uma determinada 
estratégia. Naturalmente, se o problema consiste apenas em “visitar” todos os nós, basta caminhar 
todos os elementos da lista de nós. Mas em geral o que se deseja é, a partir de um determinado nó, 
visitar todos os outros nós aos quais se pode chegar. Dizendo de outro modo, trata-se de visitar 
todos os nós para os quais exista caminho, a partir de um determinado nó.
Duas estratégias podem ser usadas para efetuar essa tarefa: camihamento em amplitude e 
caminhamento em profundidade. Exemplifiquemos utilizando o grafo abaixo, supondo-se que o 
nó de saída seja o nó A. Neste caso o grafo é não dirigido e conexo. Desse modo, todos os nós 
poderão ser visitados a partir do nó A
No caminhamento em amplitude, depois de visitado o nó A, são visitados seus vizinhos (B,C,D). 
Em seguida são visitados os vizinhos de cada um deles (vizinhos do B: nó E; vizi-nhos de C: nó 
G; vizinhos de D: ninguém) e assim por diante. Cada vez que um nó é visitado, ele é marcado, 
para que não seja visitado novamente. A seqüência gerada por essas visitas é: A B C D E G F.
No caminhaimento em profundidade inicia-se pelo nó A e continua-se por um de seus vizinhos 
(B) e depois um vizinho de B (E), etc., caminhando-se o caminho mais comprido possível. 
Esgotado esse caminho, retorna-se ao penúltimo nó desse caminho e aplica-se a mesma estratégia 
para seus outros vizinhos. Sempre que um caminho se esgota, volta-se ao nó anterior nesse 
caminho, até que se chega aos outros vizinhos de A, que foi o nó de partida. A seqüência gerada 
por essas visitas é: A B E F G C D.
A função CaminhaGrafoAmplitude utiliza estrutura de dados vista anteriormente, na qual foi 
acrescentado, em cada elemento da lista de nós, um campo lógico usado, para indicar se o nó já 
foi visitado. O parâmetro pno1 é um ponteiro que aponta para o nó a partir do qual se dá o 
caminhamento. É utilizada uma fila (fila) de ponteiros que apontam para os nós do grafo. A figura 
que representa a estrutura de dados foi simplificada para facilitar a compreensão: no lugar dos 
ponteiros “adj” foram colocados os nomes dos nós apontados por esses ponteiros.
 typedef struct tipofila { 
 void *dado; 
 struct tipofila *prox; 
 } filageral;
 typedef struct elementoarco {
 int usado;
 struct elementoarco *proxarco;
 struct elementono *padj;
 } arco;
 typedef struct elementono {
 char dado;
 int usado;
 struct elementono *proxno;
 struct elementoarco *pacessoarco;
 } no;
Caminha Amplitude
A função CaminhaGrafoProfundidade foi construída de forma recursiva, o que a torna bastante 
simples. Pode-se construir uma versão análoga ao do caminhamento em amplitude, utilizando uma 
pilha em lugar de uma fila.
Caminha Profundidade
Esta função utiliza a técnica denominada de backtracking. O programa avança por um caminho 
até não encontrar saída, e então recua um passo (ao nó anterior no caminho pesqui-sado) e tenta 
outro caminho, até não encontrar saída novamente, e assim por diante.
Esse mecanismo aparece nos próximos algoritmos, com algumas variações.
5.4.3 Determinação de caminhos 
a) função que verifica se existe algum caminho entre dois nós
Esta função apenas verifica se existe algum caminho entre dois nós dados. Retorna 1 quando 
descobre algum caminho, e retorna 0 se não há caminho algum entre os dois nós dados. O 
processo de backtracking é interrompido quando a função encontra algum caminho entre os dois 
nós dados.
Existe Caminho
b) função que determina um caminho entre dois nós
Esta função também procura se existe algum caminho entre dois nós dados. Sua diferença em 
relação à função anterior é que, se existe algum caminho, ela guarda em uma pilha a seqüência de 
nós que forma esse caminho.
Determina Caminho
c) função que determina todos os caminhos existentes entre dois nós
Neste caso, a função não encerra a execução quando encontra o primeiro caminho, mas continua 
procurando e registra todos os caminhos encontrados entre dois nós.
Determina Todos os Caminhos
d) função que determina caminho crítico
Esta função é aplicada a nós que têm os arcos valorados. Chama-se um caminho crítico aquele 
com o maior ou o menor valor da soma dos valores dos arcos. Pode-se aplicar um critério análogo 
para grafos com os nós valorados. Neste caso, a função guarda o primeiro caminho encontrado na 
pilha e continua à procura de outros. Cada vez que um novo caminho é encontrado, calcula o seu 
valor. Se o valor do novo caminho é maior do que o valor do ca-minho armazenado, o novo ocupa 
o lugar do antigo, e assim por diante. No final, o caminho armazenado será o de maior valor.
Se o grafo é uma rede, este algoritmo é útil para ser aplicado entre os nós fonte e sorvedouro, para 
determinar o caminho crítico da tarefa que a rede modela.
Caminho Crítico
e) Caixeiro Viajante
O problema do caixeiro-viajante é clássico na Computação. Faremos aqui uma aplicação de grafos 
a uma situação simplificada desse problema. Um vendedor tem que caminhar uma série de cidades 
interligadas por várias estradas, cada uma delas com um custo associado (nem to-das as cidades se 
ligam diretamente a todas as outras). A questão é determinar qual o melhor percurso a realizar, de 
modo que o custo total seja mínimo, ao mesmo tempo em que todas as cidades devem ser 
caminhadas e nenhuma delas deve ser repetida.
Para simplificar o problema, partiremos da suposição de que existe um ou mais ciclos envolvendo 
as cidades, e não há nenhuma cidade que deixe de pertencer a algum desses ci-clos. Por exemplo, 
na figura abaixo, o mapa1 pode ser resolvido pelo nosso programa, mas o mapa2 não pode, pois a 
cidade C não faz parte de nenhum ciclo (o vendedor teria que percor-rer o trecho BC na ida e na 
volta). No mapa1 há vários ciclos passando por todas as cidades: ABCDEA, AEDCBA, 
ADCBEA.Nossa solução é dada pela função CaixeiroViajante, que efetua a pesquisa dos vários caminhos 
possíveis. São utilizadas duas pilhas para armazenar ponteiros para os nós: a pilhaatual armazena 
os caminhos correntes, que estão sendo pesquisados, e a pilhamin é usada para guardar ciclos 
fechados que contêm todas as cidades (caminhos válidos). A variável valoratual guarda o custo 
atual do caminho que está sendo pesquisado em pilhaatual, e a variável valormin guarda o custo 
do caminho válido encontrado e armazenado em pilhamin. Cada vez que um caminho válido é 
encontrado, verifica-se se o seu custo é menor que o custo do último caminho válido. Neste caso, 
o novo passa a ocupar o lugar do anterior. Ao final do procedimento, em pilhamin está presente o 
melhor caminho possível, e seu custo está armazenado em valormin. A variável valormin é 
inicializada fora de CaixeiroViajante, com o valor da soma de todos os arcos do grafo.
A função CaixeiroViajante é auxiliada pela função TodosNosMarcados, que verifica se todos os 
nós existentes no grafo estão usados, ou seja, se o caminho passou por todas as cidades. 
int TodosNosMarcados(no *ginicio) {
 no *pno1;
 arco *pa1;
 int ok;
 pno1 = ginicio;
 ok = 1;
 while ((ok) && (pno1 != NULL)) {
 if (!pno1->usado) 
 ok = 0;
 pno1 = pno1->proxno;
 }
 return ok;
}
//--- encontra caminho com menor soma dos valores dos arcos ---
void CaixeiroViajante (no *ginicio, no *p1, no *pchegada) {
 arco *pa1;
 char c;
 pilhachar *paux;
 InserePilha (&pilhaatual, p1->dado);
 p1->usado = 1;
 pa1 = p1->pacessoarco;
 while (pa1 != NULL) {
 if (pa1->padj == pchegada) {
 InserePilha (&pilhaatual, pa1->padj->dado);
 valoratual = valoratual + pa1->valorarco;
 if ((valoratual < valormin) && (TodosNosMarcados(ginicio))) {
 valormin = valoratual; 
 while (!PilhaVazia (pilhamin))
 c = RetiraPilha (&pilhamin); 
 InicializaPilha (&paux);
 while (!PilhaVazia (pilhaatual))
 InserePilha (&paux, RetiraPilha (&pilhaatual));
 while (!PilhaVazia (paux)) {
 c = RetiraPilha (&paux); 
 InserePilha (&pilhaatual, c);
 InserePilha (&pilhamin, c); 
 }
 }
 valoratual = valoratual - pa1->valorarco;
 c = RetiraPilha (&pilhaatual);
 }
 else
 if (!pa1->padj->usado){ 
 valoratual = valoratual + pa1->valorarco; 
 CaixeiroViajante (ginicio, pa1->padj, pchegada);
 valoratual = valoratual - pa1->valorarco; 
 } 
 pa1 = pa1->proxarco;
 }
 c = RetiraPilha (&pilhaatual);
 p1->usado = 0;
}
Voltar para o índice de grafos
Voltar para o menu principal

Outros materiais