Sunteți pe pagina 1din 14

ALGORITMOS E

COMPLEXIDADE

GUIÃO DAS
AULAS PRÁTICAS

António Manuel Adrego da Rocha


Joaquim João Estrela Ribeiro Silvestre Madeira
Departamento de Electrónica, Telecomunicações e Informática
Universidade de Aveiro
Ano Lectivo de 2008/2009
GUIÃO DAS AULAS PRÁTICAS DE ALGORITMOS E COMPLEXIDADE 2

Programa das aulas práticas de Algoritmos e Complexidade

• Análise Empírica da Complexidade (2 aulas)


• Tipos Abstractos de Dados simples baseados em arrays e listas (2 aulas)
• Pesquisa e Ordenação (2 aulas)
• Recursividade (2 aulas)
• Filas e Pilhas
• Árvores Binárias de Pesquisa (1 aula)
• Árvores AVL (1 aula)
• Filas com Prioridade (1 aula)
• Grafos (2 aulas)

Bibliografia para as aulas práticas

• Estruturas de Dados e Algoritmos em C, António Adrego da Rocha, FCA Editora de Informática,


2008.
ou

• Programação Avançada usando C, António Adrego da Rocha, FCA Editora de Informática, 2006.

Material para as aulas práticas

• O material necessário para as aulas práticas é disponibilizado na seguinte página da Internet:


http://sweet.ua.pt/~f706/algoritmos/.
3 GUIÃO DAS AULAS PRÁTICAS DE ALGORITMOS

AULAS 1 E 2
Calcule o número de operações aritméticas (somas ou produtos) executadas pelos algoritmos seguintes:

• Cálculo do factorial n!

• Cálculo do número de Fibonacci F(n) usando a versão repetitiva simplificada (ver 1.4 − Números de
Fibonacci, páginas 7-9)


n
• Cálculo do quadrado usando a soma de sucessivos números ímpares n 2 = i =1
iésimo ímpar

• Cálculo do valor de xn (para n ≥ 0)

• Mudança de base decimal para binário


n
• Cálculo da soma de quadrados sucessivos SomaQua = i =1
i2


n
• Cálculo da soma de potências sucessivas SomaPot =
i =1
xi

• Soma dos elementos de um vector

• Soma de matrizes

• Produto de matrizes

Determine experimentalmente a complexidade de cada algoritmo em função da dimensão da entrada.


GUIÃO DAS AULAS PRÁTICAS DE ALGORITMOS E COMPLEXIDADE 4

AULAS 3 E 4
Para perceber a implementação de tipos abstractos de dados na linguagem C, deve começar por ler o
capítulo 3, analisando a implementação do Tipo Abstracto de Dados COMPLEXO.

O Tipo Abstracto de Dados VECTOR é constituído pelo ficheiro de interface vector.h e pelo ficheiro
de implementação vector.c e implementa a criação de vectores e de operações sobre vectores. O TAD
tem capacidade de múltipla instanciação e usa um controlo centralizado de erros.

Para armazenar as componentes de um vector é usado um array, permitindo assim, que os algoritmos
matemáticos das operações sobre vectores sejam facilmente implementáveis sobre esta estrutura de
dados indexada. A figura apresenta o armazenamento de um vector com 5 componentes num array.
(v4,v3,v2,v1,v0) = (2.5,1.0,0.0,3.0,5.5)

5.5 3.0 0.0 1.0 2.5


Vector[0] Vector[1] Vector[2] Vector[3] Vector[4]

Comece por ler com atenção os ficheiros e de seguida compile o módulo, usando para o efeito o
comando cc -c vector.c, sendo que cc é um alias do compilador da linguagem C programado da
seguinte maneira gcc -ansi -Wall. A compilação deve gerar o ficheiro objecto vector.o. De seguida
compile e teste as aplicações tvector.c e svector.c, que testam as operações do módulo vector. O
primeiro programa é uma aplicação simples, enquanto que o segundo é uma aplicação gráfica. Não se
esqueça que para compilar as aplicações, tem que mencionar o ficheiro objecto do TAD (vector.o) no
comando de compilação. Também é fornecida a makefile mkvector, para compilar o módulo e as
aplicações. Teste convenientemente toda a funcionalidade do TAD.

