iSorting: Um estudo sobre otimização em algoritmos de ordenação
Marcelo Josué Telles 1
Paulo Henrique Santini 1
Sandro José Rigo 1
José Vicente Canto dos Santos 1
Jorge Luis Victória Barbosa 1
Resumo: A ordenação de elementos é importante em situações de geração de índices em tabelas
de banco de dados, modelagem de sistemas inteligentes, aprendizagem de máquina, classificação
de elementos, identificação de padrões, além das situações clássicas presentes no tratamento de estruturas de dados. Na literatura atual existem diferentes algoritmos que atendem esta tarefa, alguns
são mais eficientes de acordo com as características dos elementos. Visando otimizar a tarefa de
ordenação independente do algoritmo, este artigo apresenta um estudo comparativo e propõe tarefas de pré-processamento, a fim de reduzir o tempo para ordenação. O pré-processamento proposto
consiste em gerar segmentos por intervalos fazendo com que cada um tenha elementos semelhantes.
É apresentada a técnica de pré-processamento e um comparativo entre os algoritmos de ordenação. Os resultados observados mostram que é possível reduzir o tempo necessário para a ordenação
adotando-se a técnica de pré-processamento.
Palavras-chave:
Dados.
Complexidade de Algoritmos, Algoritmos de Ordenação, Pré-processamento de
Abstract:
The ordering of elements is important in situations generation indexes in database
tables, modeling intelligent systems, machine learning, classification of elements, patterns identification, beyond the classical situations present in data structures. In the current literature, there are
various algorithms that meet this task, some are more efficient in accordance with the characteristics
of the elements. To optimize the independent ordering task of the algorithm, this paper presents a
comparative study and proposed pre-processing tasks in order to reduce the time for sorting. The
proposed preprocessing is to generate segments at intervals making each has similar elements. Preprocessing technique and a comparison between the sorting algorithms is displayed. Our results
show that it is possible to reduce the time required for sorting by adopting the pre-processing technique.
Keywords: Comparative, complexity of algorithms, preprocessing.
1
Introdução
Na literatura [1] existem diversos algoritmos de ordenação, assim como comparativos [2], [3] e [4], os
quais indicam que dependendo das características dos elementos a serem ordenados, existe um algoritmo mais
adequado. Os comparativos ainda apresentam mais detalhes, tais como, o custo de processamento, questões de
implementação e compreensão lógica do algoritmo. Em algumas situações não é possível identificar o algoritmo
mais adequado, ou não é possível modificar o algoritmo que é utilizado.
Este artigo apresenta uma análise dos principais algoritmos de ordenação e um estudo sobre os efeitos
originados de tarefas de pré-processamento, de modo a avaliar o quanto é possível otimizar a tarefa de ordenação
com atividades adicionais preliminares, independente do algoritmo a ser utilizado. A otimização consiste em
reduzir o número de operações de troca de elementos e consequentemente o tempo necessário para ordenação.
1 Programa
Interdisciplinar de Pós-Graduação em Computação Aplicada (PIPCA), UNISINOS - São Leopoldo (RS) - Brasil
{rigo,jvcanto,[email protected]} {marcelojtelles,[email protected]}
http://dx.doi.org/10.5335/rbca.2015.XXX
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
1
Existem características nos algoritmos de ordenação, que são indicadas em determinadas situações. Desta
forma, a correta identificação do algoritmo mais adequado para cada situação é uma tarefa importante, pois permite
realizar a ordenação utilizando menos processamento.
Identificar o melhor algoritmo para cada caso torna-se um desafio na área de complexidade de algoritmos,
pois isso implica em conhecer os elementos a serem ordenados. Como este conhecimento prévio nem sempre é
possível, a alternativa de pré-processamento se torna bastante viável. Para verificar o impacto desta alternativa em
cada algoritmo, são realizados testes comparativos entre os mesmos.
Uma das limitações dos trabalhos analisados [5] é a implementação do processo de paralelismo para melhorar somente o algoritmo Merge Sort, a fim de disponibilizar uma solução objetiva para a redução do tempo de
execução do mesmo. O paralelismo pode ser adequado para Merge Sort, porém, o mesmo não se enquadra para os
demais algoritmos de ordenação.
A abordagem proposta consiste em aplicar uma técnica de pré-processamento, a qual é capaz de reduzir
as operações necessárias para realizar a ordenação. Para ilustrar o impacto da tarefa de pré-processamento são
apresentados gráficos comparativos, que apresentam as diferenças entre os tempos necessários para realizar a
ordenação.
Este artigo está organizado da seguinte forma. Seção II contém uma breve descrição sobre os conceitos,
entre eles: tipos de algoritmos de ordenação presentes na literatura, custo dos algoritmos, melhor caso e pior caso.
A seção III é dedicada a técnica de pré-processamento, incluindo metodologia e implementação nos algoritmos
de ordenação. A seção IV apresenta os testes e resultados obtidos com a técnica proposta. Finalmente, a seção V
apresenta as considerações finais e possíveis trabalhos futuros.
2
Revisão da Literatura
A principal diferença entre os algoritmos de ordenação é a recursividade e forma como os elementos são
segmentados o que pode reduzir significativamente [6] a quantidade de trocas necessárias para ordenação dos elementos [7]. Os algoritmos de ordenação clássicos encontrados na literatura são: Método de Ordenação pela Bolha
(Bubble Sort), Método de Ordenação por Seleção (Selection Sort), Método de Ordenação por Inserção (Insertion
Sort), Método de Ordenação Rápido (Quick Sort), Método de Ordenação por Fusão (Merge Sort), Método de Ordenação pela Raiz (Radix Sort), Método de Ordenação proposto pelo matemático e cientista da computação Donald
L. Shell (Shell Sort) e Método de Ordenação pelo “Monte” (Heap Sort). Não foi considerado o método Cocktail
Sort (ou Shaker Sort) que funciona como Bubble Sort bidirecional. Quanto ao método Counting Sort é apresentado
sua complexidade, mas não foi analisado, pois se destina apenas à ordenação de inteiros. Já os métodos Bogo Sort
e Bucket Sort não foram analisados neste artigo, pois o algoritmo Bogo Sort é um método de ordenação apenas
para fins didáticos, sendo pouco utilizado na prática. Enquanto que o método Bucket Sort utiliza a segmentação
dos elementos por arrays (definidos como Buckets) e aplica-se a ordenação apenas de números inteiros.
Antes da apresentação dos algoritmos é descrita a estrutura na qual os dados são armazenados para ordenação, pois nos algoritmos de ordenação tal estrutura é manipulada. A ordenação deve deslocar os elementos de
posição, não apenas ordenar os índices dos mesmos. Os elementos a serem ordenados estão organizados em uma
estrutura de dados definidos em uma classe codificada na linguagem Java, denominada “Arquiva”. A estrutura tem
um número inteiro (valor) e duas palavras (letra e nomeArquivo). O número será utilizado para ordenação, a letra
é apenas para contemplar uma informação extra, enquanto que “nomeArquivo” representa o nome do arquivo no
qual se encontram as duas informações anteriores. Na listagem 1 é apresentada a classe “Arquiva”:
Listagem 1: Classe para os Dados
1
2
3
4
5
6
7
8
9
/**
*
* Comentário: Classe responsável por tratar os arquivos com os elementos a serem ordenados.
*
*/
public class Arquiva {
String letra;
int valor;
String nomeArquivo;
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
2
10
public Arquiva(){
this.letra = null;
this.valor = 0;
this.nomeArquivo = null;
}
...
11
12
13
14
15
16
17
}
Os elementos a serem ordenados serão alocados em um array do tipo “Arquiva”. Os métodos tradicionais
de busca e alteração de valores (get, set) foram suprimidos na listagem anterior, mas foram implementados.
2.1
Algoritmos de ordenação
No método de Ordenação pela Bolha (Bubble Sort) [1], é realizada uma primeira interação, onde o atributo
valor é comparado dois a dois. Caso o primeiro seja maior do que o segundo a troca é feita e isso é repetido
sucessivamente até os dois últimos elementos. Na próxima interação o último elemento não é analisado, já que o
maior elemento já está na posição correta. Na listagem 2 segue a implementação deste método:
Listagem 2: Ordenação pela Bolha
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
*
* Bubble Sort
* Ordenação pela Bolha
* Comentário: Código responsável por fazer a ordenação utilizando o conceito da bolha (Bubble Sort ) , este faz uma
* intera ção entre todos os elementos , sendo que a segunda intera ção é apenas do primeiro até o penúltimo , a terceira
* é do primeiro até antepenúltimo . Cada interação faz a comparação de dois elementos , se o elemento da posição i for
* maior que o elemento da posição seguinte , é feita a troca da posição dos elementos .
*
*/
for (int a = 0; a < quantos - 1; a++) {
for (int i = 0; i < (quantos - 1) - a; i++) {
if(arquiva[i].getValor() > arquiva[i+1].getValor()){
temp.setNomeArquivo(arquiva[i+1].getNomeArquivo());
temp.setValor(arquiva[i+1].getValor());
temp.setLetra(arquiva[i+1].getLetra());
arquiva[i+1].setNomeArquivo(arquiva[i].getNomeArquivo());
arquiva[i+1].setValor(arquiva[i].getValor());
arquiva[i+1].setLetra(arquiva[i].getLetra());
arquiva[i].setNomeArquivo(temp.getNomeArquivo());
arquiva[i].setValor(temp.getValor());
arquiva[i].setLetra(temp.getLetra());
}
}
}
Na implementação apresentada é possível identificar que cada troca de posição implica na modificação da
posição dos três atributos da classe “Arquiva”.
No método de Ordenação por Seleção (Selection Sort) [8], é feita a comparação entre o primeiro elemento
e todos demais elementos do conjunto, a fim de encontrar um elemento menor do que o primeiro. Caso seja
encontrado é feita a troca de posição dos elementos. Na sequência o segundo elemento é comparado com os
demais e assim sucessivamente. Na listagem 3 segue o algoritmo:
Listagem 3: Ordenação por Seleção
1
2
3
4
5
6
/**
*
* Selection Sort
* Ordenação por Seleção
* Comentário: O método de ordenação por sele ção percorre os elementos de uma forma semelhante com o método da bolha,
* no entanto as trocas são realizadas em uma quantidade bem inferior .
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
3
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
*
*/
for (int i = 0; i < arquiva.length - 1; i++) {
indiceMenor = i;
for (int j = i + 1; j < arquiva.length; j++) {
if (arquiva[j].getValor() < arquiva[indiceMenor].getValor()) {
indiceMenor = j;
}
}
if (indiceMenor != i) {
temp.setValor(arquiva[indiceMenor].getValor());
temp.setNomeArquivo(arquiva[indiceMenor].getNomeArquivo());
temp.setLetra(arquiva[indiceMenor].getLetra());
arquiva[indiceMenor].setValor(arquiva[i].getValor());
arquiva[indiceMenor].setNomeArquivo(arquiva[i].getNomeArquivo());
arquiva[indiceMenor].setLetra(arquiva[i].getLetra());
arquiva[i].setValor(temp.getValor());
arquiva[i].setNomeArquivo(temp.getNomeArquivo());
arquiva[i].setLetra(temp.getLetra());
}
}
Para conjuntos com muitos elementos este algoritmo executa muitas operações, pois o conjunto é lido da
posição corrente até o final todas as vezes. Note que além de comparar todos elementos, ainda existe mais uma
comparação que é feita no final (linha 16) para identificar se deve ser realizada a troca. A vantagem do método
Selection Sort é que as trocas são feitas apenas para as posições corretas dos elementos, diferente do método Bubble
Sort.
No método de Ordenação por Inserção (Insertion Sort) [9], é dado que o conjunto está ordenado até uma
determinada posição (i). A cada interação é verificado se o elemento atual (j) é menor que o elemento anterior
(j-1), caso afirmativo a troca é feita. Na listagem 4 segue o algoritmo:
Listagem 4: Ordenação por Inserção
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
*
* Insertion Sort
* Ordenação por Inserção
* Comentário: O método de inser ção percorre todos os elementos fazendo os testes para com o primeiro e
* segundo elemento, inserindo o elemento maior em uma posição mais a frente .
*
*/
int i;
for (int j = 1; j < arquiva.length; j++) {
temp.setValor(arquiva[j].getValor());
temp.setNomeArquivo(arquiva[j].getNomeArquivo());
temp.setLetra(arquiva[j].getLetra());
i = j - 1;
while (i >= 0 && arquiva[i].getValor() > temp.getValor()) {
arquiva[i + 1].setValor(arquiva[i].getValor());
arquiva[i + 1].setNomeArquivo(arquiva[i].getNomeArquivo());
arquiva[i + 1].setLetra(arquiva[i].getLetra());
i = i - 1;
}
arquiva[i + 1].setValor(temp.getValor());
arquiva[i + 1].setNomeArquivo(temp.getNomeArquivo());
arquiva[i + 1].setLetra(temp.getLetra());
}
Para conjuntos com elementos praticamente ordenados este método se mostra bastante eficiente. Sua principal utilização é em conjuntos com poucos elementos. Uma desvantagem é quando os elementos a serem ordenados,
encontram-se na forma inversa, uma vez que resulta um grande número de trocas.
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
4
No método de Ordenação por Fusão (Merge Sort) [8], os elementos a serem ordenados são separados em
dois conjuntos de forma que a ordenação é feita separadamente em cada um destes, por fim é feita a leitura de
um valor de cada conjunto a fim de identificar qual elemento deve ser colocado no conjunto resultante, de forma
a ficar ordenado. Neste método é necessário armazenar a última posição de cada conjunto, pois é possível que
um conjunto tenha sido todo copiado para o conjunto final, necessitando apenas copiar os elementos do outro
conjunto. A listagem 5 a implementação que chama as ordenações (linhas 15 e 16) dos elementos e fusão (linha
17) dos conjuntos:
Listagem 5: Ordenação por Fusão
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
*
* Merge Sort
* Ordenação por Fusão
* Comentário: O algoritmo a seguir divide o conjunto com os elementos em dois . Cada conjunto é ordenado com a chamada
* do método sortToMerge, linhas 15 e 16. Este método é chamado recursivamente até que todo conjunto seja dividido em
* segmentos ordenados. Por fim os segmentos são unificados pelo método merge. O método merge tem por finalidade realizar
* a comparação entre os 2 segmentos gerados e identificar o elemento e ser lido a fim de deixar o vetor resultante
* ordenado.
*
*/
public static Arquiva[] sortToMerge(Arquiva[] vetor, int inicio, int fim) {
if (inicio < fim) {
int meio=(inicio+fim)/2;
sortToMerge(vetor, inicio, meio);
sortToMerge(vetor, meio+1, fim);
merge(vetor, inicio, meio, fim);
}
return vetor;
}
Este método de ordenação utiliza recursividade para ordenação dos elementos, este recurso evita que o
conjunto seja lido até o final, fazendo menos trocas dos elementos. Outra vantagem deste método é que ele divide
o problema em dois, fazendo com que o número de operações seja menor. Sua principal desvantagem é a utilização
de memória para alocar o conjunto resultante.
No método de Ordenação Rápida (Quick Sort) [8], um elemento é escolhido como pivô, em seguida todos
elementos antes do pivô devem ser menores que pivô e todos elementos depois devem ser maiores, isso é feito de
forma recursiva, isto é, para cada parte do conjunto esta divisão é feita sucessivamente. Na listagem 6 segue a parte
principal deste método de ordenação:
Listagem 6: Ordenação Rápida
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
*
* Quick Sort
* Ordenação Rápida
* Comentário: O algoritmo percorre o conjunto iniciando pelos elementos posicionados na primeira e última posição
* do conjunto (2 laços intermedi ários ) . Tanto o elemento da primeira posição quanto o da última são comparados com o
* elemento central do conjunto . A troca de elementos é feita apenas depois de uma comparação. Nos dois testes lógicos
* tem−se a chamada recursiva para o método.
*
*/
private static Arquiva[] quick(Arquiva[] array, int indiceMenor, int indiceMaior) {
int i = indiceMenor;
int j = indiceMaior;
int pivo = array[indiceMenor+(indiceMaior-indiceMenor)/2].getValor();
while (i <= j) {
while (array[i].getValor() < pivo) {
i++;
}
while (array[j].getValor() > pivo) {
j--;
}
if (i <= j) {
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
5
array=trocaValores(array, i, j);
i++;
j--;
23
24
25
}
}
if (indiceMenor < j)
quick(array, indiceMenor, j);
if (i < indiceMaior)
quick(array, i, indiceMaior);
return array;
26
27
28
29
30
31
32
33
}
Na implementação do método Quick Sort a escolha do pivô tem bastante impacto no seu número de operações. A escolha do pivô pode ser fixa ou aleatória, no caso apresentado, foi fixa, o pivô é sempre o elemento da
posição do meio do array. A expectativa é que este valor não seja o maior do conjunto nem o menor. Na linha 20 o
método chama a rotina “trocaValores” que é responsável por trocar os elementos de posição, sendo que o elemento
da posição (i) vai para posição (j) e o elemento da posição (j) vai para posição (i). Este método de ordenação é
amplamente utilizado na prática.
No método de Ordenação pela Raiz (Radix Sort) [8] os elementos são comparados por partes, isto é, cada
elemento é ordenado baseando-se em uma parte do seu valor, isso é feito com o dígito menos significativo (para
elementos com mesma quantidade de dígitos). Com esta primeira análise é feita a alocação dos elementos com o
dígito menos significativo igual a 0 (zero) antes dos elementos com dígito menos significativo igual a 1 (um). O
passo seguinte consiste em ler o próximo dígito dos elementos e repetir o processo. Este algoritmo de ordenação
também utiliza o conceito de ordenação por contagem, desta forma é necessário espaço em memória para que os
elementos sejam alocados temporariamente. Mesmo se tratando de análise dos dígitos este método de ordenação
pode ser utilizado para ordenação de palavras, basta converter os caracteres das palavras por valores numéricos,
usando, por exemplo, o código ASCII. Na listagem 7 segue a implementação Radix Sort.
Listagem 7: Ordenação pela Raiz
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
*
* Radix Sort
* Ordenação pela Raiz
* Comentário: O algoritmo analisa cada dí gito de cada elemento do conjunto . Na linha 17 é realizado o teste
* para verificar se o dí gito de cada possí vel posição de um inteiro de 32 bits é um valor negativo . Tais valores
* são alocados em um conjunto temporário . No final este conjunto é preenchido com os demais valores ( linha 30) .
* Por fim ( linha 37) os valores são armazenados no array original .
*
*/
for (int desloca = Integer.SIZE - 1; desloca > -1; desloca--) {
Arquiva[] temp;
temp = new Arquiva[tam];
int j = 0;
for (int i = 0; i < tam; i++) {
boolean mover = original[i].getValor() << desloca >= 0;
if (desloca == 0 ? !mover : mover) {
temp[j] = new Arquiva();
temp[j].setValor(original[i].getValor());
temp[j].setNomeArquivo(original[i].getNomeArquivo());
temp[j].setLetra(original[i].getLetra());
j++;
} else {
original[i - j].setValor(original[i].getValor());
original[i - j].setNomeArquivo(original[i].getNomeArquivo());
original[i - j].setLetra(original[i].getLetra());
}
}
29
30
31
32
33
for (int i = j; i < temp.length; i++) {
temp[i]=new Arquiva();
temp[i].setValor(original[i - j].getValor());
temp[i].setNomeArquivo(original[i - j].getNomeArquivo());
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
6
temp[i].setLetra(original[i - j].getLetra());
34
}
35
36
for (int i = 0; i < tam; i++) {
original[i].setValor(temp[i].getValor());
original[i].setNomeArquivo(temp[i].getNomeArquivo());
original[i].setLetra(temp[i].getLetra());
}
37
38
39
40
41
42
}
O algoritmo Radix Sort faz 32 vezes a leitura de todo o conjunto. Isso parece ser algo relativamente
complexo, mas cada uma destas 32 comparações não é uma tarefa complexa, pois se trata de uma comparação
de apenas 1 dígito.
O método de Ordenação Shell Sort, proposto pelo matemático e cientista da computação Donald L. Shell
[10] é uma extensão do Insertion Sort, pois permite trocas por elementos que estão distantes, isto é, não troca
elementos imediatamente distinto, mas sim o elemento mais adequado a posição [11]. Na listagem 8 segue a
implementação Shell Sort.
Listagem 8: Ordenação proposta pelo Donald L. Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
*
* Shell Sort
* Ordenação proposta pelo Donald L. Shell
* Comentário: O algoritmo compara o elemento central com o primeiro elemento( linha 20) . O processo se repete sendo
* que o segundo elemento é comparado com o próximo elemento depois da posição central , seguindo este processo até
* que não seja realizada nenhuma troca e que o último elemento trocado seja menor que o próximo elemento lido ,
* controlado pelo i do laço da linha 13.
*
*/
int incremento = tam / 2;
while (incremento > 0) {
for (int i = incremento; i < tam; i++) {
int j = i;
Arquiva temp;
temp = new Arquiva();
temp.setValor(elementos[i].getValor());
temp.setNomeArquivo(elementos[i].getNomeArquivo());
temp.setLetra(elementos[i].getLetra());
while (j >= incremento && elementos[j - incremento].getValor() > temp.getValor()) {
elementos[j].setValor(elementos[j - incremento].getValor());
elementos[j].setNomeArquivo(elementos[j - incremento].getNomeArquivo());
elementos[j].setLetra(elementos[j - incremento].getLetra());
j = j - incremento;
}
elementos[j].setValor(temp.getValor());
elementos[j].setNomeArquivo(temp.getNomeArquivo());
elementos[j].setLetra(temp.getLetra());
}
if (incremento == 2) {
incremento = 1;
} else {
incremento *= (5.0 / 11);
}
}
Este método de ordenação utiliza espaço em memória para alocar temporariamente os elementos. Foram
propostas alterações [12] e [13] visando reduzir o número de comparações realizadas por este método de ordenação.
O método de Ordenação pelo “Monte” (Heap Sort) [1], utiliza conceitos de árvores binárias para identificar o maior elemento dentre um sub conjunto (2 filhos e um nó raiz). A rotina responsável por manter os maiores
valores (Heap máximo), na raiz é definida como MAX-HEAPIFY [1]. Na listagem 9 segue o método “heapify”,
responsável por esta tarefa.
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
7
Listagem 9: Ordenação pelo Monte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
*
* Heap Sort
* Ordenação pelo monte
* Comentário: O algoritmo segmenta o conjunto identificando um elemento mais central ( linha 3) que será comparado
* (pelo método siftDown) com dos dois ú ltimos elementos do conjunto . A troca é realizada fazendo com que a próxima
* comparação seja feita , seguindo−se para o in í cio do conjunto e deslocando os elementos da esquerda do elemento
* central para a sua direita .
*
*/
public static void heapify(Arquiva[] a, int tam){
int noRoot = (tam - 2) / 2;
while (noRoot >= 0){
siftDown(a, noRoot, tam - 1);
noRoot--;
}
}
O método “siftDown” realiza a troca dos elementos, da mesma forma que é feito, por exemplo, no método
Bubble Sort. Para dados com elementos aleatórios, este método de ordenação se mostra bastante adequado, pois
mantém sua complexidade (melhor e pior caso) em valores próximos. Uma das desvantagens deste método é a
utilização de memória para construção e armazenamento da árvore binária.
Além dos algoritmos a serem implementados pelo desenvolver, também existem métodos de ordenação em
diversas linguagens de programação. Adicionalmente será analisado o tempo gasto pelo método de ordenação
sort() da classe Collections, presente na linguagem de programação Java. Será usado este método, passando dois
parâmetros, o primeiro é o conjunto de elementos, o outro é um comparador, que foi implementado. Neste caso, na
implementação foi definido o valor inteiro para ser adotado na ordenação, assim como foi feito nas implementações
descritas acima.
2.2
Custo dos algoritmos
Analisando a complexidade envolvida nos algoritmos de ordenação, foram adotadas duas principais considerações, o melhor caso e o pior caso. A Tabela 1 apresenta um comparativo nesta perspectiva. Neste comparativo
também é apresentado o algoritmo de ordenação Counting Sort o qual pressupõe que cada um dos n elementos da
entrada é um inteiro no intervalo de 1 até k. A ideia deste algoritmo de ordenação é que para cada elemento x da
entrada é identificado a quantidade de elementos menores que x. Com base nesta informação é possível inserir o
elemento x na sua posição correta. Conforme mostra a Tabela 1, as principais complexidades são:
1)O(n2 )
2)O(n lg(n))
3)O(d(n + k))
4)O(k + n).
Para ilustrar a complexidade de cada algoritmo é apresentada a Figura 1, que exibe um gráfico, sendo que
o eixo vertical representa as diferentes complexidades, no eixo horizontal é apresentado o valor de “n”, que indica
a quantidade de elementos a serem ordenados, os valores escolhidos para “n” iniciam em 10 e vão até 50 (os
elementos variam de 1 até 500). Cada linha no gráfico indica o resultado das complexidades para cada valor de
“n”, sendo possível identificar que a complexidade 1) O(n2 ) (linha na cor verde) aumenta significativamente com
relação as demais.
3
Pré-Processamento
Diante dos conceitos apresentados sobre ordenação, é possível desenvolver operações de pré-processamento
para facilitar tal processo. O pré-processamento consiste em uma técnica de suporte, uma vez que apenas segmenta
os elementos de acordo com faixas. Tais faixas são geradas baseando-se em limites inferiores e superiores dos
elementos a serem ordenados, sendo que é necessário conhecer previamente o maior e o menor elemento. O pré-
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
8
Tabela 1: Complexidade dos algoritmos de ordenação
Algoritmo
melhor caso pior caso
1)Bubble Sort
O(n2 )
O(n2 )
2
2)Selection Sort O(n )
O(n2 )
3)Insertion Sort O(n)
O(n2 )
4)Merge Sort
O(n lg(n))
O(n lg(n))
5)Quick Sort
O(n lg(n))
O(n2 )
6)Radix Sort
O(n)
O(d(n + k)) *
7)Shell Sort
O(n lg(n))
O(n2 )
8)Heap Sort
variável
O(n lg(n))
9)Counting Sort variável
O(k + n) **
Legenda da TABELA 1.
n = quantidade de elementos no conjunto de entrada.
lg(n) = logaritmo binário de n, lg2 (n). Onde 2x = n [1].
∗ d = quantidade de dígitos; k = possíveis valores para cada dígito [1].
∗∗ k = valor do último elemento do intervalo presente no conjunto [1].
Figura 1: Comparativo das Complexidades
processamento se diferencia do conceito dividir para conquistar [14], uma vez que os elementos não são apenas
segmentados e sim organizados por faixas de similaridade, fazendo com que qualquer elemento do segmento 1
(um), seja menor que qualquer elemento do segmento 2 (dois).
Os elementos são provenientes de arquivos JSON (JavaScript Object Notation), gerados de forma automática por métodos desenvolvidos para a criação dos arquivos e escolha de números aleatórios (Reversed, Random,
Few Unique e Nearly Sorted), conforme critérios estipulados pelo usuário. Conforme a disposição dos elementos
a serem ordenados, um método de ordenação pode ser mais eficiente do que outro. Diante desta adversidade gerada pelos dados, foram realizados 10 (dez) testes com cada um dos algoritmos (com e sem o pré-processamento).
Antes da ordenação, o algoritmo deve realizar a leitura de “n” arquivos e obter os elementos que se deseja ordenar,
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
9
isto é, cada arquivo contém um elemento.
A tarefa de pré-processamento foi implementada considerando dois aspectos: distribuição dos dados e
quantidade de elementos a serem ordenados. Diante destes dois aspectos se identifica que quanto mais elementos a
serem ordenados, mais custo tem o pré-processamento. Para tratar do primeiro aspecto, que consiste em identificar
como os dados serão distribuídos, é feito um laço de repetição que segmenta os elementos a serem ordenados na
quantidade de conjuntos (array), definida pelo usuário.
4
Implementação
Os algoritmos de ordenação foram utilizados em amostras distintas de dados, isto é, os elementos a serem
ordenados foram distribuídos de forma aleatória (Random), praticamente ordenado (Nearly Sorted), praticamente
desordenado (Reversed) e poucos elementos únicos (Few Unique) também de forma aleatória. O intervalo da
entrada, ou seja, dos elementos a serem ordenados, foi definido de 1 até 200000.
O comparativo foi realizado levando-se em consideração quantas comparações2 são feitas e quantas trocas
(de posição dos elementos na memória) são realizadas na ordenação [15]. A tarefa de pré-processamento que
é realizada antes da ordenação também foi computada, pois o pré-processamento tem um custo que deve ser
considerado.
Para realização dos testes foi implementada uma rotina de inicialização, essa rotina funciona da seguinte
forma: é definido por um parâmetro a quantidade de arquivos e outro parâmetro permite definir a distribuição dos
elementos. Dentro de cada arquivo gerado existe um objeto no formato JSON, com duas tags. A primeira tag
contém uma palavra, a segunda contém um número, que será o elemento a ser ordenado. Segue a estrutura do
arquivo: "letra": "AJBY","valor":110272.
Cada arquivo recebe um nome diferente, gerado automaticamente, por exemplo: arquivo_1.json. No entanto, o número (no caso _1) não é utilizado nas ordenações, permitindo que o arquivo tivesse um nome qualquer,
este recurso foi escolhido apenas para gerar nomes distintos para os “n” arquivos. A palavra gerada dentro da tag
letra, pode ser qualquer palavra.
Antes da ordenação dos elementos, são lidos os arquivos que foram criados anteriormente e o conteúdo
da tag letra, bem como o número que está dentro da tag valor, são armazenados em um array de 5 mil posições.
Adicionalmente é armazenado o nome do arquivo, a fim de possibilitar que no final seja exibida uma lista com
os nomes dos arquivos de forma ordenada pelo número que este contém. Com isso, tem-se todos os atributos
(letra, valor, nomeArquivo) para compor a estrutura com os dados a serem ordenados. Em seguida são aplicados
os algoritmos de ordenação.
Para melhorar os algoritmos de ordenação, foi implementada uma técnica, que consiste em agrupar os
elementos por semelhança. O agrupamento é feito durante a leitura dos arquivos, os elementos ficam segmentados
em “n” array. A quantidade de comparações realizadas após a implementação da técnica é variável, uma vez
que, cada um dos array pode conter uma quantidade variada de elementos. A expectativa é que exista a mesma
quantidade de elementos em cada array, no entanto este número pode variar. Para realizar esta segmentação, cada
elemento foi comparado, afim de identificar o grupo do mesmo. A seguir na listagem 10 é apresentada a técnica
para segmentação.
Listagem 10: Listagem para segmentação dos grupos
1
2
3
4
5
6
7
8
9
10
2 As
/**
*
* Comentário: Este código percorre os elementos e para cada elemento é feito um teste para verificar qual é o seu
* respectivo segmento.
*
*/
for (int i = 0; i < x; i++) {
divTemp = (double)200000/x;
a = 1+(divTemp*i);
b = divTemp*(i+1);
comparações são feitas entre dois inteiros, mas podem ser, também, entre dois caracteres.
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
10
if ((arquiva[j].getValor() >= a) && (arquiva[j].getValor() <= b)) {
arquiva2[i][gs[i]]=new Arquiva();
arquiva2[i][gs[i]].setValor(arquiva[j].getValor());
arquiva2[i][gs[i]].setNomeArquivo(arquiva[j].getNomeArquivo());
arquiva2[i][gs[i]].setLetra(arquiva[j].getLetra());
gs[i]++;
}
11
12
13
14
15
16
17
18
}
A variável x indica a quantidade de array(s) que os elementos serão divididos. Na linha 16 é realizado o
incremento na variável “gs”, que é responsável por armazenar a quantidade de elementos em cada array.
4.1
Testes
É possível reduzir a complexidade segmentando os elementos em array(s). Para exemplificar tal redução é
apresentada a Tabela 2, onde é exibida a complexidade, dividindo-se 50 elementos em 1, 2, 3, 4 e 5 array.
Tabela 2: Complexidade dos algoritmos de ordenação com divisão dos elementos
Complexidade n = 50 n = 25 ∗ 2 n ∼
= 16 ∗ 3 n ∼
= 12 ∗ 4 n = 10 ∗ 5
1)O(n2 )
2500
1250
768
576
500
2)O(n lg(n))
282
232
192
172
166
3)O(d(n + k)) 360
420
468
528
600
4)O(k + n)
550
1050
1548
2048
2550
Legenda da TABELA 2.
n = 50 indica que os 50 elementos foram alocados em um único array.
n = 25 ∗ 2 indica que os 50 elementos foram alocados em dois arrays.
n∼
= 16 ∗ 3 indica que os 50 elementos foram alocados em três arrays.
n∼
= 12 ∗ 4 indica que os 50 elementos foram alocados em quatro arrays.
n = 10 ∗ 5 indica que os 50 elementos foram alocados em cinco arrays.
Na Figura 2 é exibido um gráfico, sendo que o eixo vertical representa as quantidades de array em que
os 50 elementos foram segmentados. Este gráfico ilustra que apenas as complexidades O(n2 ) e O(n lg(n)) se
beneficiam da segmentação.
Nos testes de ordenação foram registrados os tempos de execução de cada algoritmo. O computador utilizado possuía a seguinte configuração: processador Intel Core I5 2.5GHz, 4GB DDR3 1333MHz de memória RAM
e disco rígido de 500GB 5400RPM.
Cada algoritmo de ordenação foi executado 10 vezes e o tempo utilizado como base foi a média destes 10
testes. Foram analisados os 8 algoritmos de ordenação (Bubble Sort, Insertion Sort, Selection Sort, Quick Sort,
Merge Sort, Radix Sort, Shell Sort, Heap Sort) descritos na seção II e também o método de ordenação sort() da
classe Collections da linguagem Java. Cada um dos algoritmos foi aplicado em 4 tipos de espalhamento (Random,
Nearly Sorted, Reversed e Few Unique) e a quantidade de grupos testada foi 2, 4, 8, 12, 16 e 20.
O primeiro algoritmo testado foi o Bubble Sort. Na Figura 3 (a) Bubble Sort é apresentado o gráfico
comparativo dos tempos de execução do algoritmo Bubble Sort aplicado nos elementos a serem ordenados. É
possível verificar que com o aumento da quantidade de grupos o tempo necessário para ordenação é reduzido.
A redução de tempo é verificada em todos os tipos de espalhamento de elementos. Em seguida foi testado o
algoritmo Insertion Sort, na Figura 3 Insertion Sort é apresentado o gráfico comparativo dos tempos de execução
deste aplicado nos elementos a serem ordenados.
O terceiro algoritmo foi o Selection Sort. Na Figura 4 (a) Selection Sort é apresentado o gráfico comparativo
dos tempos de execução deste algoritmo aplicado nos elementos a serem ordenados. O quarto algoritmo foi o Quick
Sort. Na Figura 4 (b) Quick Sort é apresentado o gráfico comparativo dos tempos de execução do mesmo. Este
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
11
Figura 2: Comparativo dos Grupos
Figura 3: Algoritmo Bubble Sort e Insertion Sort
(a) Bubble Sort
(b) Insertion Sort
algoritmo apresentou pouca redução no tempo de ordenação nos espalhamentos Random, Few Unique e Nearly
Sorted, sendo que no espalhamento Reversed não houve redução no tempo e sim, aumento. Este aumento ocorreu
pois o pré-processamento não reduziu o tempo neste tipo de espalhamento, apenas nos outros 3 espalhamentos.
O quinto algoritmo foi o Merge Sort. Na Figura 5 (a) Merge Sort é apresentado o gráfico comparativo dos
tempos de execução deste algoritmo aplicado nos elementos a serem ordenados. O sexto algoritmo foi o Radix
Sort. Na Figura 5 (b) é apresentado o gráfico comparativo dos tempos de execução deste aplicado nos elementos
a serem ordenados. Quando o espaço de memória não é um recurso limitante, é preferível utilizar Radix Sort, já
que este algoritmo utiliza um espaço de memória para organizar os elementos já ordenados na sua posição correta.
Radix Sort é o mais recomendado para ordenar elementos de mesmo tamanho, tais como datas, CEPs, CPF e
endereço IP.
O sétimo algoritmo foi o Shell Sort. Na Figura 6 (a) Shell Sort é apresentado o gráfico comparativo dos
tempos de execução deste, aplicado nos elementos a serem ordenados. O oitavo algoritmo foi o Heap Sort. Na
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
12
Figura 4: Algoritmo Selection Sort e Quick Sort
(a) Selection Sort
(b) Quick Sort
Figura 5: Algoritmo Merge Sort e Radix Sort
(a) Merge Sort
(b) Radix Sort
Figura 6 (b) Heap Sort é apresentado o gráfico comparativo dos tempos de execução deste aplicado nos elementos
a serem ordenados.
Por fim o nono algoritmo foi o sort(), da classe Colletions da linguagem Java. Na Figura 7 é apresentado o
gráfico comparativo dos tempos de execução do método sort() aplicado nos elementos a serem ordenados.
5
Conclusão
Neste artigo foi apresentado o funcionamento dos algoritmos de ordenação mais comuns na literatura. Para
melhorar o processo de ordenação é apresentada uma alternativa, que consiste em segmentar os elementos a serem
ordenados. Esta tarefa é realizada durante o carregamento dos dados, ou seja, durante a leitura dos arquivos que
contém os números a serem ordenados. Uma das vantagens proposta por esta alternativa é a redução do tempo
gasto pelos algoritmos de complexidade O(n2 ) e O(n lg(n)), conforme foi demonstrado na Figura 2.
A complexidade dos algoritmos é dada por equações descritas na seção II. A fim de verificar o resultado
prático da alternativa proposta, cada algoritmo de ordenação foi analisado 10 vezes em cada tipo de espalhamento.
Em seguida o mesmo foi feito, porém com os elementos a serem ordenados, agrupados em 2, 4, 8, 12, 16 e 20
grupos. Nos testes foi possível identificar a redução no tempo de execução ao aumentar os grupos, sendo que
ocorre uma estabilidade entre 16 e 20 grupos. Outra constatação importante foi que os algoritmos que inicialmente
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
13
Figura 6: Algoritmo Shell Sort e Heap Sort
(a) Shell Sort
(b) Heap Sort
Figura 7: Collections Sort
apresentavam um tempo de execução bastante distinto, chegaram em valores próximos quando a tarefa de processamento segmentou os elementos entre 16 e 20 grupos. Outro fator que pode ser levado em consideração é a
arquitetura do computador, pois isso pode levar os algoritmos a apresentar diferenças no tempo de execução.
O conceito de dividir uma tarefa complexa em tarefas menores também pode ser aplicado ao caso estudado.
No entanto a técnica de pré-processamento é mais adequada, visto que o pré-processamento pode ser feito no
momento em que os dados são lidos dos arquivos, implicando em pouco acréscimo de código.
A técnica da bolha aplicada em um array de 5 mil elementos, apresenta custo de: 25000000, pois adota a
complexidade dada por O(n2 ). Com a técnica de agrupamento por semelhança o custo é reduzido pela metade
(se for criado 2 grupos), acrescido da quantidade de comparações adicionadas no pré-processamento (2 para cada
elemento neste caso). A redução segue enquanto a quantidade de grupos aumenta, no caso de gerar 4 grupos, o
custo é de aproximadamente 6250000 no entanto existe um limite. No caso de 5 mil arquivos a quantidade ideal
de grupos é de 3500. No entanto durante os testes se percebeu que todos os algoritmos se estabilizam ao gerar 20
grupos.
É possível que em conjuntos com mais de 5000 elementos as quantidades de grupos para chegar a estabilidade pode ser maior. Em trabalhos futuros almeja-se verificar o comportamento dos algoritmos em conjuntos
maiores do que 5000 elementos.
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
14
Agradecimentos
Os autores gostariam de agradecer aos professores, Sandro José Rigo, José Vicente Canto dos Santos e
Jorge Luis Victória Barbosa, do Programa Interdisciplinar de Pós-Graduação em Computação Aplicada (PIPCA)
da UNISINOS, que não mediram esforços para auxiliar na pesquisa. Fica o agradecimento a todo grupo de pesquisa
do MobiLab/UNISINOS (Laboratório de Computação Móvel), que contribuíram para a definição da técnica de préprocessamento e com sugestões para melhora do texto. Fica registrado o profundo agradecimento a CAPES/Brasil
(Coordenação de Aperfeiçoamento de Pessoal de Nível Superior), PROSUP/Brasil (Programa de Suporte à PósGraduação de Instituições de Ensino Particulares), Santander Universidades pela bolsa de mestrado e CNPq/Brasil
(Conselho Nacional de Desenvolvimento Científico e Tecnológico) pelo apoio a pesquisa.
Referências
[1]
CORMEN, T. H. et al. Algoritmos: teoria e prática. 2nd. ed. [S.l.]: Elsevier, 2002.
[2]
BABU, D. et al. Array-indexed sorting algorithm for natural numbers. In: Communication Software and
Networks (ICCSN), 2011 IEEE 3rd International Conference on. [S.l.: s.n.], 2011. p. 606–609.
[3]
SINGH, S.; KAUR, S. Freeze Sorting Algorithm Based on Even-Odd Elements. IOSR Journal of Engineering, v. 4, n. 3, p. 18–23, 2014.
[4]
SAREEN, P. Comparison of sorting algorithms (on the basis of average case). International Journal of Advanced Research in Computer Science and Software Engineering, ACM, v. 3, n. 3, p. 522–532, mar. 2013.
ISSN 2277 128X.
[5]
GARCIA, A. M.; CERA, M. C.; MERGEN, S. L. Analisando o Desempenho da Paralelização no Algoritmo
de Ordenação Mergesort In-place. [S.l.]: E&9, 2013.
[6]
DARLINGTON, J. A synthesis of several sorting algorithms. Acta Informatica, Springer-Verlag, v. 11, n. 1,
p. 1–30, 1978. ISSN 0001-5903. Disponível em: <http://dx.doi.org/10.1007/BF00264597>.
[7]
ZAKI, M. J.; JR, W. M. Data Mining and Analysis: Fundamental Concepts and Algorithms. [S.l.]: Cambridge University Press, 2014.
[8]
GOODRICH, M. T.; TAMASSIA, R. Data Structures and Algorithms in Java. 2nd. ed. New York, NY, USA:
John Wiley & Sons, Inc., 2000. ISBN 0471383678, 9780471383673.
[9]
ASCENCIO, A. F. G.; ARAÚJO, G. S. d. Estrutura de dados: algoritmos, análise da complexidade e implementações em JAVA e C/C++. [S.l.]: Pearson Prentice Hall, 2010. ISBN 9788576058816.
[10] SHELL, D. L. A High-speed Sorting Procedure. Commun. ACM, ACM, New York, NY, USA, v. 2, n. 7, p.
30–32, jul. 1959. ISSN 0001-0782. Disponível em: <http://doi.acm.org/10.1145/368370.368387>.
[11] SHAHZAD, B.; AFZAL, M. T. Enhanced ShellSorting Algorithm. Computer Journal of Enformatika, v. 21,
n. 6, p. 66–70, 2007.
[12] POONEN, B. The Worst Case in Shellsort and Related Algorithms. Journal of Algorithms, v. 15, p. 101–124,
1993.
[13] PLAXTON, C. G.; SUEL, T. Lower Bounds for Shellsort. Departament of Computer Science, University of
Texas at Austin, 1992.
[14] PATEL, Y.; SINGH, N.; VASHISHTHA, L. Fuse sort algorithm a proposal of divide & conquer based sorting
approach with o(nloglogn) time and linear space complexity. In: Data Mining and Intelligent Computing
(ICDMIC), 2014 International Conference on. [S.l.: s.n.], 2014. p. 1–6.
[15] KNUTH, D. E. Sorting and Searching, volume 3 of The Art of Computer Programming. [S.l.]: MA: AddisonWesley, 1973.
Revista Brasileira de Computação Aplicada(ISSN 2176-6649), Passo Fundo, v. 7, n. 1, p. xx-xx, abr. 2015
15
Download

iSorting: Um estudo sobre otimização em