Algoritmos de Ordenação Elementares


No capítulo anterior, vimos que a Busca Binária é super eficiente, mas exige um vetor ordenado. Mas como ordenar um vetor? Imagine que você recebe um baralho de cartas totalmente embaralhado. Você provavelmente usaria um "algoritmo" mental para colocá-lo em ordem sem nem perceber. Esta aula apresenta três métodos fundamentais que um computador usa para fazer exatamente isso: Bubble Sort, Selection Sort e Insertion Sort. Todos eles possuem uma complexidade de tempo semelhante, O(n2), mas usam estratégias completamente diferentes para resolver o mesmo problema.


1. Ordenação Bolha (Bubble Sort)

Como Funciona?

O Bubble Sort é o método mais famoso por sua simplicidade. Ele funciona como um organizador de fila muito metódico: ele passa pela fila comparando as pessoas duas a duas (vizinhas) e, se a da frente for maior que a de trás, ele as troca de lugar. Ele faz isso do início ao fim da fila. Ao final da primeira "passada", o maior elemento de todos ("a pessoa mais alta") naturalmente "borbulha" para a última posição. O processo se repete, a cada vez ignorando os elementos que já estão no lugar certo, até que em uma passada inteira, nenhuma troca seja feita.

Pseudocódigo e a Estratégia

O pseudocódigo abaixo mostra a lógica fundamental. O laço de fora (`para i`) representa as múltiplas 'passadas' pela lista, garantindo que o processo seja repetido o suficiente para ordenar todos os elementos. O laço de dentro (`para j`) é onde a ação acontece: ele faz as comparações entre os elementos vizinhos (`vetor[j]` e `vetor[j+1]`) e realiza a troca se o elemento da esquerda for maior que o da direita.

para i = 1 até n faça
    para j = 0 até n - 2 faça
        se vetor[j] > vetor[j + 1] então
            trocar(vetor[j], vetor[j + 1])
        fim-se
    fim-para
fim-para

Implementação em C# Comentada

A implementação em C# a seguir é uma versão otimizada da lógica do pseudocódigo. Ela é mais inteligente porque inclui formas de parar o processo mais cedo se o vetor já estiver ordenado, economizando tempo. Vamos primeiro declarar as variáveis que controlam essas otimizações e o vetor desordenado.

int[] vetor = { 99, 82, 50, 67, 90, 20, 71, 8, 21, 18 };
bool mudou = true;
int ultimo = vetor.Length - 1;
int ultimo_temp = vetor.Length - 1;

while (mudou)
{
    int pos = 0;
    mudou = false; // Assume que está ordenado até que uma troca prove o contrário
    int temp = 0;

    while (pos < ultimo)
    {
        if (vetor[pos] > vetor[pos + 1])
        {
            // Lógica de troca de posição
            temp = vetor[pos];
            vetor[pos] = vetor[pos + 1];
            vetor[pos + 1] = temp;
            
            mudou = true; // Uma troca ocorreu, então o vetor ainda não está ordenado
            ultimo_temp = pos;
        }
        pos++;
    }
    ultimo = ultimo_temp; // Reduz o limite da próxima passada
}

Analisando o Código C# Linha a Linha


2. Ordenação por Seleção (Selection Sort)

Como Funciona?

O Selection Sort age como um selecionador de time. Para preencher a primeira posição, ele olha para toda a lista, encontra o menor de todos e o move para o início, trocando de lugar com quem estava lá. Depois, para preencher a segunda posição, ele olha para todos os candidatos restantes (do segundo em diante), pega o menor deles e o coloca no segundo lugar. Ele repete esse processo, a cada passo "selecionando" o menor elemento disponível e colocando-o em sua posição final.

Pseudocódigo e a Estratégia

A lógica é representada por dois laços aninhados. O laço externo (`para i`) avança a fronteira da parte ordenada do vetor (uma posição por vez). O laço interno (`para j`) é o "caçador": ele percorre todo o resto do vetor para encontrar o índice (`minimo`) do menor valor. Ao final de cada passada do laço externo, a troca é realizada, colocando o menor valor encontrado na posição correta.