• Pretende-se desenvolver o Tipo Abstracto de Dados POLINÓMIO, usando como estrutura de


dados de suporte um array para armazenar os seus coeficientes reais. O TAD deve ter capacidade de
múltipla instanciação e controlo centralizado de erros.

Para armazenar os coeficientes de um polinómio é usado um array, permitindo assim, que os algoritmos
matemáticos das operações sobre polinómios sejam facilmente implementáveis sobre esta estrutura de
dados indexada. Tenha em atenção que um polinómio de grau N tem N+1 coeficientes. A figura ilustra
o armazenamento de um polinómio de grau 4 num array.
3.5x4 + 2.5x3 + x2 + 4.5

4.5 0.0 1.0 2.5 3.5


Pol[0] Pol[1] Pol[2] Pol[3] Pol[4]

A funcionalidade pretendida é especificada pelo ficheiro de interface polinomio.h, sendo também


fornecido o esqueleto do ficheiro de implementação polinomio.c. São também fornecidas as
aplicações tpolinomio.c e spolinomio.c e a makefile mkpolinomio, para compilar o módulo e as
aplicações. Teste convenientemente toda a funcionalidade do TAD.

• Pretende-se desenvolver o Tipo Abstracto de Dados MATRIZ, usando como estrutura de dados de
suporte um array bidimensional para armazenar os seus elementos inteiros. O TAD deve ter
capacidade de múltipla instanciação e controlo centralizado de erros.
5 GUIÃO DAS AULAS PRÁTICAS DE ALGORITMOS

A funcionalidade pretendida é especificada pelo ficheiro de interface matriz.h, sendo também


fornecido o esqueleto do ficheiro de implementação matriz.c. Comece por ler com atenção os
ficheiros e complete o módulo.

As operações de transposição de uma matriz, de soma e de produto de matrizes implementam-se


através das expressões indicadas de seguida:
Matriz_Transposta[j, i ] = Matriz[i, j], com 1 ≤ i ≤ NL e 1 ≤ j ≤ NC.
Matriz_Soma[i, j] = Matriz_A[i, j] + Matriz_B[i, j] , com 1 ≤ i ≤ NL e 1 ≤ j ≤ NC.
NCA
Matriz_Produto[i, j] = ∑ Matriz_A[i, k ] × Matriz_B[k, j] , com 1 ≤ i ≤ NLA e 1 ≤ j ≤ NCB.
K =1

São também fornecidas as aplicações tmatriz.c e smatriz.c e a makefile mkmatriz, para compilar o
módulo e as aplicações. Teste convenientemente toda a funcionalidade do TAD.

Sugestão: Para poder manipular as matrizes com acesso indexado do tipo Matriz[i][j], implemente na
memória dinâmica uma estrutura de dados matricial, como se indica na figura. Os excertos de código
necessários para criar e destruir uma sequência bidimensional com NL×NC elementos inteiros são
apresentados na Figura 2.5 (página 39).
PtMatriz
0

int **PtMatriz; 1
.
.
.

NL-1
0 1 2 . . . NC-1

• Pretende-se desenvolver o Tipo Abstracto de Dados CONJUNTO, usando como estrutura de


dados de suporte uma lista biligada para armazenar os seus elementos de modo ordenado.
Implemente um módulo, com capacidade de múltipla instanciação e com controlo de erros, para
conjuntos de caracteres alfabéticos maiúsculos.

A funcionalidade pretendida é especificada pelo ficheiro de interface conjunto.h, sendo também


fornecido o esqueleto do ficheiro de implementação conjunto.c. Comece por ler com atenção os
ficheiros e complete o módulo. São também fornecidas as aplicações tconjunto.c e sconjunto.c e a
makefile mkconjunto, para compilar o módulo e as aplicações. Teste convenientemente toda a
funcionalidade do TAD.

Para armazenar os elementos de conjunto deve ser usada uma lista biligada, mantendo os seus
elementos sempre ordenados. Desta forma optimiza-se os algoritmos associados às operações habituais
sobre conjuntos. A figura apresenta o armazenamento do conjunto {A, K, L, X} numa lista biligada.
cabeça
do PtSeg PtSeg PtSeg PtSeg
conjunto
PtAnt PtAnt PtAnt PtAnt
PtEle PtEle PtEle PtEle

