Sunteți pe pagina 1din 10

 

Relatório Final - Trabalho Prático de Projeto e Análise de 


Algoritmos 
Adailton Silva Palhano¹, Guilherme da Silva Ferreira​1​, Victor Ramos de Oliveira​1​. 
1​
Departamento de Computação – Universidade Federal do Piauí (UFPI) 
64.049-550 – Teresina – PI – Brazil 
(adailton-hag, guilherme199712)@hotmail.com,victorr.vro@gmail.com.

Abstract.  ​The  asymptotic  analysis  is  not  enough  to  clarify  the  performance 
differences  of  some  arrangement  algorithms.  Therefore,  this  work  is  an 
experimental  analysis  of  the  same  with  the  purpose  of  showing  which one has 
a  better  performance  in  ordering  arrangements  and  proposing  a  hybrid 
algorithm. 
Resumo.  A  análise  assintótica  não  é  suficiente  para  esclarecer  as  diferenças  de 
desempenho  de  alguns  algoritmos  de  ordenação  de arranjos. Por conta disso, 
este  trabalho trata-se de uma análise experimental dos mesmos com o objetivo 
mostrar  qual  deles  possui  um melhor desempenho na ordenação de arranjos e 
propor um algoritmo híbrido. 
1. Introdução 
Imagine  como  seria  buscar  um  número  em  um  catálogo  telefônico  se  os  nomes  das 
pessoas  não  estivessem  listados  em  ordem  alfabética?  Seria  muito  complicado.  A 
ordenação  ou  classificação  de registros consiste em organizá-los em ordem crescente ou 
decrescente  e  assim  facilitar  a  recuperação  desses  dados.  A  ordenação  tem  como 
objetivo  facilitar  as  buscas  e  pesquisas  de  ocorrências  de  determinado elemento em um 
conjunto ordenado. 
Como  visto  em  [Cormen  et  al.  2001],  [Brassard  &  Bratley  1996],  [Bentley 
2000],  [Kleinberg  &  Tardos  2005]  e  [Manber  1989],  a  Análise  de  Algoritmos  [Knuth 
2000]  estuda  certos  problemas  computacionais  recorrentes,  ou  seja,  problemas  que 
aparecem,  sob  diversos  disfarces,  em  uma  grande  variedade  de  aplicações  e  de 
contextos.  A  análise  de  um  algoritmo  para  um  dado  problema  trata  de:  i)  provar  que  o 
algoritmo  está  correto;  ii)  estimar  o  tempo  que  a  execução  do  algoritmo  consome.  A 
estimativa  do  espaço  de memória usado pelo algoritmo também é importante em muitos 
casos.  Dados  dois  algoritmos  para  um  mesmo  problema,  a  análise  permite  decidir  qual 
dos dois é mais eficiente.  
Pode-se  dizer  que  a  Análise  de  Algoritmos  é  uma disciplina de engenharia, pois 
ela  procura  prever  o  comportamento  de  um  algoritmo  antes  que  ele  seja  efetivamente 
implementado  e  colocado  “em  produção”.  Num  nível  mais  abstrato,  a  análise  de 
algoritmos  procura  identificar  aspectos  estruturais  comuns  a  algoritmos  diferentes  e 
estudar paradigmas de projeto de algoritmos. 
2. Fundamentação Teórica 
Nesta  seção  vamos  descrever  os  algoritmos  usados  nos  testes,  assim  como  a  sua 
complexidade em casos melhor, pior e médio. 
 

2.1 Ordenação pelo método da flutuação(​bubblesort​) 


