Você está na página 1de 10

Trabalho Prático de Matemática Discreta:

Análise de Algoritmos de Ordenação

Descrição do Grupo (no mínimo 1, e no máximo 3 membros):


Nome do Membro 1: Gabriel de O. e S. P. Barreto

Nome do Membro 2: Gabriel Castaman Brunoro

Nome do Membro 3: Pedro Felipe Armendo Sabino

1 - Introdução

A ordenação de elementos sempre foi fundamental no âmbito da programação. Sabendo


disso, programadores de todo o mundo se especializam em desenvolver algoritmos de
ordenação que podem ser replicados e aplicados em outros projetos, economizando
dinheiro e tempo de milhares de programadores no mundo. Diversos algoritmos de
ordenação já foram criados, mas deve-se saber quando e qual utilizar dependendo de
cada caso, conhecendo seu desempenho e complexidade.
Algoritmos de ordenação geralmente funcionam com a comparação de termos, podendo
haver a comparação entre os termos a serem ordenados ou até com uma biblioteca pré-
estabelecida, como a decimal, o alfabeto ou a linguagem binária.
Para a realização do presente trabalho, foram escolhidos três algoritmos para se fazer
uma análise detalhada de cada um. Os algoritmos escolhidos foram o Bubble sort, o
Merge sort e o Radix sort.
O algoritmo Bubble trabalha com a comparação entre os valores a ser ordenados,
escolhendo o maior termo e o jogando pra o fim do vetor. Já o algoritmo merge trabalha
na forma dividir e conquistar, utilizando recursão e uma quantidade significativa de
memória para seu funcionamento. Por fim, o algoritmo Radix sort trabalha com a
ordenação digito a digito dos termos a serem ordenados e pode ser utilizado para
diferentes tipos de alfabetos, como o alfabeto e o numeral.
O trabalho a seguir trata da exposição da história de cada algoritmo, tal como de sua
funcionalidade, análise e comparação de cada um.

2 - Descrição e Análise do Algoritmo Bubble Sort

O Bubble Sort é conhecido como um dos algoritmos de ordenação mais simples, o motivo
de seu nome estar relacionado a uma bolha é devido ao comportamento do algoritmo
com os valores do vetor, de modo análogo, os valores a serem ordenados serão as
bolhas que estão dentro de um tanque de água, elas vão se organizar de acordo com
seu tamanho, ou seja, as volumosas ficarão no topo. O algoritmo trabalha de maneira
similar, percorre todo o vetor e verificando dois a dois quem é valor maior, assim o maior
é jogado para frente e o menor para trás, e assim sucessivamente com os outros valores
até estarem todos ordenados em ordem ascendente ou descendente.

Pseudo-Código em linguagem C:

Fonte do Pseudocódigo:

Biggar, P.; Gregg, D. Sorting in the Presence of Branch Prediction and Caches: Fast
Sorting on Modern Computers. https://www.scss.tcd.ie, 2005. Disponível em <
https://www.scss.tcd.ie/publications/tech-reports/reports.05/TCD-CS-2005-57.pdf >.
Acesso em 05/05/21

3 - Descrição e Análise do Algoritmo Merge sort

O merge sort usa como base o método de dividir e conquistar, o qual consiste em dividir
o vetor ao meio em vários sub-vetores até que reste um único elemento no vetor para
que ele possa começar a ser resolvido, após fazer comparações de qual é menor e maior,
o algoritmo volta combinando esses números em ordem crescente até que se obtenha a
solução.
Dividir e conquistar pode ser resumido em três partes:
1. Dividir: Dividir o vetor em sub-vetores em partes menores até que fique simples
para o algoritmo começar a se resolver.
2. Conquistar: Os sub-vetores resolve se recursivamente. Se eles forem pequenos
o suficiente, resolva os subproblemas como problemas base.
3. Combinar: Combina as soluções dos sub-vetores em uma solução para o
problema original.

Pseudocódigo:
procedure mergesort( var a as array )
if ( n == 1 ) return a

var l1 as array = a[0] ... a[n/2]


var l2 as array = a[n/2+1] ... a[n]

l1 = mergesort( l1 )
l2 = mergesort( l2 )

return merge( l1, l2 )


end procedure
procedure merge( var a as array, var b as array )

var c as array
while ( a and b have elements )
if ( a[0] > b[0] )
add b[0] to the end of c
remove b[0] from b
else
add a[0] to the end of c
remove a[0] from a
end if
end while

while ( a has elements )


add a[0] to the end of c
remove a[0] from a
end while

while ( b has elements )


add b[0] to the end of c
remove b[0] from b
end while

return c
end procedure
Fonte do pseudocódigo:
Data Structures - Merge Sort Algorithm. Tutorialspoint. disponível em:
<https://www.tutorialspoint.com/data_structures_algorithms/merge_sort_algorithm.htm>
Acesso em: 05/05/2021.

4 - Descrição e Análise do Algoritmo Radix Sort