'A' 'K' 'L' 'X'

Sugestão: Para se familiarizar com listas biligadas e os seus algoritmos de manipulação, comece por ler
o item 2.4.2 – Listas biligadas (páginas 48-53).
GUIÃO DAS AULAS PRÁTICAS DE ALGORITMOS E COMPLEXIDADE 6

AULAS 5 E 6
• Pretende-se determinar experimentalmente a complexidade do algoritmo de pesquisa sequencia.

int SequentialSearch (int pseq[], unsigned int n, int pval)


{
unsigned int IndActual;

for (IndActual = 0; IndActual < n; IndActual++)


if (pseq[IndActual] == pval) return IndActual;

return -1; /* pesquisa sem sucesso */


}

• Pretende-se determinar experimentalmente a complexidade do algoritmo de pesquisa binária, nas


suas duas variantes possíveis apresentadas de seguida.

int BinarySearch (int pseq[], unsigned int n, int pval)


{
int Minimo = 0, Maximo = n-1, Medio;

while (Minimo <= Maximo)


{ /* cálculo da posição média */
Medio = ((unsigned int) Minimo + (unsigned int) Maximo) >> 1;

if (pseq[Medio] == pval) return Medio; /* pesquisa com sucesso */


/* Actualização dos limites do intervalo de pesquisa */
if (pseq[Medio] < pval) Minimo = Medio + 1;
else Maximo = Medio - 1;
}

return -1; /* pesquisa sem sucesso */


}

int BinarySearch (int pseq[], unsigned int n, int pval)


{
int Minimo = 0, Maximo = n-1, Medio;

while (Minimo <= Maximo)


{ /* cálculo da posição média */
Medio = ((unsigned int) Minimo + (unsigned int) Maximo) >> 1;

/* Actualização dos limites do intervalo de pesquisa */


if (pseq[Medio] < pval) Minimo = Medio + 1;
else if (pseq[Medio] > pval) Maximo = Medio - 1;
else return Medio; /* pesquisa com sucesso */
}

return -1; /* pesquisa sem sucesso */


}
7 GUIÃO DAS AULAS PRÁTICAS DE ALGORITMOS

Os resultados obtidos deverão ser analisados relativamente ao comportamento individual de cada um


dos algoritmos implementados com o crescimento do número de elementos da sequência a pesquisar.
Para além de verificar experimentalmente o comportamento dos algoritmos no melhor caso e no pior
caso, os testes deverão ilustrar o comportamento dos algoritmos no caso médio. Neste caso, deverão
ser considerados os seguintes tipos de situações e de testes:
– Pesquisa com 100% de sucesso. O elemento procurado está sempre presente no array: é
sucessivamente procurado, um a um, cada um dos elementos do array;
– Pesquisa com 50% de sucesso. O elemento procurado pode não estar presente no array: são
alternada e sucessivamente procurados elementos que pertencem e não pertencem ao array.

Faça a simulação dos algoritmos para sequências com N = 2K−1 números inteiros pares e determine o
número médio de comparações efectuadas para os dois casos médios da pesquisa.

Após a simulação dos algoritmos faça a análise teórica dos casos médios dos algoritmos de pesquisa
para arrays com 2K – 1 elementos. Compare os resultados experimentais com os resultados teóricos,
para sequências com N = 2K−1 elementos (com 10 ≤ K ≤ 20).

K N P − A(N) 100% T − A (N) 100% P − A(N) 50% T − A (N) 50%


10 1023 ##.## ##.## ##.## ##.##
11 2047 ##.## ##.## ##.## ##.##
12 4095 ##.## ##.## ##.## ##.##
13 8191 ##.## ##.## ##.## ##.##
14 16383 ##.## ##.## ##.## ##.##
15 32767 ##.## ##.## ##.## ##.##
16 65535 ##.## ##.## ##.## ##.##
17 131071 ##.## ##.## ##.## ##.##
18 262143 ##.## ##.## ##.## ##.##
19 524287 ##.## ##.## ##.## ##.##
20 1048575 ##.## ##.## ##.## ##.##