Com  base  no  que  foi  visto  em  [Cormen  et  al.  2001],  a  ideia  principal  do  ​Bubblesort  é 
percorrer  um  arranjo  diversas vezes, e em cada um dessas passagens fazer flutuar para o 
topo  o  maior  elemento  da  sequência.  Por  isso  o  nome  “Bubble  Sort”,  já  que  essa 
movimentação nos remete as bolhas de um tanque procurando a superfície da água.  
O  algoritmo  possui  complexidade  de  ordem  quadrática,  por  conta  disso, ele não 
é  recomendado  por  programas  que  necessitam  de  velocidade  e  trabalham  com  um 
grande volume de dados. No seu melhor caso sua complexidade é ​O(n)​, no seu pior caso 
sua complexidade é ​O(n²)​, já no seu caso médio a sua complexidade é ​O(n²)​.  
2.2 Ordenação por inserção(​insertionsort)​   
Segundo  [Cormen  et  al.  2001],  o  algoritmo  ​Insertiosort  constrói  uma  matriz  final  com 
um  elemento  de  cada  vez,  inserindo  um  elemento  por  vez.  Podemos  fazer  uma 
comparação  do  Insertion  Sort  com  o  modo  de  como  algumas  pessoas  organizam  um 
baralho  num  jogo  de  cartas.  Imagine  que  você  está  jogando  cartas.  Você  está  com  as 
cartas  na  mão  e  elas  estão  ordenadas.  Você  recebe  uma  nova  carta  e  deve  colocá-la  na 
posição correta da sua mão de cartas, de forma que as cartas obedeçam a ordenação. 
Assim  como  algoritmos  de  ordenação  quadrática,  é  bastante  eficiente  para 
problemas  com  pequenas  entradas,  sendo  o  mais  eficiente  entre  os  algoritmos  desta 
ordem  de  classificação.  No  seu  melhor  caso  sua  complexidade  é  ​O(n)​, no seu pior caso 
sua complexidade é ​O(n²),​ já no seu caso médio a sua complexidade é ​O(n²)​.  
2.3 Ordenação por intercalação(​mergesort​) 
Um  exemplo  de  algoritmo  de  ordenação  por  comparação,  do  tipo  dividir  para 
conquistar.  Isto  é,  o  algoritmo  divide  o  problema  em  subproblemas  menores  usando 
recursividade  para  resolvê-los  [Cormen  et  al.  2001].  Em  seguida,  a  fase  de  conquista 
pode ser denotada como a junção das resoluções dos subproblemas.  
Por  usar  recursividade,  o  algoritmo  ​Mergesort  necessita  de  um  alto  custo 
computacional  ,  o  que  torna  ele  menos  atrativo  na  resolução  de  alguns  problemas.  Sua 
complexidade é dada por ​O(n log n)​ em todos os casos. 
2.4 Ordenação por Heap(​heapsort​) 
Em  [Cormen  et  al.  2001],  vemos  que  é  um  algoritmo  generalista  e  faz  parte  da  família 
dos  algoritmos  de  seleção.  Recebeu  este  nome  porque  o algoritmo usa uma estrutura de 
dados chamada heap, ordenando os elementos à medida que os insere em sua estrutura.  
Os  elementos  podem  ser  removidos  na  ordem  desejada  a  partir  da  raiz, 
respeitando  a  propriedade  de  max-heap.  Que  pode  ser  representada  como  uma  árvore 
binária ou array. Para ordenar de forma decrescente deve-se construir uma heap mínima, 
onde  o  menor  elemento  fica  na  raiz;  E,  para  uma  ordenação  crescente  uma  heap  de 
máximo,  onde  o  maior  elemento  fica  na  raiz.  Sua  complexidade  é  dada  por  ​O(n log n),​  
em todos os casos. 
2.5 Ordenação rápida(​quicksort)​  
O  algoritmo  de  Ordenação  rápida(​quicksort​)  é  um  método  de  ordenação muito rápido e 
eficiente,  inventado  por  C.A.R.  Hoare  em  1960  [Paulo  1996].O  ​quicksort  adota  a 
 

estratégia  de  divisão  e  conquista.  Em  seguida  o  ​quicksort  ordena  as  duas  sublistas  de 
chaves menores e maiores recursivamente até que a lista completa se encontre ordenada.   
Segundo  [Sara  1988],  os  passos  são:  i)Escolha  um  elemento  da  lista, 
denominado  pivô;  ii)Particiona:  rearrange  a  lista  de  forma  que  todos  os  elementos 
anteriores  ao  pivô  sejam  menores  que  ele,  e  todos  os  elementos  posteriores  ao  pivô 
sejam  maiores  que  ele.  Ao  fim  do  processo  o  pivô  estará  em  sua  posição final e haverá 
duas  sub  listas  não  ordenadas;  iii)Recursivamente  ordene  a  sublista  dos  elementos 
menores e a sublista dos elementos maiores.  
No  seu  melhor  caso  sua  complexidade  é  ​O(n  log  n)​,  no  seu  pior  caso  s​ ua 
complexidade é ​O(n²)​, já no seu caso médio a sua complexidade é ​O(n log n).​   

3. Metodologia 

Este  trabalho  foi  dividido  em  2  partes,  sendo  estas:  i) Implementação e Comparação de 


Algoritmos  de  Ordenação;  ii)Proposta de Algoritmo de Ordenação Híbrido. Nesta seção 
vamos descrever as 2 partes. 

3.1 Tecnologias Usadas 

Os algoritmos foram implementados e  na linguagem Python fazendo uso do compilador 
Python  3.6.3.  Foram  testados  em  uma  máquina  com  o  processador  Intel(R)  Core(TM) 
i5-7400  CPU  @  3.00GHz,  8  Gbs  de  memoria  ram  com  2600Mhz  de  velocidade  e 
Sistema Operacional Windows 10 Home Single Language.  

3.2 Arquitetura  
A  Figura  1  mostra  uma  visão  geral  do  protótipo  desenvolvido  para  realizar  os  testes 
propostos neste trabalho. 

 
​Figura 1. Arquitetura dos testes realizados.  
Na  primeira  etapa,  chamada  de  “Vetor”,  9  vetores  foram  criados  variando  o 
comprimento,  as  quantidades  escolhidas  foram:  i)100;  ii)500;  iii)1.000;  iv)5.000; 
 

v)30.000;  vi)80.000;  vii)100.000;  viii)150.000;  ix)200.000.  Desta  forma,  temos  9 


vetores  diferentes,  ordenados  de  3  formas  diferentes,  sendo  estas:  i)Aleatória; 
ii)Crescente; iii)Decrescente. 
Na  segunda  etapa,  os  vetores  foram  submetidos  aos  algoritmos  de  ordenação,  3 
vezes,  sendo  estes:  i)Ordenação  pelo  método  da  bolha(​bublesort​);  ii)Ordenação  por 
inserção(​insertionsort)​ ;  iii)  Ordenação  por  intercalação(​mergesort)​ ;  iv)Ordenação  por 
Heap(​heapsort)​ ; v)Ordenação rápida(​quicksort)​ .  
Na  terceira  etapa  se  obtém  o  vetor  ordenado,  o  tempo  gasto(em  segundos)  e  a 
quantidade de comparações realizadas pelo algoritmo.   
3.3 Algoritmo Híbrido 
O  algoritmo  híbrido  elaborado  pelo  grupo  utilizou  o  ​quicksort  como  algoritmo 
principal  e  o  ​insertionsort  como  secundário.  O  ​quicksort  foi  escolhido  pela  sua 
velocidade  em  comparação  aos  demais  algoritmos  quando  executado  sobre  os  vetores 
especificados  pelo  trabalho,  de  forma  geral. Já o ​insertionsort foi escolhido pelo fato de 
apresentar o melhor desempenho sobre todos os vetores ordenados em ordem crescente. 
Observamos  que  a  execução  do  insertionsort  sobre  vetores  ordenados  com 
tamanho menor que 1000 elementos tem um tempo de execução mínimo. Além do mais, 
foi  feita  uma  observação  sobre  a  disposição  dos  elementos  de  um  vetor  em  ordem 
crescente. No quicksort 3 índices são usados para operar sobre o vetor, são eles: o índice 
do  início  do  vetor  (i),  o  índice  do  final  do  vetor  (f)  e o índice pivô (p). Imaginemos um 
vetor em ordem crescente de ordenação, note que os valores desses índices serão sempre 
iguais  aos  valores  apontados  por  eles  no  vetor,  ou  seja,  ​i  =  vetor[i]  ,​   ​f  =  vetor[f]  e  ​p  = 
[p]​.  Se  em  um  vetor,  onde  não  podemos  afirmar  nada  sobre  sua  ordenação,  os  valores 
dos  índices  conhecidos  forem  iguais  ao  valores  para  onde  apontam,  espera-se  que  esse 
vetor esteja ordenado em ordem crescente. 
Considerando  todas  as  observações  tomadas,  criamos  uma  condição  no 
algoritmo quicksort, que troca o fluxo de execução do algoritmo para o insertsort.  
if((i - p + 1) <= 1000) and (p == vetor[p]) and (f == vetor[f]) and (i == vetor[i])) 
Dessa  forma  podemos  otimizar  o  algoritmo  ​quicksort  fazendo  com  que  os 
subvetores  criados  durante  a  sua  execução,  que  atendam  a  condição,  sejam  ordenados 
mais  rapidamente  com  ​insertionsort​.  Na  Tabela  1  podemos  observar  o  resultado médio 
de 3 execuções do ​Hibridsort​. Na Figura 2 vemos o gráfico mostrando o tempo médio  e 
a quantidade média de comparações. Considere o tempo em segundos. 
 
 
 
 

