Hoje na aula de ciências da computação revisamos os algoritmos de ordenação mais básicos: Bubble Sort, Insertion Sort e Selection Sort. Embora não sejam os mais eficientes para grandes conjuntos de dados, eles são fundamentais para entender como a ordenação funciona e servem de base para algoritmos mais avançados como Merge Sort e Quick Sort. Vou anotar aqui o resumo de cada um, incluindo a implementação em Python e a complexidade.

Nesta aula, demos os primeiros passos no estudo de algoritmos clássicos de ordenação. Embora existam algoritmos muito mais rápidos, compreender esses três é essencial para desenvolver raciocínio lógico e análise de complexidade. Eles também ilustram conceitos importantes como invariantes de laço, troca de elementos e análise de melhor/pior caso.

O que são algoritmos de ordenação?

Algoritmos de ordenação são procedimentos que organizam os elementos de uma lista ou array em uma determinada ordem (crescente ou decrescente). Eles são essenciais em ciência da computação, pois muitas operações como busca, inserção e remoção se tornam mais eficientes quando os dados estão ordenados.

Bubble Sort

O Bubble Sort é o algoritmo mais simples. Ele percorre a lista repetidamente, comparando elementos adjacentes e trocando-os se estiverem na ordem errada. O processo se repete até que a lista esteja ordenada.

Implementação em Python:

def bubble_sort(lista):
    n = len(lista)
    for i in range(n-1):
        for j in range(n-1-i):
            if lista[j] > lista[j+1]:
                lista[j], lista[j+1] = lista[j+1], lista[j]
    return lista

Complexidade: O(n²) no pior caso e O(n) no melhor caso (quando já ordenada, com otimização).

Estabilidade: Estável.

Uma melhoria comum no Bubble Sort é interromper o algoritmo quando nenhuma troca é realizada durante uma passagem completa. Isso reduz o melhor caso para O(n) quando a lista já está ordenada. A implementação otimizada pode ser feita com uma variável swapped:

def bubble_sort_otimizado(lista):
    n = len(lista)
    for i in range(n-1):
        swapped = False
        for j in range(n-1-i):
            if lista[j] > lista[j+1]:
                lista[j], lista[j+1] = lista[j+1], lista[j]
                swapped = True
        if not swapped:
            break
    return lista

Insertion Sort

O Insertion Sort constrói a lista ordenada um elemento de cada vez, inserindo cada novo elemento na posição correta em relação aos já ordenados. É o mesmo método usado para ordenar cartas de baralho na mão.

Implementação em Python:

def insertion_sort(lista):
    for i in range(1, len(lista)):
        chave = lista[i]
        j = i - 1
        while j >= 0 and lista[j] > chave:
            lista[j+1] = lista[j]
            j -= 1
        lista[j+1] = chave
    return lista

Complexidade: O(n²) no pior caso e O(n) no melhor caso.

Estabilidade: Estável.

O Insertion Sort é eficiente para listas pequenas (com menos de 50 elementos) e também é utilizado como parte do Timsort, o algoritmo híbrido que o Python usa na função sorted() e no método list.sort(). Por ser estável e ter baixa sobrecarga, ele é ideal para ordenar listas quase ordenadas.

Selection Sort

O Selection Sort divide a lista em duas partes: a parte ordenada e a parte não ordenada. A cada iteração, ele seleciona o menor elemento da parte não ordenada e o troca com o primeiro elemento da parte não ordenada.

Implementação em Python:

def selection_sort(lista):
    n = len(lista)
    for i in range(n-1):
        min_idx = i
        for j in range(i+1, n):
            if lista[j] < lista[min_idx]:
                min_idx = j
        lista[i], lista[min_idx] = lista[min_idx], lista[i]
    return lista

Complexidade: O(n²) em todos os casos.

Estabilidade: Não estável.

Uma característica interessante do Selection Sort é que ele realiza no máximo n-1 trocas, independentemente da distribuição dos dados. Isso pode ser vantajoso em situações onde a operação de troca é muito custosa, por exemplo, quando os elementos são grandes estruturas armazenadas em memória secundária.

Comparação de Complexidade

Todos os três algoritmos têm complexidade quadrática no pior caso, mas diferem no comportamento:

  • Bubble Sort: muitas trocas, raramente usado na prática.
  • Insertion Sort: eficiente para listas pequenas ou parcialmente ordenadas.
  • Selection Sort: número de trocas reduzido, útil quando o custo de troca é alto.
AlgoritmoMelhor CasoPior CasoEstável
Bubble SortO(n)O(n²)Sim
Insertion SortO(n)O(n²)Sim
Selection SortO(n²)O(n²)Não

Além da complexidade de tempo, é importante considerar a complexidade de espaço. Todos os três algoritmos ordenam in-place, ou seja, utilizam apenas O(1) de memória adicional (além da entrada). Isso os diferencia de algoritmos como Merge Sort, que requer O(n) de espaço extra.

Quando usar cada um?

Para listas muito pequenas (menos de 50 elementos), Insertion Sort costuma ser o mais rápido. Se o custo de troca for alto, Selection Sort pode ser vantajoso pois faz poucas trocas. Bubble Sort é mais didático que prático. Para listas maiores, prefira algoritmos como Quick Sort, Merge Sort ou Timsort.

Na prática, programadores raramente implementam esses algoritmos manualmente em Python, pois a linguagem já fornece funções de ordenação otimizadas (sorted() e list.sort()). No entanto, conhecê-los é fundamental para entender como os computadores funcionam por baixo dos panos e para resolver problemas em ambientes com restrições severas.

Pontos-chave para lembrar

  • Bubble Sort, Insertion Sort e Selection Sort têm complexidade O(n²) no pior caso.
  • Insertion Sort é o mais rápido entre os três para listas pequenas ou quase ordenadas.
  • Selection Sort minimiza o número de trocas, útil quando trocar é caro.
  • Bubble Sort é mais didático do que prático.
  • Todos são in-place e estáveis (com exceção do Selection Sort, que não é estável).
  • Para dados reais, prefira Timsort (usado pelo Python) ou Quick Sort.

Conclusão

Estudar esses algoritmos simples é importante para construir uma base sólida. Nas próximas aulas pretendo abordar algoritmos mais eficientes que utilizam a abordagem de divisão e conquista.

Perguntas Frequentes (FAQ)

1. Qual a diferença entre Insertion Sort e Selection Sort?

Enquanto o Insertion Sort insere cada elemento na posição correta deslocando os demais, o Selection Sort seleciona o menor elemento e o coloca em sua posição final, sem deslocar os demais.

2. Qual desses algoritmos é o mais rápido?

Para listas muito pequenas, Insertion Sort costuma ser o mais rápido. Para listas médias, todos têm desempenho similar. Para listas grandes, nenhum deles é adequado; prefere-se Quick Sort ou Merge Sort.

3. O que significa estabilidade em algoritmos de ordenação?

Um algoritmo é estável se mantém a ordem relativa de elementos com chaves iguais. Insertion Sort é estável; Selection Sort não é.

4. O que significa in-place e por que isso importa?

Algoritmos in-place ordenam os dados usando apenas uma pequena quantidade de espaço extra, sem depender de arrays auxiliares proporcionais ao tamanho da entrada. Isso economiza memória e pode ser crucial em sistemas embarcados.

5. O Python usa qual algoritmo de ordenação?

O Python utiliza o Timsort, que combina Merge Sort e Insertion Sort. Ele é adaptável e muito eficiente para dados do mundo real, com complexidade O(n log n) no pior caso.

Links relacionados: