Quick Sort em Zig — Implementação e Explicação
O Quick Sort (ordenação rápida) é um dos algoritmos de ordenação mais utilizados na prática. Baseado na técnica de divisão e conquista, ele seleciona um elemento como pivô e particiona o array de forma que todos os elementos menores fiquem à esquerda e os maiores à direita.
Como Funciona
- Escolher pivô: selecione um elemento do array (último, primeiro, mediana, ou aleatório)
- Particionar: reorganize o array para que elementos menores que o pivô fiquem à esquerda e maiores à direita
- Recursão: aplique Quick Sort nas duas partições
Visualização do Processo
Array: [10, 80, 30, 90, 40, 50, 70]
pivô = 70
Particionamento (Lomuto):
i aponta para onde colocar o próximo menor que pivô
[10, 80, 30, 90, 40, 50, 70]
10<70 → troca posição 0 com 0: [10, 80, 30, 90, 40, 50, 70]
80>70 → nada
30<70 → troca posição 1 com 2: [10, 30, 80, 90, 40, 50, 70]
90>70 → nada
40<70 → troca posição 2 com 4: [10, 30, 40, 90, 80, 50, 70]
50<70 → troca posição 3 com 5: [10, 30, 40, 50, 80, 90, 70]
Coloca pivô na posição 4: [10, 30, 40, 50, 70, 90, 80]
↑ menores ↑ ↑ maiores ↑
Recursão nas duas partições:
[10, 30, 40, 50] e [90, 80]
... continua até partições de tamanho 1
Implementação em Zig
const std = @import("std");
/// Quick Sort usando particionamento de Lomuto.
pub fn quickSort(comptime T: type, items: []T) void {
if (items.len <= 1) return;
quickSortHelper(T, items, 0, items.len - 1);
}
fn quickSortHelper(comptime T: type, items: []T, low: usize, high: usize) void {
if (low >= high) return;
const pivot_idx = partitionLomuto(T, items, low, high);
// Recursão nas partições
if (pivot_idx > 0) quickSortHelper(T, items, low, pivot_idx - 1);
if (pivot_idx < items.len - 1) quickSortHelper(T, items, pivot_idx + 1, high);
}
/// Particionamento de Lomuto: usa o último elemento como pivô.
fn partitionLomuto(comptime T: type, items: []T, low: usize, high: usize) usize {
const pivot = items[high];
var i: usize = low;
var j: usize = low;
while (j < high) : (j += 1) {
if (items[j] <= pivot) {
const temp = items[i];
items[i] = items[j];
items[j] = temp;
i += 1;
}
}
// Coloca o pivô na posição correta
const temp = items[i];
items[i] = items[high];
items[high] = temp;
return i;
}
/// Quick Sort com particionamento de Hoare (mais eficiente).
pub fn quickSortHoare(comptime T: type, items: []T) void {
if (items.len <= 1) return;
quickSortHoareHelper(T, items, 0, items.len - 1);
}
fn quickSortHoareHelper(comptime T: type, items: []T, low: usize, high: usize) void {
if (low >= high) return;
const p = partitionHoare(T, items, low, high);
if (p > 0) quickSortHoareHelper(T, items, low, p);
quickSortHoareHelper(T, items, p + 1, high);
}
/// Particionamento de Hoare: dois ponteiros convergindo ao centro.
fn partitionHoare(comptime T: type, items: []T, low: usize, high: usize) usize {
const pivot = items[low + (high - low) / 2];
var i: usize = if (low > 0) low - 1 else 0;
var j: usize = high + 1;
if (low == 0) {
i = 0;
// Ajuste especial para quando low é 0
while (true) {
j -= 1;
while (items[j] > pivot) : (j -= 1) {}
while (items[i] < pivot) : (i += 1) {}
if (i >= j) return j;
const temp = items[i];
items[i] = items[j];
items[j] = temp;
i += 1;
}
}
while (true) {
i += 1;
while (items[i] < pivot) : (i += 1) {}
j -= 1;
while (items[j] > pivot) : (j -= 1) {}
if (i >= j) return j;
const temp = items[i];
items[i] = items[j];
items[j] = temp;
}
}
/// Quick Sort com mediana de três como pivô (anti pior caso).
pub fn quickSortMediana3(comptime T: type, items: []T) void {
if (items.len <= 1) return;
qsMediana3(T, items, 0, items.len - 1);
}
fn qsMediana3(comptime T: type, items: []T, low: usize, high: usize) void {
if (high <= low) return;
// Para partições pequenas, usa insertion sort
if (high - low < 10) {
var i: usize = low + 1;
while (i <= high) : (i += 1) {
const chave = items[i];
var j: usize = i;
while (j > low and items[j - 1] > chave) {
items[j] = items[j - 1];
j -= 1;
}
items[j] = chave;
}
return;
}
// Mediana de três: primeiro, meio, último
const meio = low + (high - low) / 2;
if (items[meio] < items[low]) {
const t = items[low];
items[low] = items[meio];
items[meio] = t;
}
if (items[high] < items[low]) {
const t = items[low];
items[low] = items[high];
items[high] = t;
}
if (items[high] < items[meio]) {
const t = items[meio];
items[meio] = items[high];
items[high] = t;
}
// Mediana está em items[meio], move para high-1
const t = items[meio];
items[meio] = items[high - 1];
items[high - 1] = t;
const pivot_idx = partitionLomuto(T, items, low, high - 1);
if (pivot_idx > 0) qsMediana3(T, items, low, pivot_idx - 1);
if (pivot_idx < items.len - 1) qsMediana3(T, items, pivot_idx + 1, high);
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
var numeros = [_]i32{ 10, 80, 30, 90, 40, 50, 70 };
try stdout.print("Original: ", .{});
for (numeros) |n| try stdout.print("{d} ", .{n});
try stdout.print("\n", .{});
quickSort(i32, &numeros);
try stdout.print("Ordenado: ", .{});
for (numeros) |n| try stdout.print("{d} ", .{n});
try stdout.print("\n", .{});
}
Análise de Complexidade
| Caso | Tempo | Espaço |
|---|---|---|
| Melhor caso | O(n log n) — pivô divide ao meio | O(log n) recursão |
| Caso médio | O(n log n) | O(log n) |
| Pior caso | O(n²) — array já ordenado com pivô fixo | O(n) recursão |
Evitando o Pior Caso
O pior caso ocorre quando o pivô é sempre o menor ou maior elemento. Estratégias para evitar:
- Mediana de três: escolhe a mediana entre primeiro, meio e último
- Pivô aleatório: escolhe aleatoriamente
- Insertion sort para partições pequenas: evita overhead de recursão
Características
- Estável: Não — o particionamento pode trocar elementos iguais
- In-place: Sim — O(log n) na pilha de recursão
- Cache-friendly: Sim — acessa memória sequencialmente
- Na prática: Geralmente o mais rápido dos O(n log n)
Quick Sort vs Merge Sort
| Aspecto | Quick Sort | Merge Sort |
|---|---|---|
| Melhor caso | O(n log n) | O(n log n) |
| Pior caso | O(n²) | O(n log n) |
| Espaço | O(log n) | O(n) |
| Estável | Não | Sim |
| Cache | Excelente | Bom |
| Prática | Geralmente mais rápido | Mais previsível |
Recursos Relacionados
- Merge Sort em Zig — Alternativa estável O(n log n)
- Heap Sort em Zig — O(n log n) garantido e in-place
- Insertion Sort em Zig — Usado como otimização para partições pequenas
- Two Pointers — Técnica similar ao particionamento de Hoare
- Array Dinâmico — Estrutura base para ordenação