Menu

Ordenação em C++: std::sort, comparadores personalizados e ordenação estável

Ordene vetores e arrays em C++ com std::sort: ordem padrão, comparadores personalizados, ordenar structs por um campo e a armadilha da ordenação fraca estrita que causa travamentos.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Ordenar é só mais um algoritmo

Na página anterior você viu que a biblioteca padrão traz algoritmos prontos que funcionam sobre qualquer intervalo por meio de iteradores. A ordenação é a que você mais vai usar, e ela ganha sua própria página porque tem algumas arestas afiadas: ordens personalizadas, estabilidade e uma regra que, se você quebrar, entrega comportamento indefinido em vez de uma resposta errada.

O cavalo de batalha é o std::sort de <algorithm>. Você dá a ele o início e o fim de um intervalo, e ele reorganiza os elementos no lugar em ordem crescente:

Nenhuma cópia é feita: o próprio vetor é reordenado. Por baixo dos panos, o std::sort costuma ser um introsort (quicksort que recorre ao heapsort), dando O(n log n) em média. Isso é quase sempre mais rápido e muito menos propenso a erros do que escrever sua própria ordenação.

Também funciona com arrays C puros: você só descreve o intervalo com ponteiros:

Ordem personalizada com um comparador

Por padrão, o std::sort ordena os elementos com operator<. Para ordenar de outra forma, passe um terceiro argumento: um comparador que recebe dois elementos e retorna true se o primeiro deve vir antes do segundo.

Uma lambda é o encaixe natural. A ordem decrescente é só a > b:

Para o caso comum de ordem decrescente pura sobre tipos embutidos, a biblioteca ainda traz um comparador pronto, o greater<T>() de <functional>:

#include <functional>
sort(nums.begin(), nums.end(), greater<int>());  // igual a a > b

O comparador também é a forma de ordenar por algo diferente do próprio valor; por exemplo, ordenar strings por comprimento em vez de alfabeticamente:

Receba os parâmetros do comparador por const& para qualquer coisa maior que alguns bytes (como string): copiar cada elemento a cada comparação é puro desperdício.

Ordenar structs por um campo

Em programas reais você geralmente ordena coleções de structs por um de seus campos. O comparador simplesmente acessa o campo que importa. Aqui ordenamos pessoas por idade, da mais nova primeiro:

Repare que Linus e Dennis têm ambos 25 anos. Aqui eles saíram na ordem relativa original, mas o std::sort não garante isso. Se a ordem relativa de elementos iguais importa, use o std::stable_sort, que a preserva (a um pequeno custo de desempenho):

Para desempatar de forma deliberada (por exemplo, ordenar por idade e depois alfabeticamente por nome), compare a chave secundária só quando as chaves primárias forem iguais. O std::tie deixa isso limpo:

A armadilha da ordenação fraca estrita

Este é o erro mais perigoso na ordenação em C++, porque ele não te dá uma resposta errada: ele dá comportamento indefinido, que muitas vezes significa um travamento ou uma leitura fora dos limites.

O std::sort exige que seu comparador defina uma ordenação fraca estrita. A regra prática: comp(x, x) deve ser false para qualquer elemento x. Em outras palavras, um elemento nunca está "antes" de si mesmo. É exatamente isso que < e > te dão, e exatamente o que <= e >= quebram:

// BUG: retorna true quando a == b, violando a ordenação fraca estrita.
sort(v.begin(), v.end(), [](int a, int b) {
    return a <= b;   // comportamento indefinido: pode travar com algumas entradas
});

Com <=, o comparador afirma que 5 vem antes de outro 5, o que é contraditório. O std::sort pode então percorrer um ponteiro além do fim do intervalo. Entradas pequenas às vezes parecem funcionar, o que torna este bug aterrorizante: ele pode passar nos seus testes e travar em produção. A correção é simplesmente <:

Um segundo problema clássico: ordenar invalida qualquer coisa que aponte para dentro do intervalo. Iteradores, ponteiros e índices que você salvou antes da ordenação já não se referem ao mesmo elemento lógico depois, porque os elementos se moveram. Refaça qualquer posição de que você precise depois de ordenar, nunca antes.

Ordenar parte de um intervalo

Às vezes você não precisa de tudo ordenado: você só quer os primeiros elementos. Ordenar o vetor inteiro para ler os três primeiros é desperdício. O std::partial_sort organiza apenas os elementos que você pedir e deixa o resto em ordem não especificada, o que é mais barato:

E se você só precisa do único elemento que estaria em uma dada posição (como a mediana), o std::nth_element faz ainda menos trabalho: ele coloca o elemento certo nesse índice, com tudo o que é menor antes e tudo o que é maior depois, tudo em O(n) em média.

Recorra a esses quando "totalmente ordenado" for mais do que o problema realmente precisa: eles economizam tempo de verdade em grandes volumes de dados.

Próximo: Templates

Você reparou que o mesmo std::sort lidou com int, string e seu próprio struct Person, e que greater<int>() poderia ser tão facilmente greater<string>()? Essa generalidade não é mágica: são os templates, o mecanismo que permite que um único pedaço de código funcione para qualquer tipo que o chamador conectar. Na próxima página veremos como escrever suas próprias funções e classes com templates, para que seu código possa ser tão agnóstico em relação ao tipo quanto os algoritmos que você vem usando.

Perguntas frequentes

Como ordenar um vetor em C++?

Inclua <algorithm> e chame sort(v.begin(), v.end()). Isso ordena os elementos no lugar em ordem crescente usando operator<. Para ordenar um array, passe arr e arr + n (ou begin(arr) / end(arr)).

Como ordenar em ordem decrescente em C++?

Passe um comparador que retorne a > b: sort(v.begin(), v.end(), [](int a, int b){ return a > b; });. Você também pode usar o sort(v.begin(), v.end(), greater<int>()); embutido, de <functional>.

Por que meu comparador faz o std::sort travar em C++?

Seu comparador precisa ser uma ordenação fraca estrita: ele tem que retornar false quando os dois argumentos são iguais. Usar <= ou >= (que retornam true para elementos iguais) quebra essa regra e é comportamento indefinido: o std::sort pode ler fora dos limites e travar. Compare sempre com < ou >.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR