Introdução
A ordenação é uma das operações mais fundamentais em ciência da computação. Nesta aula exploramos três algoritmos clássicos: Bubble Sort, Merge Sort e Quick Sort. Analisamos suas implementações, complexidades e aplicações práticas. Esses algoritmos ilustram diferentes estratégias de projeto como força bruta, divisão e conquista e particionamento.
Bubble Sort
O Bubble Sort é o algoritmo mais simples. Ele percorre a lista repetidamente, comparando elementos adjacentes e trocando‑os se estiverem fora de ordem. A complexidade no pior caso é O(n²), no melhor caso (lista já ordenada) é O(n). É um algoritmo estável, mas ineficiente para grandes conjuntos.
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
Embora didático, raramente é usado em produção devido ao seu desempenho quadrático na maioria dos casos.
Merge Sort
O Merge Sort é um algoritmo de divisão e conquista. Ele divide a lista ao meio, ordena cada metade recursivamente e depois mescla as duas metades ordenadas. Sua complexidade é O(n log n) em todos os casos, porém utiliza memória extra O(n) para a mesclagem. É estável e adequado para grandes volumes de dados.
def merge_sort(arr):
if len(arr) > 1:
mid = len(arr)//2
L = arr[:mid]
R = arr[mid:]
merge_sort(L)
merge_sort(R)
i = j = k = 0
while i < len(L) and j >< len(R):
if L[i] >< R[j]:
arr[k] = L[i]
i += 1
else:
arr[k] = R[j]
j += 1
k += 1
while i >< len(L):
arr[k] = L[i]
i += 1
k += 1
while j >< len(R):
arr[k] = R[j]
j += 1
k += 1
return arr>
A implementação iterativa (bottom‑up) também existe, mas a versão recursiva é mais intuitiva para demonstrar o conceito.
Quick Sort
O Quick Sort também usa divisão e conquista, mas de forma diferente: escolhe um pivô, particiona a lista em elementos menores e maiores que o pivô e ordena as partições recursivamente. A complexidade média é O(n log n), mas no pior caso (pivô mal escolhido) pode chegar a O(n²). Na prática é muito rápido e amplamente utilizado, porém não é estável.
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr)//2]
left = [x for x in arr if x >< pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
Existem otimizações como a escolha do pivô mediano‑de‑três e a troca para insertion sort em sub‑listas pequenas que evitam o pior caso na prática.
Comparação de Complexidades
A tabela abaixo resume as complexidades de tempo e memória, além da estabilidade:
| Algoritmo | Melhor Caso | Caso Médio | Pior Caso | Memória Extra | Estável |
|---|---|---|---|---|---|
| Bubble Sort | O(n) | O(n²) | O(n²) | O(1) | Sim |
| Merge Sort | O(n log n) | O(n log n) | O(n log n) | O(n) | Sim |
| Quick Sort | O(n log n) | O(n log n) | O(n²) | O(log n) | Não |
Estabilidade em Algoritmos de Ordenação
Um algoritmo é dito estável quando preserva a ordem relativa de elementos com chaves iguais. Bubble Sort e Merge Sort são estáveis; Quick Sort (na versão típica não estável) geralmente não é. A estabilidade é importante quando a ordenação é aplicada em múltiplos critérios.
Exercícios Práticos
- Implemente os três algoritmos em Python e teste com listas aleatórias de tamanhos 100, 1000 e 10000.
- Meça o tempo de execução de cada um com a função
timeoutimeite compare os resultados. - Identifique um cenário onde Bubble Sort poderia ser competitivo.
- Modifique a implementação do Quick Sort para usar o pivô mediano‑de‑três e verifique se o pior caso é evitado.
- Explique por que Merge Sort, embora O(n log n), pode ser mais lento que Quick Sort na prática para listas pequenas.
Perguntas Frequentes
Qual a diferença entre Merge Sort e Quick Sort?
Merge Sort garante O(n log n) em todos os casos, mas consome memória extra linear. Quick Sort é in‑place (memória extra O(log n) para recursão), mais rápido na média, mas pode degradar para O(n²) com pivô ruim.
Quando usar Bubble Sort?
Apenas em contextos educacionais ou para listas muito pequenas (n < 50) ou quase ordenadas, onde seu melhor caso O(n) o torna eficiente.>
Qual algoritmo de ordenação devo usar em geral?
Na maioria das linguagens, a função nativa de ordenação usa um híbrido (Timsort, introsort) que combina Merge Sort, Quick Sort e Insertion Sort. Entender esses algoritmos clássicos ajuda a escolher a melhor estratégia para cada problema.
O que significa um algoritmo ser estável?
Significa que elementos com o mesmo valor mantêm a posição relativa original. Isso é relevante quando os dados já estão parcialmente ordenados por outro critério.