Para calcular o número de comparações, pode utilizar uma variável de duração permanente em
conjunção com a função de comparação pretendida. Para esse efeito, encapsula-se a comparação dentro
da função CmpSearchCount, que, além de efectuar a comparação pretendida, também contabiliza o
número de vezes que é invocada. O tipo de comparação desejado é indicado pelo parâmetro ptipo,
usando o identificador: EQUAL (==); BIGGER (>); BEQUAL (>=); LESSER (<); LEQUAL (<=) e NEQUAL
(!=). O modo de actuação é indicado pelo parâmetro pmodo, usando o identificador: INIC para
inicializar a variável contadora; NORM para executar a função e incrementar o valor da variável contadora;
e REP para reportar o valor da variável contadora. O modo de actuação é indicado pelo parâmetro
pmodo, usando o identificador: INIC para inicializar a variável contadora; NORM para executar a função e
incrementar o valor da variável contadora; e REP para reportar o valor da variável contadora.
GUIÃO DAS AULAS PRÁTICAS DE ALGORITMOS E COMPLEXIDADE 8

unsigned int CmpSearchCount (int *x, int *y, int ptipo, int pmodo)
{
static unsigned int cont; /* contagem do número de comparações */

if (pmodo == INIC) cont = 0;


else if (pmodo == NORM)
{
cont++;
switch (ptipo)
{
case EQUAL : return *x == *y;
case BIGGER: return *x > *y;
case BEQUAL: return *x >= *y;
case LESSER: return *x < *y;
case LEQUAL: return *x <= *y;
case NEQUAL: return *x != *y;
}
}
else return cont;
}

A estratégia de atravessar a árvore binária de pesquisa rapidamente em detrimento da detecção do valor


procurado torna o algoritmo mais eficiente. Por isso, uma forma de optimizar ainda mais a pesquisa
binária consiste em diminuir o número de comparações efectuadas em cada iteração, eliminando a
pesquisa explícita do valor procurado, e reduzir sucessivamente a sequência à metade onde se encontra
o valor procurado, usando apenas um teste condicional dentro do ciclo repetitivo. Finalmente, quando
a sequência tiver um único elemento, esse elemento é testado para determinar se a pesquisa teve ou não
teve sucesso. Implemente a função de pesquisa binária que implementa esta estratégia e teste-a para
sequências com N = 2K elementos.
9 GUIÃO DAS AULAS PRÁTICAS DE ALGORITMOS

AULAS 7 E 8
Implemente os seguintes algoritmos recursivos e calcule o número de operações aritméticas (somas ou
produtos) executadas:

• Cálculo do factorial n! n! = n × (n − 1)!

• Cálculo da potência xn usando os seguintes métodos:

 1, se n = 0

1º método → x n = x × x n -1 2º método → x n ( ) 2
=  x n/2 , se n é par

( ) 2
 x × x n/2 , se n é ímpar

 0, se n = 0

• Cálculo do número de Fibonacci F(n) =  1, se n = 1
 F(n − 1) + F(n − 2), se n ≥ 2


n
• Cálculo da soma de potências sucessivas SomaPot = i =1
xi usando o segundo método do cálculo
recursivo da potência

 1, se n = 1
• Cálculo do número triangular T(n) = 
 T(n − 1) + n, se n > 1

 1, se n = 1
• Cálculo do número quadrático Q(n) = 
 Q(n − 1) + 2n − 1, se n > 1

Determine experimentalmente a complexidade de cada algoritmo em função da dimensão da entrada.

• Construa uma função recursiva para implementar a função C(n), definida pela seguinte relação de
recorrência.


1 , se n = 0

C(n) = 
n −1
 2 × C(i) + n ,
 n ∑
caso contrário
i =0

Construa um programa para simular a execução da função C(n), que permita determinar o seu tempo
de execução, usando para esse efeito o módulo crono (crono.h e crono.c). Efectue a análise empírica
da complexidade da função implementada, construindo uma tabela com tempos de execução da função
para diferentes valores de n. Qual é a ordem de complexidade da função recursiva?
GUIÃO DAS AULAS PRÁTICAS DE ALGORITMOS E COMPLEXIDADE 10