A história do Radix sort remonta à 1890, quando seu criador, Hermann Hollerith
trabalhava numa agência governamental encarregada de fazer o censo populacional.
Normalmente, o recenseamento da população demorava quase uma década para ser
feito mas, com a utilização da máquina de cartões perfurados criada por Hermann, esse
trabalho pôde ser realizado em apenas um ano.
O surgimento da classificação Radix se deu à necessidade de ordenar os cartões
perfurados a serem lidos por essas máquinas, de forma que não houvessem erros no
processamento das informações e foi realmente implementado na leitura desses cartões
perfurados em torno de 1923.
O primeiro algoritmo computadorizado que funcionava eficientemente surgiu somente
em 1954, criado por Harold. H. Seward, que também é criador do algoritmo counting sort,
o qual tem intima ligação com o Radix sort. Hodiernamente, o método do radix sort é
mais utilizado para coleções de strings binárias e inteiros. Sua eficiência é comparada a
outros algoritmos de comparação, sendo até superior quando abaixo de certos limites.
A ordenação em radix pode ocorrer de duas formas distintas: começando do algoritmo
menos significativo (LSD) ou do mais significativo (MSD), de forma que ordene em ordem
lexical cada digito até o maior digito do maior termo da string.
Algoritmos em LSD geralmente são mais estáveis.

Pseudocódigo:

Radix-Sort(A, d)
for j = 1 to d do
int count[10] = {0};
for i = 0 to n do
count[key of(A[i]) in pass j]++
for k = 1 to 10 do
count[k] = count[k] + count[k-1]
for i = n-1 downto 0 do
result[ count[key of(A[i])] ] = A[j]
count[key of(A[i])]--
for i=0 to n do
A[i] = result[i]
end for(j)
end func
Fonte do pseudocódigo:
Bisht, K. Radix Sort, 2019. Disponível em: < https://iq.opengenus.org/radix-sort/>
Acesso em: 05/05/2021

5 - Análise Assintótica

É possível implementar o merge sort utilizando somente um vetor auxiliar ao longo de


toda a execução, fazendo com que a complexidade de tempo seja igual a O(n.log.n),
para ambos melhor e pior casos. O algoritmo também é estável na maioria das
implementações, em que elas possam ser interativas ou recursivas, o que acaba sendo
uma desvantagem também pois como é uma chamada recursiva ela terá que criar uma
cópia do vetor para cada nível da chamada totalizando um gasto extra de memória igual
a O(n.log.n).
Já o algoritmo de ordenação Bubble Sort, no seu melhor caso, trabalha com
complexidade O(n), porém, no caso médio ou no pior caso, a complexidade é O(n²), o
que torna o algoritmo antiquado para certas situações, como a ordenação de valores
grandes ou com muitos valores em um vetor. Para esses casos, o tempo de execução
pode variar muito dependendo de como se encontra o vetor, por exemplo, se o vetor
estiver semi-ordenado ou com uma boa parte dos valores já ordenados o resultado seria
melhor, caso contrário o algoritmo tem que deslocar os valores muitas vezes, tornando
o processo lento e ineficaz.
Finalmente, a complexidade de tempo do radix sort depende de seu algoritmo
intermediário, o counting sort. Sua complexidade de tempo em todo caso é O(n*d), já
que se trata de um algoritmo de ordenação linear, em termos de complexidade de
espaço, O(n + k), onde “n” representa a quantidade de termos a serem ordenados, “d” o
número de dígitos do maior termo e “k” representa o tamanho do alfabeto utilizado
(decimais, alfabeto e binário).
Uma de suas possíveis desvantagens está em ter de ser reescrito toda vez que for
necessário mudar sua base lexical, da ordenação de números para de letras, por
exemplo.
A primeira análise que podemos fazer de cara ao analisar as complexidades dos três
algoritmos, utilizando o pior caso possível como base, é que o algoritmo Bubble sort, por
ter uma complexidade que aumenta exponencialmente, se torna completamente
ineficiente quando comparado aos outros dois, que aumentam de forma linear.
Comparando agora os algoritmos Merge e Radix, observa-se que o segundo é mais
eficiente enquanto d < log(n), sendo “d” o número de dígitos do maior numero a ser
ordenado, após isso o Merge se torna o mais eficiente dos três algoritmos.
6 - Análise Experimental

Para a realização da parte experimental do trabalho, foi utilizada uma máquina com as
seguintes especificações:

• Processador Intel Core i5-10400F, CPU @ 2.9GHz


• Armazenamento: HD Seagate BarraCuda, 2TB, 3.5´, SATA - ST2000DM008
• Memória: HyperX Fury, 8GB, 3000MHz, DDR4, CL15, Preto - HX430C15FB3/8
• Placa de Vídeo: Asus ROG Strix AMD RX 5500 XT OC Gaming, 8G, GDDR6 -
ROG-STRIX-RX5500XT-O8G-GAMING
• Placa-Mãe: Asus ROG Strix B460-G Gaming, Intel LGA 1200, mATX, DDR4

