Por que o quicksort é melhor do que outros algoritmos de classificação na prática?

31

Esta é uma repostagem de uma pergunta no cs.SE por Janoma . Créditos completos e despojos para ele ou cs.SE.

Em um curso padrão de algoritmos, aprendemos que o quicksort é O (n log n) em média e O (n²) no pior caso. Ao mesmo tempo, outros algoritmos de classificação são estudados, que são O (n log n) no pior caso (como mergesort e heapsort ) e até tempo linear no melhor caso (como bubblesort ), mas com algumas necessidades adicionais de memória.

Após uma rápida olhada em mais alguns tempos de execução , é natural dizer que o quicksort não deve ser tão eficiente quanto os outros.

Além disso, considere que os alunos aprendem nos cursos básicos de programação que a recursão não é realmente boa em geral, porque poderia usar muita memória, etc. Portanto (e mesmo que este não seja um argumento real), isso dá a ideia de que o quicksort pode não ser muito bom porque é um algoritmo recursivo.

Por que, então, o quicksort supera outros algoritmos de classificação na prática? Isso tem a ver com a estrutura dos dados do mundo real ? Isso tem a ver com o modo como a memória funciona nos computadores? Sei que algumas memórias são muito mais rápidas que outras, mas não sei se essa é a verdadeira razão desse desempenho contra-intuitivo (quando comparado às estimativas teóricas).

Rafael
fonte
3
A reputação do Quicksort data de uma época em que o cache não existia.
AProgrammer
9
"por que o quicksort supera outros algoritmos de classificação na prática?" Claro que é verdade? Mostre-nos a implementação real a que você está se referindo com esta declaração, e a comunidade lhe dirá por que essa implementação específica se comporta da maneira que funciona. Tudo o resto levará a um palpite sobre programas inexistentes.
Doc Brown
1
@DocBrown: Muitas implementações do Quicksort (ou variantes) são escolhidas em muitas bibliotecas, sem dúvida porque elas têm o melhor desempenho (eu espero que sim). Portanto, pode haver algo sobre o algoritmo que torna o Quicksort rápido, independentemente da implementação .
Raphael
1
Alguém tem que dizer isso por completo, então vou: O Quicksort não é (geralmente) estável. Por esse motivo, você pode não querer usá-lo. Além disso, por esse motivo, sua classificação padrão pode não ser um Quicksort, mesmo quando é isso que você deseja.
RalphChapin
1
@ Rafael: Muitas vezes, o que é chamado de classificação rápida é na verdade uma variação como a introdução (usada, afaik, na biblioteca padrão C ++), e não a classificação rápida pura.
Giorgio

Respostas:

21

Não concordo que o quicksort seja melhor do que outros algoritmos de classificação na prática.

Para a maioria das finalidades, Timsort - o híbrido entre a classificação de fusão / inserção que explora o fato de que os dados que você classifica geralmente começam quase classificados ou classificados inversamente.

O quicksort mais simples (sem pivô aleatório) trata esse caso potencialmente comum como O (N ^ 2) (reduzindo para O (N lg N) com pivôs aleatórios), enquanto o TimSort pode lidar com esses casos em O (N).

De acordo com esses benchmarks em C # que comparam o quicksort interno ao TimSort, o Timsort é significativamente mais rápido nos casos mais classificados e um pouco mais rápido no caso de dados aleatórios e o TimSort melhora se a função de comparação for particularmente lenta. Não repeti esses benchmarks e não ficaria surpreso se o quicksort superasse levemente o TimSort por alguma combinação de dados aleatórios ou se houvesse algo peculiar no tipo interno do C # (com base no quicksort) que o estivesse atrasando. No entanto, o TimSort possui vantagens distintas quando os dados podem ser classificados parcialmente e é aproximadamente igual ao quicksort em termos de velocidade quando os dados não são classificados parcialmente.

O TimSort também possui um bônus adicional de ser um tipo estável, ao contrário do quicksort. A única desvantagem do TimSort usa memória O (N) versus O (lg N) na implementação usual (rápida).

dr jimbob
fonte
18

A ordenação rápida é considerada mais rápida porque o coeficiente é menor que qualquer outro algoritmo conhecido. Não há razão ou prova disso, apenas nenhum algoritmo com um coeficiente menor foi encontrado. É verdade que outros algoritmos também têm tempo O ( n log n ), mas no mundo real o coeficiente também é importante.

Observe que, para classificação de inserção de dados pequena (a que é considerada O ( n 2 )), é mais rápida devido à natureza das funções matemáticas. Isso depende dos coeficientes específicos que variam de máquina para máquina. (No final, apenas a montagem está realmente em execução.) Portanto, às vezes, um híbrido de ordenação rápida e ordenação por inserção é o mais rápido na prática.

Ramzi Kahil
fonte
7
+ Certo. Os professores precisam estar mais conscientes (e eu fui professor) do fato de que fatores constantes podem variar por ordens de magnitude. Portanto, a habilidade de ajustar o desempenho realmente importa, independentemente do grande O. O problema é que eles continuam ensinando o gprof , apenas porque precisam superar esse ponto do currículo, que é 180 graus a abordagem errada.
Mike Dunlavey
2
“Não há razão ou lucro para isso”: com certeza existe. Se você cavar fundo o suficiente, encontrará um motivo.
Gilles 'SO- stop be evil'
2
@B Seven: para simplificar muito ... para um algoritmo de classificação O (n log n), existem (n log n) iterações do loop de classificação para classificar n itens. O coeficiente é quanto tempo leva cada ciclo do loop. Quando n é realmente grande (pelo menos milhares), o coeficiente não importa tanto quanto O (), mesmo que o coeficiente seja enorme. Mas quando n é pequeno, o coeficiente é importante - e pode ser a coisa mais importante se você estiver classificando apenas 10 itens.
Matt Gallagher
4
@MikeDunlavey - um bom exemplo é que a construção das pirâmides é O (n) e a classificação das suas fotos é O (n ln n), mas o que é mais rápido!
Martin Beckett
2
Existem algoritmos O (n log n) garantidos, como heapsort e mergesort, portanto, nos piores casos assintóticos, o Quicksort não é tão rápido quanto o melhor. Mas no desempenho do mundo real, algumas variantes de quicksort se saem extremamente bem. Porém, dizer "o coeficiente é menor" é como dizer "é mais rápido porque é mais rápido". Por que os fatores constantes são tão pequenos? Um dos principais motivos é que o quicksort é muito bom em termos de localidade - faz um ótimo uso de caches. O Mergesort também tem boa localidade, mas é muito difícil de executar no local.
30512 Steve314
16

O Quicksort não supera todos os outros algoritmos de classificação. Por exemplo, a classificação de pilha ascendente ( Wegener 2002 ) supera a classificação rápida para quantidades razoáveis ​​de dados e também é um algoritmo no local. Também é fácil de implementar (pelo menos, não mais difícil do que alguma variante de quicksort otimizada).

Não é tão conhecido e você não o encontra em muitos livros, o que pode explicar por que não é tão popular quanto o quicksort.

Doc Brown
fonte
+1: executei alguns testes e, de fato, a classificação por mesclagem foi definitivamente melhor que a classificação rápida para matrizes grandes (> 100000 elementos). A classificação de heap foi um pouco pior que a classificação de mesclagem (mas a classificação de mesclagem precisa de mais memória). Eu acho que o que as pessoas chamam de classificação rápida geralmente é uma variação chamada de introdução: classificação rápida que retorna ao tipo de pilha quando a profundidade da recursão ultrapassa um certo limite.
Giorgio
@Giorgio: quicksort pode ser modificado de algumas maneiras para melhorá-lo, veja, por exemplo, aqui: algs4.cs.princeton.edu/23quicksort Você tentou essas melhorias?
Doc Brown
Interessante, você pode fazer uma referência a um livro \ site para ler mais sobre isso? (de preferência um livro)
Ramzi Kahil
@ Martin: você quer dizer sobre Bottom-Up heapsort? Bem, eu dei uma referência acima. Se você deseja um recurso gratuito, a wikipedia alemã possui um artigo ( de.wikipedia.org/wiki/BottomUp-Heapsort ). Mesmo se você não fala alemão, acho que você ainda pode ler o exemplo C99.
Doc Brown
7

Você não deve se concentrar apenas no pior dos casos e apenas na complexidade do tempo. É mais sobre média do que pior, e é sobre tempo e espaço.

Ordenação rápida:

  • tem uma complexidade de tempo médio de Θ ( n log n );
  • pode ser implementado com complexidade de espaço de Θ (log n );

Também leve em consideração que a grande notação O não leva em consideração nenhuma constante, mas na prática faz diferença se o algoritmo for algumas vezes mais rápido. Θ ( n log n ) significa que o algoritmo é executado em K  n  log ( n ), onde K é constante. Quicksort é o algoritmo de comparação de-espécie com o menor K .

vartec
fonte
1
@ Gilles: tem baixo K, porque é um algoritmo simples.
vartec 30/05
5
WTF? Isso não faz sentido. A simplicidade de um algoritmo não tem relação com sua velocidade de execução. A classificação por seleção é mais simples que a classificação rápida, o que não a torna mais rápida.
Gilles 'SO- stop be evil'
1
@ Gilles: o tipo de seleção é O (n ^ 2) para qualquer caso (pior, média e melhor). Portanto, não importa o quão simples seja. Quicksort é O (n log n) para casos médios e, entre todos os algos com O (n log n) avg, é o mais simples.
Vartec
1
@ Gilles: outras coisas são iguais, a simplicidade ajuda no desempenho. Digamos que você esteja comparando dois algoritmos em que cada iteração (K n log n) de seus respectivos loops internos: o algoritmo que precisa fazer menos coisas por loop tem uma vantagem de desempenho.
comingstorm
1
@comingstorm: Expressou assim que sua afirmação é uma tautologia, mas não se refere à "simplicidade". Por exemplo, existem variantes mais complicadas do Quicksort (distinções de maiúsculas e minúsculas!) Que resultam em menor tempo de execução (tanto na teoria quanto na prática).
Raphael
5

O Quicksort geralmente é uma boa escolha, pois é razoavelmente rápido, razoavelmente rápido e fácil de implementar.

Se você é sério sobre a classificação de grandes quantidades de dados muito rapidamente, provavelmente é melhor usar algumas variações no MergeSort. Isso pode ser feito para tirar proveito do armazenamento externo, pode fazer uso de vários threads ou até processos, mas eles não são triviais para o código.

James Anderson
fonte
1

O desempenho real dos algoritmos depende da plataforma, bem como da linguagem, do compilador, da atenção do programador aos detalhes da implementação, do esforço específico de otimização etc. Portanto, a "vantagem constante do fator" do quicksort não é muito bem definida - é um julgamento subjetivo baseado nas ferramentas atualmente disponíveis e uma estimativa aproximada do "esforço equivalente de implementação" por quem realmente faz o estudo de desempenho comparativo. .

Dito isso, acredito que o quicksort tem um bom desempenho (para entrada aleatória) porque é simples e porque sua estrutura recursiva é relativamente compatível com o cache. Por outro lado, como seu pior caso é fácil de acionar, qualquer uso prático de uma classificação rápida precisará ser mais complexo do que a descrição de seu livro indicaria: assim, versões modificadas, como a introsort.

Com o tempo, à medida que a plataforma dominante muda, diferentes algoritmos podem ganhar ou perder sua vantagem relativa (mal definida). A sabedoria convencional sobre o desempenho relativo pode ficar para trás dessa mudança; portanto, se você não tiver certeza de qual algoritmo é melhor para o seu aplicativo, implemente os dois e teste-os.

tempestade
fonte
Acho que a "constante menor" a que os outros se referem é a análise formal, ou seja, o número de comparações ou trocas. Isso está muito bem definido, mas não está claro como isso se traduz em tempo de execução. Atualmente, um colega faz alguma pesquisa sobre isso.
Raphael
Minha impressão era de que se tratava de desempenho generalizado, mas eu também não contava com isso. Mas você está certo: se a sua comparação for particularmente cara, você pode procurar o número de comparações esperadas ...
comingstorm
1
Pelo motivo que você declara, falar sobre desempenho geral (em termos de tempo) não é significativo no caso geral, pois muitos detalhes são considerados. O motivo para contar apenas operações selecionadas não é que elas são caras, mas que ocorrem "com mais freqüência". "no sentido de notação Landau (Big-Oh), então contá-los fornece seus assintóticos brutos. Assim que você considera constantes e / ou tempo de execução, essa estratégia é muito menos interessante.
Raphael
Uma boa implementação do QuickSort será compilada de modo que seus valores de pivô permaneçam em um registro da CPU pelo tempo que for necessário. Isso geralmente é suficiente para superar uma classificação teoricamente mais rápida com tempos comparáveis ​​de Big-O.
Dan Lyons
Algoritmos de classificação diferentes têm características diferentes em relação ao número de comparações e ao número de trocas que eles fazem. E o @DanLyons observa que uma classificação típica em uma biblioteca realiza suas comparações por meio de funções fornecidas pelo usuário, e manter os valores nos registros em várias chamadas de função é bastante complicado.
Pointy