para i = 0 até n - 1 faça
    minimo := i
    para j = i + 1 até n faça
        se vetor[j] < vetor[minimo] então
            minimo := j
        fim-se
    fim-para
    trocar(vetor[i], vetor[minimo])
fim-para

Implementação em C# Comentada

O código em C# segue diretamente a lógica do pseudocódigo. Usamos um laço `for` para controlar a posição que estamos querendo preencher (`i`) e um laço `for` aninhado para encontrar o menor valor no restante do vetor.

int[] vetor = { 99, 82, 50, 67, 90, 20, 71, 8, 21, 18 };
int min, temp;

for (int i = 0; i < vetor.Length - 1; i++)
{
    min = i; // Assume que o menor é o primeiro elemento da parte não ordenada

    // Encontra o índice do menor elemento no resto do vetor
    for (int pos = i + 1; pos < vetor.Length; pos++)
    {
        if (vetor[pos] < vetor[min])
        {
            min = pos;
        }
    }

    // Se um menor foi encontrado, troca com a posição atual
    if (vetor[i] != vetor[min])
    {
        temp = vetor[i];
        vetor[i] = vetor[min];
        vetor[min] = temp;
    }
}

Analisando o Código C# Linha a Linha


3. Ordenação por Inserção (Insertion Sort)

Como Funciona?

Este método é o mais parecido com a forma que organizamos cartas de baralho na mão. Ele divide o vetor em duas partes: uma ordenada (no início) e uma desordenada. Ele então pega o primeiro elemento da parte desordenada e o "insere" no lugar correto dentro da parte ordenada. Para fazer isso, ele compara esse elemento com os valores anteriores (da direita para a esquerda), empurrando os maiores para a direita para "abrir espaço", até encontrar o local certo para encaixá-lo.

Pseudocódigo e a Estratégia

O laço principal (`para i`) seleciona o próximo elemento a ser inserido. Guardamos o valor desse elemento para não o perdermos. O laço `enquanto` é a parte crucial: ele compara o valor guardado com os elementos da parte já ordenada e "empurra" os maiores para a direita (`vetor[j + 1] := vetor[j]`) para criar um espaço. Quando o `enquanto` termina, encontramos a posição correta e inserimos o valor guardado nesse espaço.

para i = 1 até n - 1 faça
    valor := vetor[i]
    j := i - 1
    enquanto j >= 0 e valor < vetor[j] faça
        vetor[j + 1] := vetor[j]
        j := j - 1
    fim-enquanto
    vetor[j + 1] := valor
fim-para

Implementação em C# Comentada

A implementação em C# traduz a lógica de "pegar uma carta" e "abrir espaço" de forma bem direta. O laço `for` seleciona a "carta" e o laço `while` aninhado faz o trabalho de deslocar os elementos para encontrar o ponto de inserção.

int[] vetor = { 99, 82, 50, 67, 90, 20, 71, 8, 21, 18 };
int num, pos_verificada;

for (int pos = 1; pos < vetor.Length; pos++)
{
    num = vetor[pos]; // "Carta" que vamos inserir na parte ordenada
    pos_verificada = pos - 1;

    // Enquanto houver elementos para comparar e a "carta" for menor
    while (pos_verificada >= 0 && num < vetor[pos_verificada])
    {
        // Empurra o elemento maior para a direita para abrir espaço
        vetor[pos_verificada + 1] = vetor[pos_verificada];
        pos_verificada--;
    }
    // Insere a "carta" no espaço que foi aberto
    vetor[pos_verificada + 1] = num;
}

Analisando o Código C# Linha a Linha

Veja o seguinte conteúdo para melhor entendimento sobre ordenação por seleção: link

Veja o seguinte conteúdo para melhor entendimento sobre ordenação por inserção: link


Considerações Finais

Bubble, Selection e Insertion Sort são os "blocos de montar" dos algoritmos de ordenação. Embora todos tenham uma performance considerada lenta para grandes volumes de dados ($O(n^2)$), entender suas diferentes estratégias (comparar vizinhos, caçar o menor, organizar como cartas) é fundamental para evoluir como programador. Eles nos ensinam que um mesmo problema pode ser resolvido de várias maneiras. No próximo capítulo, conheceremos a recursão, uma técnica que abre portas para algoritmos de ordenação muito mais eficientes.