Pular para conteúdo

Aula 19 - Algoritmos de Ordenação Eficientes (QuickSort e MergeSort) 📊

Objetivo Pedagógico

Objetivo: Arquitetura e implementação dos algoritmos ótimos de ordenação baseados em Divisão e Conquista: MergeSort (estável, O(n log n)) e QuickSort (in-place com partição Lomuto/Hoare).


📑 1. Fundamentos Teóricos & Análise Técnica

A ordenação de dados é uma das operações mais executadas na história da computação. O teorema matemático do limite inferior da ordenação por comparação prova que nenhum algoritmo baseado em comparações pode ordenar \(n\) elementos no pior caso em tempo inferior a \(\Omega(n \log n)\).

Os dois expoentes industriais da estratégia de Divisão e Conquista: 1. MergeSort (John von Neumann, 1945): - Divisão: Divide recursivamente o vetor ao meio até subvetores unitários. - Conquista: Intercala (Merge) os subvetores ordenados em tempo \(O(n)\). - Propriedades: Complexidade temporal garantida de \(O(n \log n)\) em todos os cenários (melhor, médio e pior) e Estabilidade (preserva a ordem relativa de chaves iguais). Custo: exige \(O(n)\) de memória auxiliar. 2. QuickSort (Tony Hoare, 1959): - Particionamento: Escolhe um pivô e rearranja os elementos de modo que menores fiquem à esquerda e maiores à direita. - Propriedades: Opera in-place (\(O(\log n)\) de pilha), com performance prática imbatível no caso médio (\(O(n \log n)\)) devido à excelente localidade de cache. Pior caso: \(O(n^2)\) se o pivô for mal escolhido (mitigado com a técnica da Mediana de Três).

📐 Arquitetura Conceitual & Diagrama de Fluxo

graph TD
    subgraph MergeSortDiv ["Divisão e Conquista no MergeSort"]
        V["[38, 27, 43, 3, 9, 82, 10]"] --> L["[38, 27, 43]"]
        V --> R["[3, 9, 82, 10]"]
        L --> L1["[27, 38, 43] (Ordenado)"]
        R --> R1["[3, 9, 10, 82] (Ordenado)"]
        L1 & R1 --> Final["[3, 9, 10, 27, 38, 43, 82] (Merge Final O(n log n))"]
    end
    style V fill:#e3f2fd,stroke:#1565c0
    style L1 fill:#fff3e0,stroke:#e65100
    style R1 fill:#fff3e0,stroke:#e65100
    style Final fill:#e8f5e9,stroke:#2e7d32

🔍 Pilares e Diretrizes Técnicas

Nesta unidade, aprofundamos os seguintes conceitos fundamentais: - Estabilidade na Ordenação: Garantia de que a ordenação de objetos secundários não desfaça ordenações prévias. - Particionamento In-Place: Rearranjo de elementos dentro da própria memória do array sem alocar novos vetores. - Pivô Mediana de Três: Seleção do pivô a partir da mediana entre o primeiro, meio e último elemento para evitar o pior caso \(O(n^2)\). - Algoritmos Híbridos Modernos: Bibliotecas padrão utilizam variações como Timsort (MergeSort + InsertionSort) ou Introsort (QuickSort + HeapSort).


🛠️ 2. Implementação Prática em Algoritmos de Divisão e Conquista

Abaixo está a implementação técnica de referência, estruturada com padrões de engenharia de software e foco em robustez:

// quicksort_hoare.c (QuickSort com Particionamento de Hoare em C)
#include <stdio.h>

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// Particionamento eficiente de Hoare
int partition_hoare(int arr[], int low, int high) {
    int pivot = arr[low + (high - low) / 2];
    int i = low - 1;
    int j = high + 1;

    while (1) {
        do { i++; } while (arr[i] < pivot);
        do { j--; } while (arr[j] > pivot);
        if (i >= j) return j;
        swap(&arr[i], &arr[j]);
    }
}

void quicksort(int arr[], int low, int high) {
    if (low < high) {
        int p = partition_hoare(arr, low, high);
        quicksort(arr, low, p);
        quicksort(arr, p + 1, high);
    }
}

💡 Análise Passo a Passo do Código

  1. Partição de Hoare: Utiliza dois ponteiros convergentes (i e j), realizando em média 3x menos trocas (swaps) que o esquema de Lomuto.
  2. Recursão sobre Subvetores: Divide o problema exatamente nos limites do ponto de partição retornado.
  3. Ordenação In-Place: Zero consumo de memória na heap, operando diretamente sobre o vetor original.

🎯 3. Próximos Passos & Sequência Didática