Tabela 1. Média do Tempo, em segundos, do algoritmo Híbrido. 

Figura 2. Gráfico comparativo, a esquerda vemos a relação ao tempo médio, e a direita 


em relação a quantidade média de comparações.   

4. Resultados 
Nesta seção iremos exibir os resultados dos testes para cada algoritmo. 
4.1 Ordenação pelo método da bolha(​bubblesort​) 
Na  Figura  3,  mostramos  o  comportamento  do  algoritmo  ​bubblesort.​   Tanto  em  questão 
de  comparações  executadas  para  cada  tamanho  de  vetor,  quanto  em  questão  de  tempo 
gasto  para  a  conclusão  do  algoritmo.  Como podemos ver no gráfico a esquerda, quando 
tratamos  de  comparação,  o  ​bubblesort  executa  a  mesma  quantidade  de  execução  por 
tamanho  de  vetor,  não  importando  a  forma  que  esteja  ordenado.  Já  quando  falamos  de 
tempo  de  execução,  ele  possui  tempos  semelhantes  até  o  vetor  de  30.000 posições. Daí 
em  diante,  ele  gasta  mais  tempo  computacional  no  arranjo  de  ordem  decrescente, 
seguido  pelo  arranjo  aleatório  e  por  fim  o  arranjo  crescente. Como mostrado no gráfico 
a  direita.  Na  Tabela  2  podemos  observar  o  resultado  médio  de  3  execuções  do 
Bubblesort. 

Figura 3. Comportamento ​Bubblesort.​  


 

 
 

Tabela 2. Média do Tempo, em segundos, do algoritmo ​Bubblesort​. 

 
4.2 Ordenação por inserção(​insertionsort​) 
No  ​Insertionsort  a  disparidade  entre  os  números  de  comparações  entre  cada  tipo  de 
arranjo  é  facilmente  notada.  Vendo  a  Figura  4,  no  gráfico  a  direita,  a  partir  do  arranjo 
com  30.000  posições,  o  número  de  comparações  do  vetor  decrescente  cresce quase que 
o  dobro  do  vetor  aleatório.  Enquanto  o  crescente  se  mantém  em  um  valor  de 
comparações  relativamente  pequeno.  Um  comportamento  semelhante  se  aplica  ao 
tempo,  como  podemos  ver  no  gráfico  a  direita,  o  vetor  decrescente  continua  com  o 
maior  crescimento  e o vetor crescente apresenta a menor variação.Na Tabela 3 podemos 
observar o resultado médio de 3 execuções do ​Insertionsort. 
 

Figura 4. Comportamento ​Insertionsort.​  


 

 
 
 

 
 

Tabela 3. Média do Tempo, em segundos, do algoritmo ​Insertionsort.​  

4.3 Ordenação por intercalação(​mergesort)​  


Observando  a  Figura  5,  no  gráfico  a  esquerda,  o  Mergesort  executa  um  maior  número 
de  comparações  no  vetor  ordenado  de  forma  aleatória,  enquanto  para  os  outros  vetores 
mantém  a  mesma  média.  Os  vetores  preenchidos  de  maneira  crescente  e  decrescente 
possuem  o  número  de  comparações  semelhante.  Quando  observamos  o  gráfico  a 
esquerda,  percebemos  que  seu  comportamento  é  bastante  diferente.  Para  arranjos  entre 
1.000  e  50.000  posições,  aproximadamente,  os  tempos  de  execução  para  os  vetores 
crescente,  decrescente  e  aleatórios  são  semelhantes.  Entre  80.000  e  100.000 
aproximadamente,  o  vetor  crescente  possui  o  maior  tempo  computacional,  enquanto  o 
decrescente  apresenta  o  menor  tempo.  Após  esse  intervalo  esses dois vetores trocam de 
posição, o decrescente torna-se o algoritmo de maior tempo computacional e o crescente 
passa  para  o  menor  tempo.  O  arranjo  aleatório  sempre  se  apresenta  com  um  tempo 
computacional  mediano.  Tornando-se ainda o menor durante a execução dos arranjos de 
200.000  posições.  Na  Tabela  4  podemos  observar  o  resultado  médio de 3 execuções do 
mergesort. 