Uma forma de resolver problemas recursivos de maneira a evitar o cálculo repetido de valores, consiste
em calcular os valores de baixo para cima, ou seja, de C(0) para C(n) e utilizar um array para manter os
valores entretanto calculados. Este método designa-se por programação dinâmica e reduz o tempo de
cálculo à custa da utilização de mais memória para armazenar valores intermédios.

Usando a técnica de programação dinâmica, construa uma função repetitiva para implementar a função
C(n) e efectue uma análise empírica da sua complexidade. Qual é a ordem de complexidade desta
implementação da função?

Analisando a solução anterior, desenvolva uma função repetitiva que não necessite de utilizar o array e
efectue uma análise empírica da sua complexidade. Qual é a ordem de complexidade desta
implementação da função?

Finalmente, faça a análise formal das três implementações da função C(n) e confirme as ordens de
complexidade obtidas experimentalmente.

FILAS E PILHAS
Para se familiarizar com as memórias fila e pilha sugere-se a leitura do Capítulo 7 – Filas e pilhas.
Estude o funcionamento das implementações dinâmicas, baseadas em listas ligadas e analise as
implementações dinâmicas genéricas.

A memória fila (Queue), cuja funcionalidade é especificada pelo ficheiro de interface queue.h e
implementada pelo ficheiro de implementação queue.c, é uma memória abstracta, com capacidade de
múltipla instanciação. Comece por compreender a sua funcionalidade e depois acrescente-lhe a função
QueueHead, que copia o elemento que se encontra à cabeça da fila, sem contudo o retirar da fila.
Acrescente também a função QueueEmpty, que determina se a fila está ou não vazia.

A memória pilha (Stack), cuja funcionalidade é especificada pelo ficheiro de interface stack.h e
implementada pelo ficheiro de implementação stack.c, é uma memória abstracta, com capacidade de
múltipla instanciação. Comece por compreender a sua funcionalidade e depois acrescente-lhe a função
StackTop, que copia o elemento que se encontra no topo da pilha, sem contudo o retirar da pilha.
Acrescente também a função StackEmpty, que determina se a pilha está ou não vazia.

Compile os módulos e teste a sua funcionalidade, usando nomeadamente o programa capicua.c que
determina se uma sequência de caracteres é um palíndromo. Um palíndromo é uma palavra que se lê da
mesma maneira, quer seja da esquerda para a direita, quer seja da direita para esquerda. Ou seja, é uma
palavra que normalmente se designa por capicua.

Implemente uma memória fila duplamente terminada (Double-Ended Queue – Deque), também
designada por fila dupla, dinâmica genérica cuja funcionalidade é especificada pelo ficheiro de interface
deque.h.. Uma fila dupla é uma fila que permite inserir e retirar elementos, quer da cabeça, quer da
cauda. A fila dupla implementa as seguintes operações: criar uma fila dupla DequeCreate; destruir uma
fila dupla DequeDestroy; colocar um novo elemento na cabeça da fila dupla DequePush; retirar um
elemento da cabeça da fila dupla DequePop; colocar um novo elemento na cauda da fila dupla
DequeInject; retirar um elemento da cauda da fila dupla DequeEject; e determinar se uma fila dupla
está ou não vazia DequeEmpty. Tenha em atenção que precisa de usar como estrutura de dados de
suporte uma lista biligada, de forma a permitir retirar elementos das duas extremidades da fila dupla.
11 GUIÃO DAS AULAS PRÁTICAS DE ALGORITMOS

AULA 9
O Tipo Abstracto de Dados Árvore Binária de Pesquisa (ABP) é constituído pelo ficheiro de interface
abp.h e pelo ficheiro de implementação abp.c e implementa a manipulação de árvores binárias de
pesquisa, com capacidade para armazenar números inteiros. O TAD tem capacidade de múltipla
instanciação e usa um controlo centralizado de erros.

Para testar o TAD é fornecido o programa tabp.c e a makefile mkabp. Comece por testar
convenientemente toda funcionalidade do TAD, usando para esse efeito os ficheiros de árvores
disponibilizados. Também é fornecido o programa irabp.c que simula a inserção e a remoção de
elementos na árvore, fazendo a sua visualização hierárquica na horizontal, após cada operação. Simule o
programa para as sequências de elementos dos ficheiros.

