Logo Passei Direto
Buscar
Material
páginas com resultados encontrados.
páginas com resultados encontrados.

Prévia do material em texto

Ariel Da Silva Dias
PROJETO E ANÁLISE DE 
ALGORITMOS
Sumário
INTRODUÇÃO ������������������������������������������������� 3
ALGORITMOS GULOSOS �������������������������������� 4
Problema do agrupamento de alunos ��������������������������������� 8
ALGORITMO DA MOCHILA ��������������������������11
ALGORITMOS DE PROGRAMAÇÃO 
DINÂMICA ����������������������������������������������������15
Recursão e programação dinâmica ����������������������������������� 17
Programação dinâmica e série de Fibonacci �������������������� 17
ALGORITMOS DE DIVISÃO E CONQUISTA ��� 20
Torre de Hanoi ��������������������������������������������������������������������� 22
Divisão e conquista e programação dinâmica ������������������ 24
BACKTRACKING ������������������������������������������26
Exemplo de uso do backtracking ��������������������������������������� 27
Problema das n-rainhas ������������������������������������������������������ 29
Encontrar caminhos Hamiltonianos em um grafo ������������ 32
CONSIDERAÇÕES FINAIS ����������������������������33
REFERÊNCIAS BIBLIOGRÁFICAS & 
CONSULTADAS ��������������������������������������������35
2
INTRODUÇÃO
Em um projeto de algoritmo não existe uma “bala 
de prata” que seja a cura para todos os problemas 
de computação� Diferentes problemas requerem 
o uso de diferentes tipos de técnicas� Um bom 
programador usa todas essas técnicas com base 
no tipo de problema� Algumas técnicas comumen-
te usadas são a divisão e conquista, algoritmos 
gulosos (que curiosamente não são algoritmos, 
mas sim uma técnica), programação dinâmica e 
backtracking�
Neste e-book, estudaremos as diferentes técnicas 
de implementação de algoritmos, citando exem-
plos com práticas que podem ser reproduzidas em 
diferentes linguagens de programação�
Todas essas técnicas possuem um objetivo: en-
contrar uma solução ótima para um dado problema 
computacional� Você vai perceber, por exemplo, que 
qualquer técnica de desenvolvimento é capaz de 
resolver um problema, entretanto, surgem outros 
problemas: o tempo de execução e a quantidade 
de memória utilizada� Desse modo, essas técnicas 
que serão apresentadas a seguir tem o objetivo de 
melhorar a execução e diminuir a complexidade�
3
ALGORITMOS GULOSOS
O algoritmo guloso ou ganancioso é uma estraté-
gia de solução de problemas que toma decisões 
localmente ótimas em cada estágio na esperança 
de alcançar uma solução globalmente ótima� Esse 
algoritmo simples e intuitivo pode ser aplicado 
para resolver qualquer problema de otimização 
que exija o resultado ótimo máximo ou mínimo, 
além disso, a melhor coisa sobre esse algoritmo 
é que é fácil de entender e implementar�
A complexidade do tempo de execução associada 
a uma solução gananciosa é bastante razoável� 
No entanto, você pode implementar uma solução 
gananciosa somente se a declaração do problema 
seguir duas propriedades mencionadas a seguir:
 y Propriedade da escolha gananciosa: escolher 
a melhor opção em cada fase pode levar a uma 
solução ótima global (geral)�
 y Subestrutura ótima: se uma solução ótima 
para o problema completo contém as soluções 
ótimas para os subproblemas, o problema tem 
uma subestrutura ótima�
Seguindo as etapas que serão apresentadas, você 
será capaz de formular uma solução gananciosa 
para a declaração de problemas:
4
 y Etapa 1: em um determinado problema, encontre 
a melhor subestrutura ou subproblema�
 y Etapa 2: determine o que a solução incluirá 
(por exemplo, maior soma, caminho mais curto)�
 y Etapa 3: crie um processo iterativo para analisar 
todos os subproblemas e criar uma solução ideal�
Como exemplo, vamos considerar um caso bem 
próximo de nossa realidade e que pode ser explicado 
no formato de um algoritmo (não necessariamente 
com programação)�
Declaração do problema: Seu carro pode percorrer 
uma quantidade x de quilômetros com o tanque 
cheio e você tem que chegar a B vindo de A� Você 
tem que chegar ao seu destino com um número 
mínimo de reabastecimentos�
Existem três opções:
 y reabastecer no posto de gasolina mais próximo;
 y reabasteça no posto de gasolina mais distante 
possível;
 y viajar até que não haja combustível�
