Docsity
Docsity

Prepare-se para as provas
Prepare-se para as provas

Estude fácil! Tem muito documento disponível na Docsity


Ganhe pontos para baixar
Ganhe pontos para baixar

Ganhe pontos ajudando outros esrudantes ou compre um plano Premium


Guias e Dicas
Guias e Dicas


Estrutura de Dados: Implementação de Grafos e Algoritmos de Busca, Notas de estudo de Informática

A implementação de grafos usando listas de adjacência e algoritmos de busca em profundidade e largura em c. A estrutura do grafo é representada por um vetor de estruturas, onde cada estrutura armazena o número do nó e um ponteiro para a lista de adjacência. A lista de adjacência é implementada como uma lista ligada, onde cada nó contém um número e um ponteiro para o próximo nó.

Tipologia: Notas de estudo

2013

Compartilhado em 24/08/2013

robert-araujo-9
robert-araujo-9 🇧🇷

5

(1)

2 documentos

1 / 25

Toggle sidebar

Esta página não é visível na pré-visualização

Não perca as partes importantes!

bg1
Grafos
(Em Linguagem de Programação C)
Kevim Brasil Maciel
Graduando Em Ciência Da Computação
Manaus/AM 2013
Grafos é um conjunto não-vazio de vértices e arestas, tais que cada arco
conecta dois nós, ou seja G(V,A). De forma mais simples, um grafo é um conjunto de
pontos e arcos, e esses arcos conectam esses pontos (ou nós).
Nesta apostila estudaremos a implementação do algoritmo para grafos não
direcionados, não trabalharemos com grafos ponderados, pois ao final das explicações
o leitor terá a capacidade de implementa-la facilmente.
Então dividimos assim o estudo dos grafos:
Grafo não direcionado:
Registro;
Main;
Menu;
Inserção das Arestas;
Impressão da lista de adjacência;
Busca em largura;
Busca em profundidade;
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19

Pré-visualização parcial do texto

Baixe Estrutura de Dados: Implementação de Grafos e Algoritmos de Busca e outras Notas de estudo em PDF para Informática, somente na Docsity!

Grafos

(Em Linguagem de Programação C)

Kevim Brasil Maciel

[email protected]/[email protected]

Graduando Em Ciência Da Computação

Manaus/AM 2013

Grafos é um conjunto não-vazio de vértices e arestas, tais que cada arco

conecta dois nós, ou seja G(V,A). De forma mais simples, um grafo é um conjunto de

pontos e arcos, e esses arcos conectam esses pontos (ou nós).

Nesta apostila estudaremos a implementação do algoritmo para grafos não

direcionados, não trabalharemos com grafos ponderados, pois ao final das explicações

o leitor terá a capacidade de implementa-la facilmente.

Então dividimos assim o estudo dos grafos:

 Grafo não direcionado:

 Registro;

 Main;

 Menu;

 Inserção das Arestas;

 Impressão da lista de adjacência;

 Busca em largura;

 Busca em profundidade;

Para o garfo direcionado, a única coisa que mudará será a inserção das

arestas, você utilizara somente o primeiro teste que é demonstrado na função.

Registro

O registro é onde ficara as informações sobre o que cada espaço de memória

terá que armazenar. Em relação ao registro você pode utilizar duas formas simples,

uma consiste em apenas um espaço de memória de um ponteiro para uma lista de

encadeada, o outro armazenará não só um ponteiro mais também qual ponto é aquele.

Daremos ênfase somente a que possui dois campos, pois sua lista de adjacência será

do mesmo tipo que ela, logo não precisaremos criar outro registro.

Registro com dois campos. Um para guardar o ponto do grafo e outro um

ponteiro para uma lista de adjacência, lista de adjacência são todos os pontos para qual

aquele ponto está ligado:

typedef struct x{

int num;

struct x *prox;

}x;

Aqui chamamos o tipo abstrato de dado de “ x” , mas você pode colocar o nome

que desejar. O registro consiste somente nisso. Mas como trabalhamos com busca em

largura e em profundidade teremos mais alguns registros, um será utilizado na busca

