Algoritmos de Ordenação: Insertion Sort
Dando sequência à série, agora será explicado outro algoritmo de ordenação conhecido como insertion sort (ordenação por inserção).
Vamos ao Insertion Sort
A ideia do insertion sort consiste em ordenar os itens, inserindo-os na posição corresponde da lista. Mas, o que isso quer dizer afinal?
Vamos utilizar um exemplo do mundo real para melhor compreensão da ideia por trás do algoritmo: um baralho francês.
Imagine que você esteja comprando uma carta e que você já tenha algumas cartas na sua mão, as quais você já fez o trabalho de ordenar corretamente.
Você não sabe que carta virá, mas por motivos de organização para o jogo, gostaria de manter a ordenação conforme as cartas vão para sua mão.
No momento em que você saca uma carta, o valor se torna a referência para você fazer a ordenação (chamaremos de “chave” daqui para a frente) e então, começa a comparar o valor com as cartas da sua mão (começando da última carta à direita) em direção à esquerda, até que encontre uma carta que seja maior ou igual à esta.
Finalmente, você descobre que posição esta carta deve ficar de acordo com a ordem das cartas. Porém, as outras cartas que já estão na sua mão e são maiores precisam ser movidas para a direita para essa carta ser “encaixada” ou inserida naquela posição.
Essa é exatamente a ideia por trás do insertion sort.
Então, em resumo:
- Compare o valor do item “chave” que está entrando com os outros itens até que se sua posição seja encontrada. Essa comparação é feita em direção à esquerda.
- Se o item que você está comparando for menor, desloque o item para a direita , visando “abrir” um novo espaço para colocar a carta na posição correspondente);
- Finalmente ao encontrar um item maior ou não haver mais itens, significa que você encontrou a posição que este item deve estar: coloque o item “chave” na posição correspondente.
Vamos à um exemplo do algoritmo em execução (como sempre fazemos durante esta série :)).
Vamos voltar atrás e lembrar do selection sort. No post anterior, dividimos a lista entre lista ordenada e a lista não ordenada. Faremos a mesma coisa no insertion sort.
Desempenho
Apesar de essencialmente ser parecido com o selection sort, há algumas diferenças que valem a pena citar.
No selection sort, não temos pior e melhor caso. Ele sempre executará a mesma quantidade de vezes dado o tamanho da lista, não importa que itens venham na entrada.
Outra diferença se comparado ao selection sort é que há mais movimentações na lista (ocorrendo inplace). Caso o cenário dado haja alguma limitação ou maior custo para movimentação (I/O), selection sort seria uma melhor opção.
Já o insertion sort realiza menos operações se comparado ao selection sort caso a lista esteja quase ordenada.
Por exemplo, no melhor caso para o insertion sort é uma lista já ordenada, pois precisaria percorrer n — 1 vezes (todos os elementos da lista, com exceção ao primeiro).
Em relação às desvantagens, o insertion sort é um algoritmo com uma performance ruim para grande quantidades de itens a serem ordenados, pois assim como selection e bubble sort, ele precisa percorrer a lista para cada elemento (n * n = O(n²) — será explicado a seguir o porquê do algoritmo ser O(n²)).
Vamos utilizar o pior caso para realizar a análise assintótica: uma lista em ordem decrescente com os elementos: 5, 4, 3, 2, 1
Ao realizar o insertion sort, lembre-se que começamos pelo segundo item da lista. Este item será comparado com o elemento da primeira posição (1 operação)
Ao realizar o processo para o próximo item (valor 3), serão comparado dois itens que já foram ordenados: 4 e 5 (lembre-se, a comparação é feita até que se encontre alguém maior que à esquerda da posição da chave ou chegue ao fim da lista. Estamos analisando um pior caso que sempre percorrerá toda a lista nas comparações e sabemos que 3 não é maior que 4 e 5) (2 operações)
Ao realizar o processo para o próximo item (valor 2), serão comparado três itens que já foram ordenados: 3, 4 e 5 (lembre-se, a comparação é feita até que se encontre alguém maior que à esquerda da posição da chave ou chegue ao fim da lista. Estamos analisando um pior caso e sabemos que 2 não é maior que 3, 4, 5) (3 operações)
Ao realizar o processo para o último item (valor 1), serão comparado quatro itens que já foram ordenados: 2, 3, 4 e 5 (lembre-se, a comparação é feita até que se encontre alguém maior que à esquerda da posição da chave ou chegue ao fim da lista. Estamos analisando um pior caso e sabemos que 1 não é maior que 2, 3, 4, 5) (4 operações).
Vamos então verificar quantas operações foram necessárias:
4 + 3 + 2 + 1 = 10 operações
Caso fosse uma lista decrescente com os elementos 6, 5, 4, 3, 2, 1 teríamos:
5 + 4 + 3 + 2 + 1 = 15 operações
Se você leu o post sobre selection sort, isso soa familiar? É isso mesmo! O pior caso para insertion sort é o mesmo número de operações que o selection sort sempre leva para qualquer caso. Caso não tenha lido ou não lembre, vamos fazer um resumo:
Podemos agrupar essas somas em pares dos maiores com os menores valores respectivamente, pois a adição de cada par sempre será o número de elementos da lista (Por exemplo, numa lista decrescente com cinco elementos são necessárias dez operações para ordená-la ou: 1 + 2 + 3 + 4 + 5. Se agruparmos em pares os menores e os maiores, a soma dos mesmos sempre será cinco)
1 + 2 + 3 + 4 = (4 + 1) + (3 + 2)
(4 + 1) = 5
(3 + 2) = 5
Para descobrir o número de pares dado o número de elementos (n) em uma lista:
(n — 1) / 2
Sabemos também que a soma de cada par é o mesmo que o tamanho da lista. Logo, podemos multiplicar a soma de cada par (n) pela quantidade pares:
tamanho da lista * número de pares
n * (n — 1) / 2
(n² - n) / 2
(n² / 2) - (n / 2) = O(n²)
Assim como selection sort, sua análise assintótica é O(n²).
Existem casos que seu uso é adequado. Por exemplo, caso seja inseridos novos elementos na lista muitas vezes e essa lista já esteja ordenada, é necessário percorrer toda a lista apenas uma vez para descobrir a posição em que aquele item deve ficar. Em outras palavras: não é necessário executar todo o processo de ponta a ponta como em outros algoritmos de ordenação.
Exemplo
Por fim, um exemplo de código do algoritmo utilizando Python:
E por agora é só :)