Figura 5. Comportamento ​Mergesort​. 

 
 

 
 
 

Tabela 4. Média do Tempo, em segundos, do algoritmo ​Mergesort​. 

4.4 Ordenação por Heap(​heapsort)​  


Observando  a  Figura  6,  no  gráfico  a  esquerda,  vemos  que  não há uma grande diferença 
na  quantidade  de  comparações,  mesmo  assim  podemos  observar  que  acima  de  100.000 
notamos  que  o  vetor  ordenado  de  forma  crescente  realiza  uma  quantidade  maior  de 
comparações.  Observando  o  gráfico  a  esquerda,  vemos  que  o  tempo  também  não 
apresenta  uma  diferença  significativa,  mas  podemos  observar  que  quando  a  quantidade 
do  vetor  chega  a  80.000,  100.000  e  200.000  o  vetor  organizado  de  forma  crescente 
apresenta  tempo  maior,  já  quando  a  quantidade  do  vetor  é  150.000  o  vetor  decrescente 
apresenta  o  tempo  maior.  Na  Tabela  5  podemos  observar  o  resultado  médio  de  3 
execuções do ​heapsort. 

Figura 6. Comportamento ​Heapsort​. 

 
 
 
 
 

 
 
 
 

Tabela 5. Média do Tempo, em segundos, do algoritmo ​Heapsort​. 

4.5 Ordenação rápida(​quicksort​) 


Olhando  a  imagem  7,  o  Quicksort  apresentou  uma  leve  variação  em  seu  número  de 
comparações.  Onde  o  arranjo  aleatório  apresenta  um  maior  número  de  comparações, 
enquanto  os  demais  arranjos  possuem  números  menores  e  semelhantes.  Um 
comportamento  semelhante  pode  ser  visto  no  gráfico  a  direita.  Os  arranjos  possuem 
tempo  computacional  semelhante  até  chegarmos  em  vetores  que  possuem  mais  de 
30.000  posições.  Onde  arranjos  ordenados  de  forma  aleatória  possuem  tempo 
computacional  maior,  e  os  demais  apresentam  um  tempo  menor  e  semelhante.  Como 
visto  no  gráfico  a  esquerda  que  apresenta  o  número  de  comparações.  Na  Tabela  6 
podemos observar o resultado médio de 3 execuções do ​quicksort. 

 
 

Figura 7. Comportamento ​Quicksort.​  

Tabela 6. Média do Tempo, em segundos, do algoritmo ​Quicksort.​  

5. Conclusão 

A análise desses algoritmos nos remeteu a resultados semelhantes aos resultados obtidos 
com  as  equações  de  recorrência  de  cada  algoritmo.  Ou  seja,  os  resultados  estão  dentro 
do  escopo  de tempo estabelecido com a análise assintótica para cada um deles. Mas, por 
se  tratar  de  uma  análise  experimental,  fomos  capazes  de  descobrir  qual  algoritmo 
consumia  mais  tempo, mesmo dentre aqueles que possuíam uma equação de recorrência 
semelhante. 
References 

A. Paulo . ​Métodos de Classificação de Dados e Análise de suas Complexidades.​ Rio de 


Janeiro: Campus​,​ 1996. 
B.  Sara.  ​Computer  Algorithms​.  Introduction  to  Design  and  Analysis  (em  inglês)  2ª  ed. 
Reading, Massachusetts: Addison-Wesley. 1988 
D.E.  Knuth.  ​Selected  Papers  on  Analysis  of  Algorithms.​   CSLI  Lecture  Notes,  no.  102. 
Center for the Study of Language and Information (CSLI), Stanford, CA, 2000.   
G. Brassard and P. Bratley. ​Fundamentals of Algorithmics.​ Prentice Hall, 1996. 
J.L. Bentley. ​Programming Pearls.​ ACM Press, second edition, 2000. 
J. Kleinberg and E. Tardos. ​Algorithm Design​. Addison-Wesley, 2005. 
T.H.  Cormen,  C.E.  Leiserson,  R.L.  Rivest,  and  C.  Stein.  ​Introduction  to  Algorithms​. 
MIT Press and McGraw-Hill, second edition, 2001. 
U. Manber.​ Introduction to Algorithms: A Creative Approach.​ Addison-Wesley, 1989. 

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