Você está na página 1de 7

TRABALHO PRÁTICO 3:

Volume do show
Elverton C. Fazzion
1
Departamento de Ciência da Computação – Universidade Federal de Minas Gerais (UFMG)
elverton@dcc.ufmg.br

Resumo. Baseando que eu seja um guitarrista e começe a tocar uma música


com um volume inicial, qual o maior volume que consigo tocar na última
música sendo que para cada variação em uma música eu tenho um limite in-
ferior (maior ou igual a 0) e um limite superior (menor que um limite dado) e
tenho as variações entre cada música? Este é o problema que será resolvido
aqui.

1. Introdução
Este trabalho tem como plano de fundo, um problema parecido com o problema da
mochila, com a diferença que neste problema eu não tenho uma pergunta do tipo “coloco
ou não o item“ . Neste caso, uma analogia talvez seja melhor para entender.
Suponha que seja um balde com um pequeno volume inicial de àgua. Eu tenho x
passos a se fazer: acrescentar ou retirar um determinado volume de água. Meu limite é
que a àgua do balde não transborde e que eu não fique com um volume de água negativo.
Assim, o que eu devo fazer nesses x passos para que eu termine com o maior volume de
àgua possı́vel?
Analisando este exemplo, podemos jogá-lo para o nosso problema atual onde a
àgua representa o volume, o volume máximo representa o balde e o volume de àgua final
representa o volume da última música. Dessa forma, neste trabalho iremos responder
algumas perguntas pensando nisso. Tais perguntas serão: O problema tem subestrutura
ótima? O problema tem propriedade gulosa? Há sobreposição de subproblemas? Dessa
forma, prosseguimos a documentação para responder a essas perguntas.

2. REFERÊNCIAS RELACIONADAS
Podemos dividir as referências associadas ao problema estudado e a solução proposta
dentre os seguintes grupos:
• LateX: Não basta apenas resolver um problema. É necessário documentá-lo para
que outros o entendem e o utilizem como guia para a resolução de um problema.
O LateX é uma ferramenta amplamente utilizada para gerar documentações.
• Linguagem C: Todo o trabalho foi implementado em linguagem de programação
C. A linguagem C é uma linguagem de propósito geral amplamente utilizada no
desenvolvimento de sistemas de alto desempenho, de tempo real, sistemas op-
eracionais, compiladores, dentre outros. O compilador mais utilizado para a lin-
guagem C é o GCC (desenvolvido pelo projeto GNU).
• Projeto e análise de algoritmos: Algoritmos são procedimentos computacionais
capazes de resolver diversos problemas do mundo real. Entender sobre eles é
muito útil para se tentar produzir algoritmos mais eficientes e como melhor de-
sempenho, retirando passos desnecessários ao problema.
3. SOLUÇÃO PROPOSTA
Para dar a solução proposta, é necessário que seja respondida as perguntas feitas, para
abordar a solução para cada uma delas. A solução do problema pede para implementar
um algoritmo força bruta. Além disso, é preciso implementar uma solução gulosa e uma
solução usando programação dinâmica. Assim, essa sessão será dividida em três partes:
uma explicando cada uma dessas programações (força bruta, dinâmica e gulosa). Caso
não haja como fazer alguma delas, será explicado no seu escopo.

3.1. Algoritmo Força-Bruta


O algoritmo força bruta é o obrigatório a se fazer, como descrito nas requisições. Dessa
forma, a ideia usada para implementar tal algoritmo foi como se fosse o caminhamento
por uma árvore pré-ordem, usando um algoritmo recursivo. A árvore tem o seguinte
formato:

Figura 1. Exemplo do camin-


hamento pré-ordem feito pelo
algoritmo de força bruta. O
algoritmo vai caminhando em
pré-ordem. Ao achar um vol-
ume inválido no meio do cam-
inho, seja porque estourou o
limite ou porque está abaixo de
0, ele cessa o caminhamento
porque aquele nodo e vai para
o nodo seguinte.

O algoritmo força-bruta funciona dessa forma. Assim sendo, fazendo sua análise
de complexidade no pior dos casos, onde nenhum nodo não ultrapassará o limite ou ficará
negativo. Dessa forma, ele percorrerá uma árvore binária e o custo para se percorrer isto
é O(2n ).

3.1.1. Pseudo-código Força Bruta

• Objetivo:Dado um número inicial, um vetor de números e um valor limite, con-


