From e43124198dae91ba8e407b4e70a800b8d360f8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Falc=C3=A3o?= Date: Fri, 6 Oct 2023 02:54:23 -0300 Subject: [PATCH] =?UTF-8?q?Add=20info=20sobre=20estrat=C3=A9gia,=20c=C3=B3?= =?UTF-8?q?digo=20e=20complexidade=20do=20BubbleSort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [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 --- conteudos/ordenacao/InsertionSort.md | 169 +++++++++++---------------- 1 file changed, 70 insertions(+), 99 deletions(-) diff --git a/conteudos/ordenacao/InsertionSort.md b/conteudos/ordenacao/InsertionSort.md index e41e4d9..d264fbc 100644 --- a/conteudos/ordenacao/InsertionSort.md +++ b/conteudos/ordenacao/InsertionSort.md @@ -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. @@ -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 @@ -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]) { @@ -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]; @@ -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; @@ -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).