A segunda opção parece a melhor, em vez de arris-
car encontrar um posto de combustível no ponto 
em que nosso combustível acabará�
Vamos olhar para o algoritmo, começamos nossa 
jornada e viajamos para o posto de combustível 
mais distante que podemos chegar� A partir daí, 
5
repetimos o processo escolhendo a segunda opção 
novamente e chegamos ao posto de combustível 
mais distante possível e assim faremos até che-
garmos ao nosso destino�
Observe então o nosso algoritmo:
 y Primeiro fazemos uma escolha gananciosa 
(para chegar ao posto de gasolina mais distante 
possível);
 y Dessa forma, reduzimos nosso problema em 
subproblemas (dividindo nossa jornada em vários 
postos de gasolina);
 y Em seguida, iteramos sobre o subproblema 
(alcançar o posto de gasolina mais distante pos-
sível) no tempo determinado�
Um possível código é apresentado na tabela 1 a 
seguir�
Tabela 1: código do exemplo de abastecimento�
1 class combustivel{
2
3 val maximaDistanciaCarroPodePercorrer: Int = 400
4 val matrizParadasCombustivel: Array = ar-
rayOf(100, 350, 400, 600, 700, 800, 900, 1200)
5 val numeroParadas = matrizParadasCombustivel.
size
6 var numeroAbastecimentos = 0
7 var posicaoParada = 0
8
9 fun encontrarMinAbast(): Int {
6
10
11 while (destinoNaoEncontrado()) {
12 val ultimaParadaAbastecimento = 
posicaoParada
13
14 while (destinoNaoEncontrado() and isNextSto-
pReachable()) {
15 posicaoParada += 1
16 }
17
18 if (posicaoParada == 
ultimaParadaAbastecimento)
19 return -1 
20
21 if (destinoNaoEncontrado()) {
22 numeroAbastecimentos++
23 }
24 }
25
26 return numeroAbastecimentos
27 }
28
29 private fun isNextStopReachable(): Boolean {
30 return ((matrizParadasCombustivel[posicaoParada 
+ 1] - matrizParadasCombustivel[posicaoParada])enquadram nesse 
intervalo� Agrupe-os�
 y Em seguida, passe para o próximo número a 
partir do último número agrupado O(n)�
 y Repita o processo�
9
Portanto, semelhante ao problema anterior do 
combustível, encontramos uma abordagem O(n) 
se as idades forem dadas de maneira ordenada, 
caso contrário, se você incluir a classificação dos 
números, é um O(nLogn) – o que ainda é um enor-
me melhoria comparando a O(2n), e isso faz uma 
enorme diferença� Talvez o problema de tempo 
exponencial mais famoso em NP, por exemplo, seja 
encontrar fatores primos de um grande número� 
Verificar uma solução requer apenas multiplicação, 
mas resolver o problema parece exigir sistemati-
camente experimentar muitos candidatos�
10
ALGORITMO DA MOCHILA
Esse é um dos problemas clássicos da computação: 
imagine que você tem que planejar uma longa via-
gem e tem uma mochila para caber os vários itens 
que precisa carregar, por exemplo, uma seleção 
de frutas que vai lhe nutrir durante toda a viagem�
Como você tem uma capacidade limitada nesta 
mochila, decidiu levar diferentes quantidades de 
diferentes frutas, mas o objetivo final é maximizar 
a “ingestão de calorias” que você obtém de sua 
mochila cheia�
Esse, novamente, é um problema de maximização 
em que estamos tentando descobrir a combinação 
de itens alimentares que nos dão o máximo valor 
deles�
Vamos supor que Q signifique os pesos, enquanto 
V corresponde ao valor do alimento, em termos 
de ingestão de calorias� Repetimos os seguintes 
passos até que nossa mochila esteja cheia:
 y Podemos descobrir a fruta mais valiosa divi-
dindo os pesos por valores, ou seja, peso/valores�
 y Encha a mochila com esse produto mais valioso 
(chamaremos de PMV)�
 y Podemos encher parcialmente ou completa-
mente esse produto na mochila� Se conseguirmos 
preencher completamente e ainda houver espaço 
11
para outros itens, descubra o próximo melhor PMV 
e repita essas etapas�
O problema da mochila modela uma situação 
análoga ao enchimento de uma mochila, que não 
pode mais suportar um certo peso, com todo ou 
parte de um determinado conjunto de objetos 
tendo cada elemento um peso e um valor� Os itens 
colocados na mochila devem maximizar o valor 
total, sem ultrapassar o peso máximo�
Na teoria encontramos uma solução que funciona 
e tem um processo repetitivo de quebrar problemas 
em menores e, portanto, temos um algoritmo guloso�
A ideia é adicionar os objetos mais eficazes como 
prioridade, até que a bolsa fique saturada, observe 
o pseudocódigo a seguir:
Tabela 2: código algoritmo guloso
1 mochila(n,v,w,W)
2 for j=0 to j=W
3 t[0, j]=0 
4 for i=1 to n
5 for j=0 to W
6 if w[i]>j 
7 t[i, j]=t[i - 1, j]
8 else 
9 t[i, j] = max(t[i - 1, j], t[i - 1, j - w[i]]+v[i])
Fonte: elaborado pelo autor�
12
No algoritmo da tabela 2, a função mochila recebe 
quatro parâmetros:
 y n  número de itens;
 y v[i]  valor do i-ésimo item;
 y w[i]  peso do i-ésimo item;
 y W  capacidade máxima da mochila.
Portanto, precisamos escolher os itens cujo peso 
total não ultrapasse o limite de peso e seu valor 
total seja o mais alto possível� Por exemplo, su-
ponha os seguintes itens com seus pesos e seus 
respectivos valores:
 y Item 1  R$ 1,00 e pesa 1kg
 y Item 2  R$ 8,00 e pesa 3kg
 y Item 3  R$ 18,00 e pesa 5kg
 y Item 4  R$ 22,00 e pesa 6kg
 y Item 5  R$ 28,00 e pesa 7kg
Queremos colocar esses itens em uma mochila, 
no entanto a mochila suporta, no máximo, 11kg� 
Deste modo, a melhor solução para o exemplo 
acima é escolher o item de 5kg e o item de 6kg, 
que dá um valor máximo de R$ 40,00 dentro do 
limite de peso�
A complexidade desse algoritmo, conforme des-
crito, é O(n²), pois haverá um loop while e um loop 
for� Entretanto, se ordenarmos os itens em ordem 
decrescente de relação Valor/Peso, podemos sim-
13
plesmente escolher os itens em ordem decrescente 
até que a mochila esteja cheia, o que, portanto, 
torna nossa solução O(N log N)�
Embora declarado e resolvido de maneira simples, 
o problema da mochila pode ser mapeado dire-
tamente e usado como protótipo para inúmeros 
problemas práticos� As aplicações diretas incluem:
 y uma empresa de transporte tentando embalar 
o maior volume de pacotes em um avião de trans-
porte sem quebrar a capacidade de peso;
 y o desejo de uma equipe esportiva profissio-
nal de construir uma equipe que atenda a várias 
projeções estatísticas sem quebrar o teto salarial�
Além do algoritmo guloso, também podemos utili-
zar a programação dinâmica e recursividade para 
resolver o problema da mochila�
14
ALGORITMOS DE 
PROGRAMAÇÃO 
DINÂMICA
A programação dinâmica é uma excelente abordagem 
que pode ser aplicada a uma classe de problemas 
para obter uma solução eficiente e ótima.
Em outras palavras, o conceito por trás da pro-
gramação dinâmica é quebrar os problemas em 
subproblemas e salvar o resultado para o futuro, 
para que não tenhamos que calcular o mesmo 
problema novamente� A otimização adicional de 
subproblemas que otimiza a solução geral é co-
nhecida como propriedade de subestrutura ótima�
Duas maneiras pelas quais a programação dinâ-
mica pode ser aplicada:
 y Top-Down: Nesse método, o problema é 
decomposto e se o problema já estiver resolvido, 
o valor salvo é retornado, caso contrário, o valor 
da função é memorizado, ou seja, será calculado 
pela primeira vez; para todas as outras vezes, o 
valor armazenado será chamado de volta. A me-
morização é uma ótima maneira para programas 
computacionalmente caros. 
 y Bottom-Up: Essa é uma maneira eficaz de evitar 
a recursão, diminuindo a complexidade de tempo 
que a recursão acumula (ou seja, custo de memória 
15
devido ao recálculo dos mesmos valores)� Aqui, as 
soluções para pequenos problemas são calculadas, 
o que cria a solução para o problema geral�
Deste modo, a programação dinâmica pode ser 
aplicada se você notar que o problema pode ser 
dividido em subproblemas e estes podem ser 
divididos em muitos outros menores, sendo que 
alguns deles se sobrepõem (ou seja, requer o 
cálculo de valores previamente calculados)� O 
objetivo principal é otimizar o código reduzindo a 
repetição de valores armazenando os resultados 
dos subproblemas�
A programação dinâmica é uma estratégia para 
linearizar problemas de programação exponen-
cialmente difíceis, ou seja, a ideia é armazenar 
os resultados dos subproblemas para que não 
tenhamos que recalculá-los posteriormente�
Também podemos resolver o problema da mochila 
com programação dinâmica� Para usar a progra-
mação dinâmica, primeiro criamos uma tabela 
bidimensional com dimensões de 0 a n e 0 a W� 
Em seguida, usamos uma abordagem Bottom-Up 
para calcular a solução ótima� Nessa solução, 
temos um loop aninhado sobre o item número 
n e o limite de peso W� Portanto, seu tempo de 
execução é O(nW)�
16
RECURSÃO E PROGRAMAÇÃO 
DINÂMICA
A recursão é uma maneira de encontrar a solução 
expressando o valor de uma função em termos de 
outros valores dessa função direta ou indiretamente 
– tal função é chamada de função recursiva, logo, 
segue uma abordagem de cima para baixo�
A programação dinâmica nada mais é do que 
recursão com memorização, ou seja, calcular e 
armazenar valores que podem ser acessados 
posteriormente para resolver subproblemas que 
ocorrem novamente, tornando seu código mais 
rápido e reduzindo a complexidade do tempo (os 
ciclos computacionais da CPU são reduzidos)�
Aqui, a ideia básica é economizar tempo pelo uso 
eficiente do espaço, pois a recursão leva tempo, mas 
não espaço, enquanto a programação dinâmica usa 
espaço para armazenar soluções para subproble-
mas para referência futura, economizando tempo�
PROGRAMAÇÃO DINÂMICA E 
SÉRIE DE FIBONACCI
A série de Fibonacci é uma sequência de números 
de tal forma que cada número é a soma dos dois 
anteriores, começando em 0 e 1, definida pela 
fórmula:
F(n)=F(n-1)+F(n-2)
17
Podemos utilizar um método recursivo para calcular 
o Fibonacci,observe na tabela 3 a seguir�
Tabela 3: código Fibonacci recursivo
1 Def ibonacci_recursivo(n):
2 if nlinear e 
merge sort
Exemplo: multiplicação de 
matrizes
Fonte: elaborado pelo autor�
Observe pela tabela 6 que ambos os algoritmos 
possuem suas propriedades bem definidas, ou seja, 
podemos resolver problemas distintos, porém não 
se limitando a eles�
25
BACKTRACKING
Backtracking é um algoritmo geral para resolver 
alguns problemas computacionais, mais nota-
velmente problemas de satisfação de restrições, 
que constrói adicionalmente candidatos para as 
soluções e abandona os retrocessos de um can-
didato assim que determina que ele não pode ser 
concluído para uma solução razoável� O algoritmo 
de backtracking é usado em várias aplicações, 
incluindo o problema das N-rainhas, o problema 
do passeio do cavaleiro, problemas de resolução 
de labirintos e a busca por todos os caminhos de 
Hamilton em um grafo�
Trata-se de uma técnica algorítmica cujo objetivo 
é usar força bruta para encontrar todas as solu-
ções para um problema, o que implica compilar 
gradualmente um conjunto de todas as soluções 
possíveis� Como um problema terá restrições, as 
soluções que não as atenderem serão removidas�
Ele encontra uma solução construindo-a passo a 
passo, aumentando os níveis ao longo do tempo 
através de chamadas recursivas� Uma árvore de 
busca conhecida como árvore de espaço de estados 
é usada para encontrar essas soluções, em que 
cada ramo em uma árvore de espaço de estados 
representa uma variável e cada nível representa 
uma solução�
26
Um algoritmo de backtracking usa o método de 
busca em profundidade, isso quer dizer que quan-
do o algoritmo começa a explorar as soluções, a 
função abundante é aplicada para que o algoritmo 
possa determinar se a solução proposta satisfaz 
as restrições� Se isso acontecer, ele continuará pro-
curando, caso contrário, a ramificação é removida 
e o algoritmo retorna ao nível anterior�
EXEMPLO DE USO DO 
BACKTRACKING
Estamos pegando um exemplo muito simples aqui 
para explicar a teoria por trás de um processo de 
backtracking� Queremos dispor três letras a, b, c 
de tal forma que c não possa ficar ao lado de a.
De acordo com o backtracking, primeiro, cons-
truiremos uma árvore de espaço de estados e 
encontraremos todas as soluções possíveis, em 
seguida, as verificaremos com a restrição fornecida 
e manteremos apenas as soluções que satisfaçam 
a restrição dada�
27
Figura 2: árvore de espaço de estados
a
b
b
c b c a b a
c a c
c
a b
Fonte: elaborado pelo autor.
As possíveis soluções dos problemas seriam: 
(a,b,c), (a,c,b), (b,a,c), (c,b,a)�
No entanto, as soluções válidas para esse problema 
seriam aquelas que satisfizessem a restrição, ou 
seja, se mantêm apenas (a,b,c) e (c,b,a) no conjunto 
final de soluções.
O algoritmo de backtracking é aplicado a alguns 
tipos específicos de problemas. Por exemplo, po-
demos usá-lo para encontrar uma solução viável 
para um problema de decisão, além disso, ele se 
mostra muito eficaz para problemas de otimização.
Em qualquer algoritmo de backtracking, o algoritmo 
busca um caminho para uma solução viável que 
inclua alguns pontos de verificação intermediários. 
Se os checkpoints não levarem a uma solução vi-
28
ável, o problema pode retornar aos checkpoints e 
seguir outro caminho para encontrar uma solução�
Para alguns casos, um algoritmo de backtracking 
é usado para o problema de enumeração a fim de 
encontrar o conjunto de todas as soluções viáveis 
para o problema�
PROBLEMA DAS N-RAINHAS
Um exemplo clássico de backtracking é o problema 
das n-rainhas, proposto pela primeira vez pelo entu-
siasta de xadrez alemão Max Bezzel em 1848� Dado 
um tabuleiro de xadrez de tamanho n, o problema 
é colocar n-rainhas no tabuleiro n vezes, de modo 
que não haja duas rainhas atacando uma à outra�
Para esse problema, precisamos encontrar todos 
os arranjos das posições n das rainhas no tabuleiro, 
mas há uma restrição: nenhuma rainha deve ser 
capaz de atacar outra rainha� O código em Python 
a seguir mostra um exemplo prático�
Tabela 7: código problema n-rainhas
1 N = 5 #dimensao do tabuleiro
2 linha = [False] * N
3 diagAsc = [False] * (2 * N - 1)
4 diagDesc = [False] * (2 * N - 1)
5 numSol = 0
6 solucao = [0] * N
7
29
8 def rainha(col):
9 if colexplorar todas as soluções existentes�
34
Referências Bibliográficas 
& Consultadas
BORIN, V. P. Estrutura de dados. Curitiba: 
Contentus, 2020. [Biblioteca Virtual].
CAMPOS FILHO, F. F. Algoritmos numéricos: 
uma abordagem moderna de cálculo numérico. 
3. ed. Rio de Janeiro: LTC, 2018. [Minha 
Biblioteca].
CORMEN, T. H. Desmistificando algoritmos. 
1. ed. Rio de Janeiro: LTC, 2014. [Minha 
Biblioteca].
DROZDEK, A. Estrutura de dados e 
algoritmos em C++. 4. ed. Cengage Learning, 
2017. [Minha Biblioteca].
GOLDBARG, M. C.; GOLDBARG, E. G., LUNA, 
H. P. L. Otimização combinatória e meta-
heurísticas: Algoritmos e Aplicações. 1. ed. 
Rio de Janeiro: GEN / Elsevier, 2016. [Minha 
Biblioteca].
PINTO, R. A.; PRESTES, L. P.; SERPA, M. S.; 
COUTO, J. M. C.; BIANCO, C. M.; NUNES, P. 
C. M. Estrutura de dados. Porto Alegre: Sagah, 
2019. [Minha Biblioteca].
TOSCANI, L. V., VELOSO, P. A. S. 
Complexidade de algoritmos. 3. ed. Porto 
Alegre: Bookman, 2012. [Minha Biblioteca].
WAZLAWICK, R. S. Introdução a algoritmos 
e programação com Python: uma abordagem 
dirigida por testes; 1. ed. Rio de Janeiro: 
Elsevier, 2017. [Minha Biblioteca].
	_GoBack
	Introdução
	Algoritmos gulosos
	Problema do agrupamento de alunos
	Algoritmo da mochila
	Algoritmos de programação dinâmica
	Recursão e programação dinâmica
	Programação dinâmica e série de Fibonacci
	Algoritmos de divisão e conquista
	Torre de Hanoi
	Divisão e conquista e programação dinâmica
	Backtracking
	Exemplo de uso do backtracking
	Problema das n-rainhas
	Encontrar caminhos Hamiltonianos em um grafo
	Considerações finais
	Referências Bibliográficas & Consultadas

Mais conteúdos dessa disciplina