Acrescente ao Tipo Abstracto de Dados Árvore Binária de Pesquisa a seguinte funcionalidade:

• Uma função recursiva para obter um ponteiro para o nó do menor elemento armazenado na
árvore. A função deve ter o seguinte protótipo:
PtABP ABPMinNode (PtABP pabp);

• Uma função repetitiva para obter um ponteiro para o nó do maior elemento armazenado na
árvore. A função deve ter o seguinte protótipo:
PtABP ABPMaxNode (PtABP pabp);

• Uma função para obter o elemento armazenado na árvore, dado um ponteiro para o seu nó. A
função deve ter o seguinte protótipo:
int ABPElement (PtABP pnode);

• Uma função recursiva para determinar a soma dos elementos armazenados na árvore. A função
deve ter o seguinte protótipo:
int ABPTotalSum (PtABP pabp);

• Uma função repetitiva para determinar o número de elementos múltiplos de 3 armazenados na


árvore. A função deve ter o seguinte protótipo:
unsigned int ABPMult3Count (PtABP pabp);

• Uma função recursiva para determinar o número de elementos ímpares armazenados na árvore. A
função deve ter o seguinte protótipo:
unsigned int ABPOddCount (PtABP pabp);

• Uma função repetitiva para determinar a soma dos elementos pares armazenados na árvore. A
função deve ter o seguinte protótipo:
unsigned int ABPEvenSum (PtABP pabp);

• Uma função para determinar a soma dos elementos armazenados na árvore, com número de ordem
ímpar. Ou seja, a soma do primeiro, terceiro, quinto, sétimo, etc. menores números inteiros
armazenados na árvore. A função deve ter o seguinte protótipo:
int ABPOddOrderSum (PtABP pabp);
GUIÃO DAS AULAS PRÁTICAS DE ALGORITMOS E COMPLEXIDADE 12

Escreva um programa, chamado por exemplo testeabp.c que permita testar estas operações. O
programa constrói uma árvore binária de pesquisa a partir de um ficheiro, cujo nome é passado como
argumento na linha de comando e depois apresenta no monitor, o número de nós e a altura da árvore
criada. De seguida, o programa indica também: o valor do menor elemento armazenado na árvore; o
valor do maior elemento armazenado na árvore; a soma dos elementos da árvore; a contagem dos
números múltiplos de 3 da árvore; o número de números ímpares da árvore; a soma dos elementos
pares da árvore; e a soma dos elementos com número de ordem ímpar da árvore.

AULA 10
O Tipo Abstracto de Dados Árvore Adelson-Velskii Landis (AVL) é constituído pelo ficheiro de
interface avl.h e pelo ficheiro de implementação avl.c e implementa a manipulação de árvores AVL,
com capacidade para armazenar números inteiros. O TAD tem capacidade de múltipla instanciação e
usa um controlo centralizado de erros.

Para testar o TAD é fornecido o programa tavl.c e a makefile mkavl. Comece por testar
convenientemente toda funcionalidade do TAD, usando para esse efeito os mesmos ficheiros de
árvores disponibilizados para a árvore ABP.

Também é fornecido o programa iravl.c que simula a inserção e a remoção de elementos na árvore,
fazendo a sua visualização hierárquica na horizontal, após cada operação. Simule o programa para as
sequências de elementos dos ficheiros. Compare as simulações com as da árvore ABP.
13 GUIÃO DAS AULAS PRÁTICAS DE ALGORITMOS

AULA 11
Comece por ler o Capítulo 9 – Filas com prioridade, mais concretamente o item 9.5 – Fila com
prioridade com amontoado (páginas 374-377), para se familiarizar com a implementação de uma fila
com prioridade baseada num amontoado binário (binary heap) e os respectivos algoritmos.

O Tipo Abstracto de Dados Fila com Prioridade, que é constituído pelo ficheiro de interface pqueue.h
e pelo ficheiro de implementação pqueue.c, implementa a manipulação de filas com prioridade
orientada aos máximos, com capacidade para armazenar números inteiros. O TAD tem capacidade de
múltipla instanciação e as operações devolvem um código de erro relativo à execução da operação.

