Autores: Rodrigo Pansolim da Rosa e William
Almeida
Introdução:
Problema de Ordenação
Ordenação é o ato de se colocar os elementos de uma sequência
de informações, ou dados, em uma ordem de acordo com uma lógica determinística.
Dado um
conjunto de n dados:
Α = {a1, a2, a3
…. an} o dito
problema de ordenação do conjunto Α é resolvido quando os elementos são
dispostos de maneira lógica, perante uma de suas propriedades. No caso onde os
elementos do conjunto A são números, podemos utilizar o critério de grandeza, e
ordenar os elementos em ordem crescente, onde todos os elementos são
exclusivos, e não haverá repetições.
Logo teremos A = {s1,s2,s3,...,sn}
onde s1 < s2 < s3..........<sn.
Comparação de chaves
É dito como um algoritmo de ordenação por comparação
entre chaves, todo o tipo de algoritmo de ordenação que considera como operação
individual, a comparação entre dois elementos inteiramente colocados no
conjunto.
Os algoritmos de ordenação através de comparação de
chaves são os mais simples possíveis, consideram por logica imediata, resolver
o problema de ordenação através da característica determinante qual será a
posição de uma chave em relação as outras. Basicamente, para comparações
numéricas por ordem de grandeza, um algoritmo de ordenação deve seguir os
seguintes fatores determinísticos:
· Dados a,b,c com a < b e b < c logo a < c por
transitividade de implicação perante o silogismo hipotético;
· Dados a e b, temos que a
> b ou exclusivamente b > a, tricotomia perante operantes ou ainda
perante a lei da contradição (duas premissas opostas não podem ser mutuamente
verdadeiras).
Uma vez que
tal característica de ordenação tenha propriedades onde a sua definição
Conseguem
ser derivadas nas implicações acima. É possível ordenar elementos que possuam
esta característica a partir da mesma.
Métodos de ordenação usados na
construção híbrida do TimSort:
Insertion Sort, ou método de ordenação por inserção, é o
algoritmo de ordenação que percorre um vetor de elementos da esquerda para a
direita e à medida que avança vai deixando os elementos mais à esquerda
ordenados.
O merge
sort, ou ordenação por mistura, é um exemplo de algoritmo de ordenação por
comparação que segue o paradigma de divisão e conquista. O algoritmo divide o
problema em pedaços menores, resolve cada pedaço e depois junta (merge) os
resultados.
Definição:
Timsort é um algoritmo de ordenação por comparação de
chaves, híbrido derivado do merge sort e do insertion sort, projetado para ter
boa performance em vários tipos de dados do mundo real. Foi inventado por Tim
Peters em 2002 para ser usado na linguagem de programação Python, e tem sido o
algoritmo de ordenação padrão de Python desde a versão 2.3. Ele atualmente é
usado para ordenar arrays em Java SE 7.
Imagem de Tim Peter
Tim Peters descreve o algoritmo da seguinte forma:
Como o
merge sort, é um algoritmo de ordenação por comparação estável com uma
complexidade de pior caso de Θ ( n log n ).
Funcionamento:
Definições:
N: Tamanho do vetor de entrada.
Run: Um sub-vetor ordenado (decrescentemente ou
crescentemente) que compõe o vetor de entrada.
Minrun: é um comprimento mínimo de uma Run
calculado para N >= 64.
Ex de Runs:
O valor do minrun é determinado com base no valor
de N, pelos seguintes princípios:
1. O valor de minrun não deve ser tão grande →
minrun < 256;
2. O valor de minrun não deve ser tão pequeno →
minrun > 8;
3. Se N / minrun for uma potência de 2 (ou perto
disso).
Para calcular o Minrun basta pegar
os seis bits mais significativos de N, e somar 1 se os
bits menos significativos restantes contiverem pelo menos um bit com valor 1. Exceção: se N < 64
então minrun = N e o TIMSORT se tornará em um simples INSERTION SORT.
int GetMinrun (int n)
{
int r = 0;/*
Torna-se 1 se os bits menos significativos contêm pelo menos um bit */
while (n
>= 64) {
r = (r |
n) & 1;
n++;
}
return n +
r;
}
Em seguida o algoritmo prossegue os seguintes
passos:
1. O índice do elemento atual é colocado no início do
vetor de entrada.
2. Começando a partir da posição do elemento atual,
procure um Run (um sub-vetor ordenado) no vetor de entrada. Por
definição o Run será pelo menos o elemento atual e o próximo
(pois formará um vetor ordenado, seja crescente ou decrescente), sendo que a
composição de mais elementos no Run dependerá da forma como os
elementos estão organizados. O próximo elemento é considerado se ao
considerá-lo no Run atual, o Run continue
ordenado. Se o Run final está ordenado de forma decrescente,
os elementos são "reordenados" em uma ordem crescente (por meio de um
algoritmo simples de inversão de vetor).
3. Se o run atual for menor que o minrun, pega-se o
número de elementos que compõem a run atual que são iguais ao do minrun.
4. Então o Run atual é ordenado
via InsertionSort. Como este Run é pequeno e parcialmente
ordenado, a ordenação é rápida.
5. O índice do elemento atual é definido para o
elemento seguinte da run.
6. Se o fim do vetor não foi alcançado, executa-se
esse algoritmo novamente do ponto 2 até o ponto 6.
Por fim é realizado o merge:
1. Runs de tamanho relativamente iguais devem ser
combinados, para que se ganhe em eficiência.
2. Avalia se deve ser feito o merge: sejam X, Y e Z um
conjunto de 3 Runs que seguem as seguintes condições X > Y
+ Z e Y > Z. Cria-se um vetor temporário com o tamanho do menor sub-vetor,
depois copia os elementos do menor vetor pra dentro do
vetor temporário. Em cada passo ele compara os elementos do vetor Y com os
elementos do vetor temporário pra ordenar e coloca no
X.
Exemplo com A, B, e C
no lugar de X, Y e Z.
Galope
O galope é
outra técnica usada pela classificação Tim para reduzir ainda mais as comparações
durante a mesclagem, a fim de aumentar a eficiência de um algoritmo.
Enquanto
mescla dois sub-vetores A e B de maneira ordenada, a classificação Tim executa
galopando. O TimSort assume que, se muitos valores da execução A forem
inferiores aos valores da execução B, é provável que A continue a ter valores
menores que B. O galope utiliza a pesquisa binária para fazer menos comparações
durante o procedimento de mesclagem. Dessa forma, o Timsort pode mover uma
seção inteira de A e procurar um local apropriado para colocar em B. Ex:
O galope melhora o tempo de execução da
mesclagem, reduzindo as comparações.
Diagrama de fluxo do
Timsort
Resumindo:
Dividimos a
um vetor em blocos ordenados chamados run. Ordenamos esses runs usando o
InsertionSort, um por um e, em seguida, mesclamos esses runs usando a função
merge usada no MergeSort. Se o tamanho do vetor for menor que a run, a
matriz será ordenada apenas usando o InsertionSort. O tamanho da run pode
variar de 32 a 64, dependendo do tamanho do vetor. A função de mesclagem
funciona bem quando N / minrun é uma potência de 2 (ou perto disso). A idéia é
baseada no fato de que o InsertionSort funcionar bem para matrizes pequenas.
Em resumo, Timsort faz duas coisas incrivelmente
bem:
Vídeo TimSort sendo usado para ordenar vetores com chaves
dispostas aleatoriamente :
https://www.youtube.com/watch?v=NVIjHj-lrT4
Exemplos de códigos
do TimSort em C e Python: // C# program to perform TimSort. using System; class GFG { public const int RUN =
32; // this function sorts
array from left index to // to right index which
is of size atmost RUN public static void
insertionSort(int[] arr, int left, int right) { for
(int i = left + 1; i <= right; i++) {
int
temp = arr[i]; int
j = i - 1; while
(arr[j] > temp && j >= left) {
arr[j+1]
= arr[j]; j--;
}
arr[j+1]
= temp; }
} // merge function merges
the sorted runs public static void merge(int[] arr, int l, int m, int r) { //
original array is broken in two parts //
left and right array int
len1 = m - l + 1, len2 = r - m; int[] left = new int[len1]; int[] right = new int[len2]; for
(int x = 0; x < len1; x++) left[x]
= arr[l + x]; for
(int x = 0; x < len2; x++) right[x]
= arr[m + 1 + x]; int
i = 0; int
j = 0; int
k = l; //
after comparing, we merge those two array //
in larger sub array while
(i < len1 && j < len2) {
if
(left[i] <= right[j]) {
arr[k]
= left[i]; i++;
}
else {
arr[k]
= right[j]; j++;
}
k++;
}
//
copy remaining elements of left, if any while
(i < len1) {
arr[k]
= left[i]; k++;
i++;
}
//
copy remaining element of right, if any while
(j < len2) {
arr[k]
= right[j]; k++;
j++;
}
} // iterative Timsort
function to sort the // array[0...n-1]
(similar to merge sort) public static void
timSort(int[] arr, int n) { //
Sort individual subarrays of size RUN for
(int i = 0; i < n; i+=RUN) insertionSort(arr, i, Math.Min((i+31), (n-1))); //
start merging from size RUN (or 32). It will merge //
to form size 64, then 128, 256 and so on .... for
(int size = RUN; size < n; size = 2*size) {
//
pick starting point of left sub array. We //
are going to merge arr[left..left+size-1] //
and arr[left+size, left+2*size-1] //
After every merge, we increase left by 2*size for
(int left = 0; left < n; left += 2*size) {
//
find ending point of left sub array //
mid+1 is starting point of right sub array int
mid = left + size - 1; int
right = Math.Min((left + 2*size - 1), (n-1)); //
merge sub array arr[left.....mid] & //
arr[mid+1....right] merge(arr, left, mid, right); }
}
} // utility function to
print the Array public static void
printArray(int[] arr, int n) { for
(int i = 0; i < n; i++) Console.Write(arr[i]
+ " "); Console.Write("\n");
} // Driver program to test
above function public static void Main() { int[] arr = {5, 21, 7, 23, 19}; int
n = arr.Length; Console.Write("Given
Array is\n"); printArray(arr, n); timSort(arr, n); Console.Write("After
Sorting Array is\n"); printArray(arr, n); } //This code is
contributed by DrRoot_ } |
# Python3 program to perform TimSort. RUN = 32 # This function sorts array from left index
to # to right index which is of size atmost
RUN def insertionSort(arr,
left, right): for i in range(left + 1, right+1): temp
= arr[i] j
= i - 1 while
arr[j] > temp and j >= left: arr[j+1]
= arr[j] j
-= 1 arr[j+1]
= temp # merge function merges the sorted runs def merge(arr, l, m, r):
# original array is
broken in two parts # left and right
array len1, len2 = m - l + 1, r - m left, right = [],
[] for i in range(0, len1): left.append(arr[l + i]) for i in range(0, len2): right.append(arr[m + 1 + i]) i, j, k = 0, 0, l # after comparing, we
merge those two array # in larger sub
array while i < len1 and j
< len2: if
left[i] <= right[j]: arr[k]
= left[i] i
+= 1 else:
arr[k]
= right[j] j
+= 1 k
+= 1 # copy remaining elements
of left, if any while i < len1: arr[k]
= left[i] k
+= 1 i
+= 1 # copy remaining element
of right, if any while j < len2: arr[k]
= right[j] k
+= 1 j
+= 1 # iterative Timsort function to sort the # array[0...n-1]
(similar to merge sort) def timSort(arr,
n): # Sort individual
subarrays of size RUN for i in range(0, n, RUN): insertionSort(arr, i, min((i+31), (n-1))) # start merging from size
RUN (or 32). It will merge # to form size 64, then
128, 256 and so on .... size = RUN while size < n: #
pick starting point of left sub array. We #
are going to merge arr[left..left+size-1] #
and arr[left+size, left+2*size-1] #
After every merge, we increase left by 2*size for
left in range(0, n, 2*size): #
find ending point of left sub array #
mid+1 is starting point of right sub array mid
= left + size - 1 right
= min((left + 2*size - 1), (n-1)) #
merge sub array arr[left.....mid] & #
arr[mid+1....right] merge(arr, left, mid, right) size
= 2*size # utility function to print the Array def printArray(arr,
n): for i in range(0, n): print(arr[i],
end = " ") print()
# Driver program to test above function if __name__ == "__main__": arr = [5, 21, 7, 23,
19] n = len(arr) print("Given
Array is") printArray(arr,
n) timSort(arr,
n) print("After
Sorting Array is") printArray(arr,
n) # This code is
contributed by Rituraj Jain |
Referências:
https://hackernoon.com/timsort-the-fastest-sorting-algorithm-youve-never-heard-of-36b28417f399
https://dev.to/s_awdesh/timsort-fastest-sorting-algorithm-for-real-world-problems--2jhd
https://timsorteda.blogspot.com/2017/06/timsort.html
https://pt.wikipedia.org/wiki/Timsort
https://www.geeksforgeeks.org/timsort/
https://pt.wikipedia.org/wiki/Insertion_sort
https://pt.wikipedia.org/wiki/Merge_sort
https://pt.wikipedia.org/wiki/Merge_sort