Skip to content

Commit

Permalink
Add info sobre estratégia, código e complexidade do BubbleSort
Browse files Browse the repository at this point in the history
[x] adicionei informações sobre a estratégia
[x] refinamento da ilustração
[x] refinamento do código
[x] adicionei informações sobre a análise de complexidade
  • Loading branch information
eduardolfalcao authored Oct 6, 2023
1 parent f9f6803 commit e431241
Showing 1 changed file with 70 additions and 99 deletions.
169 changes: 70 additions & 99 deletions conteudos/ordenacao/InsertionSort.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# InsertionSort

InsertionSort não está entre os melhores algoritmos de ordenação, mas em cenários práticos é um pouco melhor do que os algoritmos já estudados, SelectionSort e BubbleSort.

No SelectionSort, a ideia é selecionar a menor carta da mão esquerda e trazer para o fim da mão direita.
A ideia do InsertionSort é selecionar a próxima carta da mão esquerda e **inserir de modo ordenado na mão direita**.
## Estratégia do algoritmo de acordo com seu nome

- Ordenação por inserção
- Escolhe um elemento do vetor desordenado, e insere no vetor ordenado na posição correta (que mantenha o vetor ordenado)
- Se no SelectionSort, a ideia é selecionar a menor carta da mão esquerda e trazer para o fim da mão direita, no InsertionSort a ideia é selecionar a próxima carta da mão esquerda e **inserir de modo ordenado na mão direita**.

## Ilustração do funcionamento do algoritmo

Imagine um jogador com as seguintes cartas: 8, 2, 4, 3, 7 (não interessando o naipe).
Vamos aplicar a ideia do InsertionSort e ilustrar na tabela a seguir.
Expand All @@ -17,38 +20,38 @@ t3 | 3, 7 | 2, 4, 8
t4 | 7 | 2, 3, 4, 8
t5 | VAZIO | 2, 3, 4, 7, 8

Quando fui implementar o InsertionSort, primeiramente pensei em um algoritmo que torna a implementação um pouco mais complexa.
Vou citar aqui pra que vocês tenham em mente a abordagem mais complexa, e evitem seguir este caminho.

Primeiro vamos implementar este algoritmo usando um array adicional, de mesmo tamanho.
Neste caso, esta implementação é **out-of-place**.
Não é muito difícil pensar no funcionamento in-place desse algoritmo.
Considere que os números em negrito já estão ordenados.

**Algoritmo Insertion-Sort (mais difícil de implementar):**
tempo | Vetor |
:-------------------------:|:-------------------------:
t0 | **8**, 2, 4, 3, 7 |
t1 | **2, 8**, 4, 3, 7 |
t2 | **2, 4, 8**, 3, 7 |
t3 | **2, 3, 4, 8**, 7 |
t4 | **2, 3, 4, 7, 8** |

Premissa: assuma que **v** é o vetor desordenado e **ordenado** é o vetor adicional
1. Para cada elemento em **v** a partir do índice 1 (elemento a ser inserido em *ordenado*)
2. Encontre o local de inserção ordenada no vetor **ordenado**. Atribua local à variável j.
3. Desloque uma posição à direita todos os elementos a partir de local até o "fim" de **ordenado**
4. Adicione o elemento em **ordenado[j]**
Agora uma ilustração com mais detalhes, que se aproxima mais da implementação.

tempo | Mão Esquerda | Mão Direita
tempo | Vetor | comentário
:-------------------------:|:-------------------------:|:-------------------------:
t0 | 8, 2, 4, 3, 7 | VAZIO
t1 | 2, 4, 3, 7 | 8
t1.1 | 2, 4, 3, 7 | 8, 8
t2 | 4, 3, 7 | 2, 8
t2.1 | 4, 3, 7 | 2, 8, 8
t3 | 3, 7 | 2, 4, 8
t3.1 | 3, 7 | 2, 4, 4, 8
t4 | 7 | 2, 3, 4, 8
t4.1 | 7 | 2, 3, 4, 8, 8
t5 | VAZIO | 2, 3, 4, 7, 8
t0 | **8**,2,4,3,7 | [8] já está ordenado, e [2, 4, 3, 7] desordenado
t1 | **2,8**,4,3,7 | 2 é menor que 8, por isso trocamos 2 e 8; **[2,8]** já está ordenado, e [4, 3, 7] desordenado
t2 | **2,4,8**,3,7 | 4 é menor que 8, por isso trocamos 4 e 8
t3 | **2,4,8**,3,7 | 4 não é menor que 2, por isso não trocamos 4 e 2; **[2,4,8]** já está ordenado, e [3, 7] desordenado
t4 | **2,4,3,8**,7 | 3 é menor que 8, por isso trocamos 8 e 3;
t5 | **2,3,4,8**,7 | 3 é menor que 4, por isso trocamos 4 e 3;
t6 | **2,3,4,8**,7 | 3 não é menor que 2, por isso não trocamos 2 e 3; **[2,3,4,8]** já está ordenado, e [7] desordenado
t7 | **2,3,4,7,8** | 7 é menor que 8, por isso trocamos 8 e 7;
t7 | **2,3,4,7,8** | 7 é não menor que 4, por isso não trocamos 4 e 7; todo o array está ordenado: **[2,3,4,7,8]**