truir uma árvore binária de operações para encontrar o maior valor possı́vel após
a n-ésima operação que não ultrapasse o valor limite. A árvore binária funciona
da seguinte maneira: cada número do vetor é somado ou subtraı́do de cada nó
acima. Á direita de cada nó árvore temos a subtração do mesmo pelo elemento do
vetor correspondente e mais à esquerda, as soma. O elemento correspondente é o
elemento cujo ı́ndice do vetor é igual ao nı́vel da árvore onde se encontra.
• Parâmetros de entrada: n,v,L,p,t,x,xm valor inicial, vetor de números, valor lim-
ite das operações, nı́vel atual,tamanho do vetor, vetor operações, vetor de melhores
operações vazio
• Parâmetros de saı́da: xm vetor das melhores operações (0 - sub e 1 - soma) e
resultado (na última posição)

1: Algoritmo de força bruta


if (p = t) then
if (n ¿ xm[t] e n ¡= L) then
for i = 0 até t-1 passo 1 do
xm[i] = x[i];
end
xm[t] = n;
end
retorna;
end
x[p] = 0;
n = n - v[n];
p = p+1;
Operacao binaria(n,v,L,p,t,x,xm);
p = p-1;
n = n + v[n];
x[p] = 1;
n = n + v[n];
p = p+1;
Operacao binaria(n,v,L,p,t,x,xm);
p = p-1;
n = n - v[n];
retorna;

Como esse algoritmo ficou com muitos algumentos de entrada (7 no total), para
melhorar o uso dele, visto que p,x e xm podem ser criados dinâmicamente, existe uma
função máscara para usar essa função. Ela gera o p,x e xm, só necessitando de n,v,L,t.

2: Mascara(n,v,L,t)
Cria p,x,xm;
Chama Operação binaria(n,v,L,p,t,x,xm);

3.2. Algoritmo Guloso


Esse algoritmo não existe para esse problema, ou pelo menos, uma solução para tal não foi
encontrada. Esse problema possuı́ substrutura ótima, A justificativa para isso é: baseando
na árvore acima, se retirarmos um pedaço dela, onde o nó inicial transforme no volume
inicial, temos aı́ um novo problema. As variações são as mesmas do volume grande para
o intervalo de onde tiramos essa sub-árvore. Dessa forma, esse novo problema nos leva a
um resultado ótimo, justificando o porque de esse problema possuir subestrutura ótima.

Porém, não há uma regra para um algoritmo guloso. A prova se dá por contra-
exemplo. Suponha que haja esse algoritmo. Dessa forma, para cada subestrutura ótima,
Figura 2. Observe pelas setas
o que o algoritmo guloso faria.
Porém o melhor resultado está
num caminho que ele excluiu.

ele irá pegar o melhor valor de cada subestrutura. Porém isso pode nos levar a um resul-
tado que não seja o melhor. A figura abaixo nos mostra isso:
Por essa razão, como não há garantia que, sempre ao seguir o melhor resultado
de cada subproblema, ele estará indo pelo melhor caminho. Assim, não há um algoritmo
guloso para este problema.

3.3. Algoritmo Dinâmico

Nesse problema, temos uma sobreposição de problemas. Suponha que, após descer um
nı́vel da árvore, encontremos dois nodos com o mesmo valor. Dali para baixo, esses nodos
darão origem às mesmas árvores, pois fará as mesmas operações para cada variação no
volume. Ou seja, basta calcular um, visto que temos resultados duplicados, que achamos
um resultado único para comparar com os demais resultados.
Uma proposta para resolver o problema usando programação dinâmica se encontra
abaixo, usando matriz.

3.3.1. Pseudo-código Programação Dinâmica

• Objetivo: Esse algoritmo resolve o problema do último volume da música de


maneira dinâmica através de uma matriz (2 por volume limite + 1)
• Parâmetros de entrada: n,L,v,T número de músicas, volume limite, volume ini-
cial, transições
• Parâmetros de saı́da: Maior volume possı́vel para a última música

A análise de complexidade desse algoritmo pode ser feita da seguinte maneira: o