O código de cada algoritmo foi implementado na linguagem C, utilizando o software


CodeBlocks. A entrada de dados foi sendo feita a partir de um algoritmo criado para
aleatorizar números no intervalo de valores selecionado, de forma que os valores
aumentassem de acordo com o aumento da quantidade de valores a serem ordenados.
A seguir consta as tabelas e os gráficos de cada algoritmo testado:

Tabela 1: resultado Bubble sort. Gráfico 1: Plotagem da tabela 1


Qnt. Termos tempo (s)
50 0,028
100 0,029
bubble sort
200 0,034
300 0,04 160
400 0,045
140
500 0,051
1000 0,051 120
tempo em segundos

2000 0,11
3000 0,164 100
4000 0,198 80
5000 0,27
10000 0,643 60
20000 1,874
40
30000 3,775
40000 6,845 20
50000 10,048
75000 20,96 0
100000 35,714 0 50000 100000 150000 200000 250000
125000 54,408 quantidade de termos
150000 77,26
175000 104,553
200000 135,077
Tabela 2: Resultados Merge sort

Qnt. termos tempo (s) Qnt. termos tempo (s)


x f(x) 10000 0,345
50 0,029 20000 0,736
100 0,03 30000 1,071
200 0,033 40000 1,411
300 0,036 50000 1,692
400 0,041 75000 2,471
500 0,043 100000 3,37
1000 0,056 125000 4,042
2000 0,101 150000 4,7
3000 0,136 175000 5,443
4000 0,175 200000 6,172
5000 0,21 225000 6,8
250000 7,434

Gráfico 2: Plotagem da tabela 2

Merge sort
8

6
tempo em segundos

0
0 50000 100000 150000 200000 250000 300000
quantidade de termos
Tabela 3: Resultados Radix Sort

Qnt. termos tempo (s) Qnt. termos tempo (s)


50 0,032 10000 0,365
100 0,028 20000 0,768
200 0,031 30000 1,114
300 0,036 40000 1,411
400 0,039 50000 1,817
500 0,042 75000 2,657
1000 0,058 100000 3,448
2000 0,098 125000 4,248
3000 0,138 150000 5,004
4000 0,173 175000 5,71
5000 0,224 200000 6,523
225000 6,86
230000 6,984

Gráfico 3: Plotagem da tabela 3

Radix sort
8

6
tempo em segundos

0
0 50000 100000 150000 200000 250000
quantidade de termos
Para termos uma melhor visualização do que os gráficos nos representam, podemos uni-
los em um só:

Grafico 4: união dos 3 gráficos

Comparação entre os três algoritmos


160

140

120
tempo em segundos(s)

100

80 bubble sort
Radix sort
60 Merge Sort

40

20

0
0 50000 100000 150000 200000 250000 300000
quantidade de termos

Como esperado desde o começo, podemos ver que o tempo de processamento do


algoritmo bubble é altamente superior quanto maior a quantidade de entradas a serem
ordenadas, tornando-o ineficiente para o tratamento de muitos dados.
O Bubble é bem restrito quanto ao uso, só deve ser utilizado se o número de valores do
vetor for extremamente baixo, para esses casos o uso é favorável, em função de sua
baixa utilização de memória.
Para uma melhor visualização dos algoritmos Radix e Bubble, foi plotado um novo gráfico
somente com os valores dos dois gráficos:

Gráfico 5: Comparação entra Radix e Merge

Comparação entre o Radix e o Merge Sort


8

7
tempo em segundos(s)

4
Radix
3 merge
2

0
0 50000 100000 150000 200000 250000 300000
quantidade de termos

Graficamente muito similares, até certo ponto os algoritmos Merge sort e Radix sort
mantêm tempos de processamento similares, mas quando o número de dígitos do maior
valor a ser ordenado é maior que (log(n)) o Merge se sobressaí sobre o Radix no tempo
de processamento.
Em termos de uso de memória o algoritmo Merge utiliza mais, pois cria um vetor a cada
subdivisão, sendo viável somente se "d" for maior que (log n), caso contrário recomenda-
se a utilização do Radix, que faz um uso de memória menor, mantendo seu desempenho.

7 - Referências

Cormen, Thomas H.; Leisenson, Charles E.; Rivest, Ronald L.; Stein, Clifford.
Introdução aos Algoritmos, segunda edição. MIT press e MCGraw-Hill, 2001.

Knuth, Donald. The Art of Computer Programming, volume 3: sorting and


searching, Terceira edição. Addison-Wesley, 1997.

Mangal, Priyansh. Radix sort – Explanation, Pseudocode and Implementation.


Codingeek, 2017. Disponível em: https://www.codingeek.com/algorithms/radix-
sort-explanation-pseudocode-and-implementation . Acesso em 26/04/2021.

Você também pode gostar