## Código

Agora vamos ver como ficou a implementação out-of-place desta abordagem.

```c
//insertionSort Out-of-Place
void insertionSortOP(int** v, int tamanho) {
void insertionSort(int** v, int tamanho) {
int* ordenado = (int*)malloc(tamanho * sizeof(int));
ordenado[0] = (*v)[0];
for (int i = 1; i < tamanho; i++) { //i = indice da carta escolhida na mao esquerda
Expand All @@ -67,54 +70,11 @@ void insertionSortOP(int** v, int tamanho) {
}
```
Não é muito difícil transformar esta implementação em in-place.
tempo | Vetor |
:-------------------------:|:-------------------------:
t0 | **8**, 2, 4, 3, 7 |
t1 | **2, 8**, 4, 3, 7 |
t2 | **2, 4, 8**, 3, 7 |
t3 | **2, 3, 4, 8**, 7 |
t4 | **2, 3, 4, 7, 8** |
```c
//insertionSort In-Place
void insertionSortIP(int* v, int tamanho) {
for(int i = 1; i < tamanho; i++){ //i = indice da carta a ser inserida ordenada
int j;
for(j = 0; j < i; j++){ //j até i é são os limites do array ordenado
if(v[j] > v[i]) { //achamos a posicao da insercao
for (int k = j; k < i; k++) { //agora vamos abrir espaco
v[k + 1] = v[k];
}
break;
}
}
v[j] = v[i];
}
}
```

No entanto, existe uma outra abordagem para implementar o mesmo algoritmo, bastando para isso juntar os passos 2 e 3 em um único passo.
Basta ir trocando o elemento sendo ordenado com os elementos à sua esquerda, caso o elemento na posição anterior (esquerda) seja maior do que o elemento sendo ordenado.
Desta forma, abrir espaço para a inserção acontece de forma iterativa, até que o elemento sendo ordenado encontre sua posição.
Vejamos a ilustração.