primeiro for executa n - 1 vezes. Dentro dele há dois for’s que executam L + 1 vezes.
Como cada um executa apenas operações O(1), o custo de cada um pode ser visto como
igual. Assim, o custo do primeiro for é O((n - 1)*(L + 1)). O custo do segundo for é O(L).
Assim, o custo desse algoritmo é o máximo dos dois for’s, que é O((n - 1)*(L + 1)).
3: Algoritmo da programação dinâmica
matriz[0][v] = v;
for i = 0 até n - 1 passo 1 do
if i mod 2 = 0 then
linhaacima = 1;
linhaabaixo = 0;
end
else
linhaacima = 0;
linhaabaixo = 1;
end
for j = 0 até L passo 1 do
if Matriz[linhaabaixo][j] != -1 then
soma = matriz[linhaabaixo][j] + T[i];
if soma ¡= L then
matriz[linhaacima][soma]=soma;
end
subtracao = matriz[linhaabaixo][j] - T[i];
if subtracao ¿= 0 then
matriz[linhaacima][subtracao] = subtracao;
end
end
end
for j = 0 até L + 1 passo 1 do
matriz[linhaabaixo][j] = -1;
end
end
Max = -1 ;
for i = 0 até L + 1passo 1 do
if matriz[linhaacima][i] ¿ max then
max = matriz[linhaacima][i];
end
end
retorna max;

3.4. Estruturas de dados


Não foram necessárias estruturas de dados para a resolução dos algoritmos de força bruta
e programação dinâmica.

4. IMPLEMENTAÇÃO
A implementação foi dividida em duas. Uma para a força bruta e uma para a programação
dinâmica, cada qual em pasta separada, com sua própria main. Porém por motivos de
simplicidade, serão citadas aqui em conjunto.

4.1. Código
4.1.1. Arquivos .c

• brute-force.c: Arquivo que contém os algoritmos de resolução do problema por


força-bruta.
• dynamic.c: Arquivo que contém os algoritmos de resolução do problema por
programação dinâmica.
4.1.2. Arquivos .h

• brute-force.h: Define o cabeçalho das funções do arquivo brute-force.c.


• dynamic.h: Define o cabeçalho das funções do arquivo dynamic.c.

4.2. Compilação
O programa deve ser compilado através do compilador GCC através de um makefile in-
cluı́do com o programa. Tanto o força bruta quanto o dinâmico possuem seus próprios
makefiles em suas pastas.

4.3. Execução
A execução do programa tem como parâmetros:
• Um arquivo padronizado de acordo com a especificação do problema.
O comando para a execução do programa é da forma:
./Executable <arquivo de entrada>

4.3.1. Formato da entrada

A entrada é composta de múltiplos casos de teste. Cada caso de teste é fornecido em duas
linhas. A primeira linha contém 3 inteiros: número de músicas no show, volume inicial e
o volume limite. Na segunda linha, temos N -1 inteiros que representam as alterações no
volume de uma música para a próxima.
1 (número de instâncias)
4 5 10
5 3 7

4.3.2. Formato da saı́da

A saı́da do programa é feita na saı́da padrão. Cada linha da saı́da padrão contém o maior
volume possı́vel para cada instância.
10

5. AVALIAÇÃO EXPERIMENTAL
A avaliação experimental foi feita com base no timer do bash: time ¡comando¿ e usando
sys time + user time. Dessa forma, para comparar o desempenho do dinâmico com o
força bruta, foram colocados no mesmo grafico o tempo gasto por cada um para cada
música. O importe a comentar é que o o limite nesse caso foi o maior possı́vel e o volume
inicial foi algo mediano. Além disso, as variações foram colocadas as minimas possı́veis
fazendo com que o força bruta percorresse a árvore ao máximo, mostrando o seu tempo
de execução máximo. O mesmo foi feito para o dinâmico, para que o mesmo tivesse uma
grande variedade de números diferentes.
Figura 3. Observe que o
tempo para o dinâmico per-
manece quase que constante
para até 1000 entradas en-
quanto o força bruta tem um
pico com 33 músicas.

6. CONCLUSÃO
O objetivo do trabalho, que era estudar e aplicar a teoria de programação dinâmica,
força bruta, sobreposição de problemas e subestruturas ótimas foi atingido. Deu para
notar aa diferença de desempenho quando um problema é resolvido por força bruta e
por programação dinâmica. Além do mais, a busca por um modelo guloso para re-
solver esse problema, fez com que chegasse a conclusões exemplificadas como: um prob-
lema com subestrutura ótima nem sempre pode-se aplicar um algoritmo guloso e que a
implementação dinâmica melhora em grande escala o tempo de execução do algoritmo.

7. BIBLIOGRAFIA:
• Cplusplus:(”http://cplusplus.com”): Referência de Syntax C.
• Cormen: Algoritmo e prática: Livro para a teoria de programação dinâmica,
sobreposição de problemas, subestrutura ótima, e algoritmo guloso.

Você também pode gostar