Para testar o TAD são fornecidos os programas tpqueue.c e spqueue.c e a makefile mkpqueue.
Comece por testar convenientemente toda funcionalidade do TAD, com excepção das operações
PQueueIncrease e PQueueDecrease que estão incompletas. Finalmente, complete a funcionalidade
destas duas operações e teste-as convenientemente.

TPC
Para simular o algoritmo do caminho mais curto de Dijkstra usa-se habitualmente uma fila com
prioridade implementada com um amontoado binário e organizada com prioridade orientada aos
mínimos, que armazene elementos estruturados do tipo VERTICE, sendo a prioridade dos elementos
estabelecida pelo campo TCost.
/* definição de um elemento da fila com prioridade */
typedef struct dijkstra
{
unsigned int Vertice; /* vértice */
int TCost; /* custo do caminho até ao vértice */
} VERTICE;

Crie uma fila com prioridade para elementos estruturados do tipo VERTICE, cuja funcionalidade é
descrita no ficheiro de interface pqueue_dijkstra.h e usando o esqueleto do ficheiro de
implementação pqueue_dijkstra.c. Implemente as seguintes operações:

• Criação de uma fila com prioridade PQueueCreate;


• Destruição de uma fila com prioridade PQueueDestroy;
• Inserção de um elemento PQueueInsert;
• Remoção do elemento com menor chave PQueueDeleteMin;
• Promoção do elemento na fila com prioridade PQueueDecrease;
• Determinar se a fila com prioridade está ou não vazia PQueueEmpty.

Para testar a funcionalidade desta fila com prioridade pode usar o programa teste1.c, cuja execução é
apresentada no ficheiro teste1.txt. Pode também usar o programa teste2.c, que simula o algoritmo de
Dijkstra para o digrafo apresentado na página 388 (sendo o custo infinito representado pelo valor 100),
e cuja execução é apresentada no ficheiro teste2.txt.
GUIÃO DAS AULAS PRÁTICAS DE ALGORITMOS E COMPLEXIDADE 14

AULAS 12 E 13
Comece por ler o Capítulo 10 – Grafos (ver 10.3 − Implementação do Grafo, 10.4 − Caracterização do
Grafo e 10.5 − Digrafo dinâmico, páginas 397-411). Estude o funcionamento da implementação do
digrafo dinâmico e analise a sua implementação, para se familiarizar com os algoritmos.

O Tipo Abstracto de Dados DIGRAFO é constituído pelo ficheiro de interface digrafo.h e pelo
ficheiro de implementação digrafo.c e implementa a manipulação de digrafos dinâmicos. Para testar o
TAD é fornecido o programa de simulação gráfica sdigrafo.c e a makefile mkdigrafo.

Comece por testar convenientemente toda funcionalidade do TAD. Depois, acrescente-lhe a seguinte
funcionalidade:
• Verificar se o nó ni é uma “fonte”, i.e., se tem associados um ou mais arcos emergentes, mas não
tem nenhum arco incidente. A função deve ter o seguinte protótipo:
int DigraphSource (PtDigraph pdigraph, unsigned int pvertice);

• Verificar se o nó ni é um “sumidouro”, i.e., se tem associados um ou mais arcos incidentes, mas não
tem nenhum arco emergente. A função deve ter o seguinte protótipo:
int DigraphSink (PtDigraph pdigraph, unsigned int pvertice).

As operações DigraphSource e DigraphSink devolvem o valor 1 em caso afirmativo ou 0 no caso


contrário.

• Acrescente ao TAD digrafo o algoritmo de Dijkstra, que é fornecido no ficheiro dijkstra.c. Para
implementar este algoritmo precisa de uma fila com prioridade, orientada aos mínimos, para
elementos estruturados do tipo VERTICE (pqueue_dijkstra).

• Escreva um programa para simular a execução do algoritmo de Dijkstra sobre um digrafo


armazenado num ficheiro. O programa constrói um digrafo a partir de um ficheiro, cujo nome é
passado como argumento na linha de comando, depois pede o vértice de partida e imprime no
monitor o caminho mais curto para cada um dos vértices alcançáveis a partir do vértice indicado.

• Acrescente também ao TAD digrafo o algoritmo de Bellman-Ford e escreva um programa,


semelhante ao anterior, para simular a execução deste algoritmo.

S-ar putea să vă placă și