em profundidade e ambos serão utilizados na busca em largura.

typedef struct pilha{

int num;

struct pilha *prox;

}pilha;

Esse registro será utilizado em ambas as buscas, vale lembrar que a busca

em profundidade trabalha com a ideia da pilha (LIFO) o primeiro que entra é o último a

sair, e a busca em largura trabalha coma ideia da fila (FIFO) o primeiro que entra é o

primeiro que sai.

typedef struct sentinela{

struct pilha *inicio;

struct pilha *fim;

}sentinela;

A sentinela, caso você não lembre, guarda o início e o final da fila, e é com o

auxílio dela que trabalharemos a busca em largura.

for(i=0;i<tam;i++){

grafo[i].num=i;

grafo[i].prox=NULL;

system("cls");

do{

printf("\t 1 - Inserir Arestas\n");

printf("\t 2 - Imprimir\n");

printf("\t 3 - Busca em Profundidade\n");

printf("\t 4 - Busca em Largura\n");

printf("\t 5 - Sair\n\n");

printf("Opcao: ");

scanf("%d",&op);

switch(op){

case 1: grafo=insere_aresta(grafo,tam);break;

case 2: imprime(grafo,tam);break;

case3: busca_em_prof(grafo,tam,grafo[0].num);break;

case4: busca_largura(grafo,tam,grafo[0].num);break;

case 5:exit(0);break;

default: printf(erro);break;

system("pause");

system("cls");

}while(op!=5);

Inicialização dos campos do nosso grafo

Imprime as

Opções

Leitura da opção do usuario

Chama a função

Correspondente

A escolha do usuário

Na função menu recebemos um ponteiro para o grafo e o tamanho do grafo,

utilizamos esse método de receber o tamanho do grafo por parâmetro para que não

ocorra o caso de excedermos o tamanho do grafo e consequentemente ocasionar um

erro gravíssimo. Feito isso as funções são chamadas segundo a ordem do usuário.

Inserção das Arestas

O objetivo desta função é criar uma lista de adjacência, lista de adjacência

são todas as arestas que partem, ou chegam em determinado ponto (ou nó), você deve

ter estudado isso ou em estrutura de dados avançada ou em matemática.

Assim como na função menu, recebemos um ponteiro para o garfo, e o valor

do tamanho do grafo justificado anteriormente. Depois disso lemos do usuário os dois

pontos, é isso que irá dizer ao programa, de onde parte a aresta e qual é o seu extremo.

Para entendermos melhor vamos olhar a imagem do início da apostila.

Ok, depois de lermos o primeiro e o segundo ponto, vamos inserir a lista de

adjacência, a lista de adjacência será explicada a imagem abaixo. Possuímos nosso

grafo, e a lista de adjacência será uma lista encadeada com todos os pontos para qual

aquele ponto está ligado. Para explicar, usamos como exemplo, um grafo com quatro

pontos, vamos explica-lo e em seguida mostrar sua lisa de adjacência.

1º ponto, é de

onde parte a

aresta

2º ponto, é o

limite da aresta

Essa é aresta que

vamos inserir, o ponto

1 liga o ponto 2

Como não é um grafo direcionado, esses pontos também apontam para o

ponto 1.

Agora, vamos fazer a lista de adjacência do ponto 2. O ponto 2 aponta para o

ponto 0, 1 e 3, mas observe na lista que o ponto 2 já possui o 0 e 1 na lista de

adjacentes, então não é necessário repeti-lo. Apenas coloca-se os novos.

Como não é um grafo direcionado, esses pontos também apontam para o

ponto 2. Todavia o ponto 2 já está na lista de adjacentes dos outros pontos, exceto do

ponto 3, logo será o único lugar que necessário inseri-lo.

Observe que a lista está completa, se fossemos conferir as arestas do grafo

verificaríamos que concluímos a lista de adjacentes.

A função de inserção de arestas, ou lista de adjacentes, depois de ler dois

números do usuário, que são os pontos, faz dois testes, ele verifica se esses

números são iguais, quando ocorre este caso, o ponto aponta para ele próprio, se

essa condição for verdadeira alocamos apenas um espaço de memória, e fazemos

o grafo na posição daquele número receber um novo elemento com o seu próprio

valor.

if(n1==n2){

novo=(x *)malloc(sizeof(x));

novo->num=n2;

novo->prox=NULL;

novo->prox=grafo[n1].prox;

grafo[n1].prox=novo;

Caso contrário, ele aloca dois espaços em memória, um para cada número,

lembrando novamente de que como não é um grafo direcionado, ambos apontam

para ambos.

else{

novo=(x *)malloc(sizeof(x));

novo->prox=NULL;

novo->num=n2;

novo->prox=grafo[n1].prox;

Recebe a lista de adjacentes daquela posição

Recebe o novo elemento da lista

novo=(x *)malloc(sizeof(x));

novo->prox=NULL;

novo->num=n2;

novo->prox=grafo[n1].prox;

grafo[n1].prox=novo;

out=(x *)malloc(sizeof(x));

out->prox=NULL;

out->num=n1;

out->prox=grafo[n2].prox;

grafo[n2].prox=out;

printf("\n\tOK\n\n");

return grafo;

Impressão da lista de adjacentes

Essa é uma função simples e pequena, ela consiste em receber por

parâmetro um ponteiro para o grafo, e a criação de uma variável auxiliar que

percorrerá toda a lista de adjacente imprimindo todos os valores enquanto a lista não

for vazia, ele percorre todas as posições do vetor do grafo, imprimindo toda a lista.

void imprime(x *grafo, int tam){

x *aux;

int i;

for(i=0;i<tam;i++){

printf("Lista do Numero: %d :",grafo[i].num);

if(grafo[i].prox==NULL)

continue;

aux=grafo[i].prox;

while(aux!=NULL){

printf(" ->%d",aux->num);

aux=aux->prox;

puts("\n\n");

Essa função funciona assim, pegamos a primeira posição do vetor, e sua lista

de adjacente. Vamos ver um exemplo com apenas três pontos.

Depois de pegarmos essa posição, uma variável auxiliar recebe a lista de

adjacência.

A função ira imprimir o valor que está na variável auxiliar, depois passará para

a próximo da lista de adjacência, até que o próximo seja nulo, quando isso ocorrer a

função passará para a próxima posição do grafo, se ele estava na posição 0 vai para

1, se estava na 1 vai para a posição 2 assim sucessivamente, até que tenha

imprimido todas as posições do grafo.

Busca em largura

posição

aux

Posição

atual

fila->fim=NULL;

} else{

aux=fila->inicio;

fila->inicio=fila->inicio->prox;

free(aux);

return fila;

Também recebemos a sentinela por parâmetro, a parti daí são testes.

Verificamos se o inicio e o fim da sentinela são iguais, se forem é porque a fila só

tem um elemento, ambos então recebem o valor nulo, senão, utiliza-se um auxiliar

para receber o início da fila, e passa o início para o próximo elemento, em seguida

libera-se a memória da variável auxiliar que contém o antigo início da fila.

Agora vejamos a função de busca em largura:

Recebemos por parâmetro três dados: o ponteiro para o grafo, o tamanho do

grafo, e o início, que é nele que obtemos a informação de onde iniciar a busca em

largura. Alocamos a sentinela, e inicializamos seus campos. Criamos um vetor

chamado visitado para manter o controle de quais elemento já foram visitados para

que não o fiquemos repetindo.

Logo de início chamamos a função para inserir na fila o início que foi escolhido

pelo usuário. Em seguida entramos no principal loop do programa, sua condição é

enquanto o fim da sentinela for diferente de vazio, “while(sent->fim!=NULL)”.

Então em uma variável receberá o número que está no início da fila e desalocamos

o início da fila, e verifica se o vetor de visitado naquela posição é diferente de 0, se

for colocamos o valor 1 nele, o 1 significa que ele já foi visitado. Imprimimos esse

valor, pronto já começamos a busca em largura.

Uma variável auxilia vai receber a lista de adjacência daquela posição do

grafo. Entramos então no próximo teste da função , enquanto a variável auxiliar for

diferente de nulo , “while(aux!=NULL)”. Enquanto essa condição não é verdadeira

verificamos se o vetor de visitados na posição que há no campo número da variável

auxiliar já foi visitado “if(visitados[aux->num]!=1)”, se ele ainda não foi visitado,

a função insere_fila é chamada, enviando o valor da variável auxiliar que está

caminhando na lista adjacente.

void busca_largura(x *grafo, int tam, int inicio){

int visitados[tam];

sentinela *sent=(sentinela *)malloc(sizeof(sentinela));

sent->inicio=NULL;

sent->fim=NULL;

int i,pos,y;

x *aux;

for(i=0;i<tam;i++){

visitados[i]=0;

sent=insere_fila(sent,inicio);

while(sent->fim!=NULL){

pos=sent->inicio->num;

sent=desenfila(sent);

if(visitados[pos]!=1){

visitados[pos]=1;

printf(" %d ",pos);

aux = grafo[pos].prox;

while(aux!=NULL){

if(visitados[aux->num]!=1){

y=aux->num;

sent=insere_fila(sent,y);

aux=aux->prox;

Aloca o

sentinela e

inicializa

os campos

Inicializa o vetor de visitados

Insere na fila

Insere na fila

A função desempilha simplesmente passa o início da pilha para o próximo

elemento e libera a memória.

A busca em profundidade é idêntica a função de busca em largura, somente

alguns detalhes foram retirados.

void busca_em_prof(x *grafo, int tam, int inicio){

int visitados[tam];

pilha *p=NULL;

int i,pos,y;

x *aux;

for(i=0;i<tam;i++){

visitados[i]=0;

p=empilha(p,inicio);

while(p!=NULL){

pos=p->num;

p=desempilha(p);

if(visitados[pos]!=1){

visitados[pos]=1;

printf(" %d ",pos);

aux = grafo[pos].prox;

while(aux!=NULL){

if(visitados[aux->num]!=1){

y=aux->num;

p=empilha(p,y);

aux=aux->prox;

Insere na pilha

Insere na fila

Inicializa o vetor de visitados

Toda ideia logica das buscas em profundidade e largura são as

mesmas.

Código completo Grafo

#include<stdio.h>

#include<stdlib.h>

typedef struct x{

int num;

struct x *prox;

}x;

typedef struct sentinela{

struct pilha *inicio;

struct pilha *fim;

}sentinela;

typedef struct pilha{

int num;

struct pilha *prox;

}pilha;

char erro[]={"Opcao Invalida!\n"};

void menu(x *grafo, int tam);

for(i=0;i<tam;i++){

grafo[i].num=i;

grafo[i].prox=NULL;

system("cls");

do{

printf("\t 1 - Inserir Arestas\n");

printf("\t 2 - Imprimir\n");

printf("\t 3 - Busca em Profundidade\n");

printf("\t 4 - Busca em Largura\n");

printf("\t 5 - Sair\n\n");

printf("Opcao: ");

scanf("%d",&op);

switch(op){

case 1: grafo=insere_aresta(grafo,tam);break;

case 2: imprime(grafo,tam);break;

case 3: busca_em_prof(grafo,tam,grafo[0].num);break;

case 4: busca_largura(grafo,tam,grafo[0].num);break;

case 5:exit(0);break;

default: printf(erro);break;

system("pause");

system("cls");

}while(op!=5);

x *insere_aresta(x *grafo, int tam){

int n1,n2,i;

x *novo, *out;

x *aux;

printf("Insira a aresta\n");

printf("1 Ponto: ");

scanf("%d",&n1);

printf("2 Ponto: ");

scanf("%d",&n2);

if(n1==n2){

novo=(x *)malloc(sizeof(x));

novo->num=n2;

novo->prox=NULL;

novo->prox=grafo[n1].prox;

grafo[n1].prox=novo;

else{

novo=(x *)malloc(sizeof(x));

novo->prox=NULL;

novo->num=n2;

novo->prox=grafo[n1].prox;

grafo[n1].prox=novo;

out=(x *)malloc(sizeof(x));

out->prox=NULL;

out->num=n1;

out->prox=grafo[n2].prox;