tempo | array | comentário
:-------------------------:|:-------------------------:|:-------------------------:
t0 | **8**,2,4,3,7 | [8] já está ordenado, e [2, 4, 3, 7] desordenado
t1 | **2,8**,4,3,7 | 2 é menor que 8, por isso trocamos 2 e 8; **[2,8]** já está ordenado, e [4, 3, 7] desordenado
t2 | **2,4,8**,3,7 | 4 é menor que 8, por isso trocamos 4 e 8
t3 | **2,4,8**,3,7 | 4 não é menor que 2, por isso não trocamos 4 e 2; **[2,4,8]** já está ordenado, e [3, 7] desordenado
t4 | **2,4,3,8**,7 | 3 é menor que 8, por isso trocamos 8 e 3;
t5 | **2,3,4,8**,7 | 3 é menor que 4, por isso trocamos 4 e 3;
t6 | **2,3,4,8**,7 | 3 não é menor que 2, por isso não trocamos 2 e 3; **[2,3,4,8]** já está ordenado, e [7] desordenado
t7 | **2,3,4,7,8** | 7 é menor que 8, por isso trocamos 8 e 7;
t7 | **2,3,4,7,8** | 7 é não menor que 4, por isso não trocamos 4 e 7; todo o array está ordenado: **[2,3,4,7,8]**
Agora vamos ver a implementação in-place.
```c
//insertionSort In-Place
void insertionSortIP(int* v, int tamanho) {
void insertionSort(int* v, int tamanho) {
for (int i = 1; i < tamanho; i++) { //i = indice da carta a ser inserida ordenada
for (int j = i; j > 0; j--) {
if (v[j - 1] > v[j]) {
Expand All @@ -134,7 +94,7 @@ Refatorando:

```c
//insertionSort In-Place
void insertionSortIP(int* v, int tamanho) {
void insertionSort(int* v, int tamanho) {
for (int i = 1; i < tamanho; i++) {
for (int j = i; j > 0 && v[j - 1] > v[j]; j--) {
int temp = v[j - 1];
Expand All @@ -145,38 +105,22 @@ void insertionSortIP(int* v, int tamanho) {
}
```
ou

```c
//insertionSort In-Place
void insertionSortIPV4(int* v, int tamanho) {
for (int i = 1; i < tamanho; i++) {
int j = i;
while (j > 0 && v[j - 1] > v[j]) {
int temp = v[j - 1];
v[j - 1] = v[j];
v[j] = temp;
j--;
}
}
}
```
Na versão anterior, nós fazemos algo semelhante a um BubbleSort inverso:
Na versão anterior, nós fazemos algo semelhante a um BubbleSort inverso - escolhemos um elemento e fazemos ele flutuar (para a esquerda, por isso chamei de inverso) para a posição correta:
- **2,4,8**,3,7
- **2,4,3,8**,7
- **2,3,4,8**,7
Uma outra versão que executa menos operações seria a seguinte:
- **2,4,8**,3,7
- guarda o valor 3 em uma variável: valor = v[3];
- 2,4,8,8,7
- 2,4,4,8,7
- 2,3,4,8,7
- guarda o valor 3 em uma variável, e.g., valor = v[3], e move todos os elementos maiores do que valor uma posição para a direita
- 2,4,8,8,7
- 2,4,4,8,7
- no final, atribui valor na última posição maior do que valor
- 2,3,4,8,7
```c
//insertionSort In-Place
void insertionSortIPV5(int* v, int tamanho) {
void insertionSort(int* v, int tamanho) {
for (int i = 1; i < tamanho; i++) {
int valor = v[i];
int j;
Expand All @@ -188,11 +132,38 @@ void insertionSortIPV5(int* v, int tamanho) {
}
```

- No melhor caso, um array ordenado, InsertionSort é O(n). O for interno nunca é executado.
- No pior caso, um array ordenado de forma decrescente, InsertionSort é O(n²).

## Análise Assintótica

```c
void insertionSort(int* v, int tamanho) {
for (int i = 1; i < tamanho; i++) { // i assume os valores {1, 2, ..., n-1}
int valor = v[i]; // essa inicialização/atribuição executa n-1 vezes
int j; // essa alocação executa n-1 vezes
for (j = i; j > 0 && v[j - 1] > valor; j--) {
v[j] = v[j - 1]; // essa atribuição executa n²/2 - n/2 vezes
// quando i=1; j assume os valores {1}; em suma, executa 1 vezes
//lembrando que j chega a assumir o valor 0, porém esse valor não satisfaz a condicional de execução do laço, e portanto o código de dentro desse for não executará quando j=0
// quando i=2; j assume os valores {1, 2}; em suma, executa 2 vezes
// quando i=3; j assume os valores {1, 2, 3}; em suma, executa 3 vezes
//...
// quando i=n-1; j assume os valores {1, 2, 3, ..., n-1}; em suma, executa n-1 vezes
// Fórmula geral da soma dos termos de uma PA: Sn = n(a1+an)/2
// Sn-1 = (n-1) * (1+n-1)/2 = n * (n-1) / 2
}
v[j] = valor; // essa atribuição executa n-1 vezes
}
}
```
No pior caso, o InsertionSort é proporcional ao tamanho do vetor ao quadrado, ou seja, O(n²).
No melhor caso, o InsertionSort é Ômega(n).
Isso acontece quando o vetor está ordenado.
Note que a condicional "v[j - 1] > valor" nunca será satisfeita, e dessa forma o trecho de código que possui complexidade quadrática (for interno) não executaria.
Porém, note que o InsertionSort é ligeiramente mais inteligente do que o BubbleSort.
Ele consegue detectar intervalos do vetor que estão ordenados, e evita esforço adicional nesses intervalos, enquanto o BubbleSort só consegue tirar proveito da identificação de que um vetor está completamente ordenado.
Por essa razão, o InsertionSort é um algoritmo utilizado em conjunto com outros algoritmos, como por exemplo o MergeSort, para melhorar ainda mais o desempenho deles.
## Resumo
In-place, estável, O(n²), Ômega(n).

0 comments on commit e431241

Please sign in to comment.