Em um curso padrão de algoritmos, aprendemos que o quicksort é em média e no pior caso. Ao mesmo tempo, outros algoritmos de classificação são estudados que são no pior dos casos (como mergesort e heapsort ) e até tempo linear no melhor dos casos (como bubblesort ), mas com algumas necessidades adicionais de memória.O ( n 2 ) O ( n log n )
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 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).
Atualização 1: uma resposta canônica está dizendo que as constantes envolvidas no do caso médio são menores do que as constantes envolvidas em outros algoritmos . No entanto, ainda não vi uma justificativa adequada disso, com cálculos precisos, em vez de apenas idéias intuitivas.O ( n log n )
De qualquer forma, parece que a diferença real ocorre, como algumas respostas sugerem, no nível da memória, em que as implementações aproveitam a estrutura interna dos computadores, usando, por exemplo, que a memória cache é mais rápida que a RAM. A discussão já é interessante, mas eu ainda gostaria de ver mais detalhes com relação ao gerenciamento de memória, pois parece que a resposta tem a ver com isso.
Atualização 2: Existem várias páginas da Web que oferecem uma comparação de algoritmos de classificação, algumas mais sofisticadas que outras (principalmente a classificação- algorithms.com ). Além de apresentar uma boa ajuda visual, essa abordagem não responde à minha pergunta.
fonte
Respostas:
Resposta curta
O argumento de eficiência do cache já foi explicado em detalhes. Além disso, há um argumento intrínseco, por que o Quicksort é rápido. Se implementado como com dois "ponteiros cruzados", por exemplo , aqui , os loops internos têm um corpo muito pequeno. Como esse é o código executado com mais frequência, isso compensa.
Resposta longa
Em primeiro lugar,
O caso médio não existe!
Como o melhor e o pior caso costumam ocorrer extremos, raramente ocorrem na prática, é feita uma análise de caso médio. Mas qualquer análise de caso médio pressupõe alguma distribuição de entradas ! Para a classificação, a escolha típica é o modelo de permutação aleatória (tacitamente assumido na Wikipedia).
Por que Notação?O
O descarte de constantes na análise de algoritmos é feito por um motivo principal: se estou interessado em tempos de execução exatos , preciso de custos (relativos) de todas as operações básicas envolvidas (mesmo ignorando os problemas de cache, canalizando nos processadores modernos ...). A análise matemática pode contar com que frequência cada instrução é executada, mas o tempo de execução de instruções únicas depende dos detalhes do processador, por exemplo, se uma multiplicação de números inteiros de 32 bits leva tanto tempo quanto a adição.
Existem duas maneiras de sair:
Corrija algum modelo de máquina.
Isso é feito na série de livros de Don Knuth , “The Art of Computer Programming”, para um computador “típico” artificial inventado pelo autor. No volume 3, você encontra resultados médios exatos de casos para muitos algoritmos de classificação, por exemplo
Esses resultados indicam que o Quicksort é o mais rápido. Mas, isso só é provado na máquina artificial de Knuth, não implica necessariamente nada para dizer o seu PC x86. Observe também que os algoritmos se relacionam de maneira diferente para pequenas entradas:
[ fonte ]
Analisar operações básicas abstratas .
Para classificação baseada em comparação, normalmente são trocas e comparações de chaves . Nos livros de Robert Sedgewick, por exemplo, "Algoritmos" , essa abordagem é seguida. Você encontra lá
Como você vê, isso não permite comparações de algoritmos como a análise exata do tempo de execução, mas os resultados são independentes dos detalhes da máquina.
Outras distribuições de entrada
Como observado acima, os casos médios são sempre com relação a alguma distribuição de entrada, portanto, pode-se considerar outros que não sejam permutações aleatórias. Por exemplo, foram feitas pesquisas para o Quicksort com elementos iguais e há um bom artigo sobre a função de classificação padrão em Java
fonte
Existem vários pontos que podem ser feitos em relação a essa questão.
Quicksort geralmente é rápido
O Quicksort geralmente é mais rápido do que a maioria dos tipos
A razão para essa eficiência de cache é que ela varre linearmente a entrada e particiona linearmente a entrada. Isso significa que podemos aproveitar ao máximo cada carregamento de cache que fazemos enquanto lemos todos os números que carregamos no cache antes de trocá-lo por outro. Em particular, o algoritmo é inconsciente do cache, o que fornece um bom desempenho para cada nível de cache, o que é outra vitória.
O Quicksort geralmente é mais rápido que o Mergesort
Essa comparação é completamente sobre fatores constantes (se considerarmos o caso típico). Em particular, a escolha é entre uma escolha subótima do pivô para o Quicksort versus a cópia de toda a entrada do Mergesort (ou a complexidade do algoritmo necessário para evitar essa cópia). Acontece que o primeiro é mais eficiente: não há teoria por trás disso, apenas é mais rápido.
Por fim, observe que o Quicksort é um pouco sensível às informações que estão na ordem correta; nesse caso, pode pular alguns swaps. O Mergesort não possui essas otimizações, o que também torna o Quicksort um pouco mais rápido em comparação ao Mergesort.
Use o tipo que atenda às suas necessidades
Em conclusão: nenhum algoritmo de classificação é sempre ideal. Escolha o que melhor se adapte às suas necessidades. Se você precisar de um algoritmo que seja o mais rápido para a maioria dos casos, e não se importar que possa acabar sendo um pouco lento em casos raros, e não precise de uma classificação estável, use o Quicksort. Caso contrário, use o algoritmo que melhor se adapte às suas necessidades.
fonte
Em um dos tutoriais de programação da minha universidade, pedimos aos alunos que comparassem o desempenho do quicksort, mergesort, tipo de inserção versus o list.sort interno do Python (chamado Timsort ). Os resultados experimentais me surpreenderam profundamente, uma vez que a list.sort embutida teve um desempenho muito melhor do que outros algoritmos de classificação, mesmo em instâncias que facilmente executavam quicksort, combinando uma falha. Portanto, é prematuro concluir que a implementação usual do quicksort é a melhor prática. Mas tenho certeza de que há uma implementação muito melhor do quicksort, ou alguma versão híbrida dele por aí.
Este é um bom artigo de blog de David R. MacIver que explica o Timsort como uma forma de fusão adaptativa.
fonte
list.sort
beneficia-se de ser uma função interna otimizada por profissionais. Uma comparação mais justa teria todas as funções escritas no mesmo idioma e no mesmo nível de esforço.Eu acho que uma das principais razões pelas quais o QuickSort é tão rápido em comparação com outros algoritmos de classificação é porque é compatível com o cache. Quando o QS processa um segmento de uma matriz, ele acessa elementos no início e no final do segmento e se move em direção ao centro do segmento.
Portanto, quando você inicia, acessa o primeiro elemento da matriz e uma parte da memória (“local”) é carregada no cache. E quando você tenta acessar o segundo elemento, ele (provavelmente) já está no cache, por isso é muito rápido.
Outros algoritmos como o heapsort não funcionam assim, eles pulam muito no array, o que os torna mais lentos.
fonte
Outros já disseram que o tempo médio de execução assintótico do Quicksort é melhor (na constante) do que em outros algoritmos de classificação (em determinadas configurações).
Observe que existem muitas variantes do Quicksort (veja, por exemplo, a dissertação de Sedgewick). Eles têm desempenho diferente em diferentes distribuições de entrada (uniforme, quase classificado, quase inversamente classificado, muitas duplicatas, ...) e outros algoritmos podem ser melhores para alguns.
fonte
ps: para ser preciso, ser melhor que outros algoritmos depende da tarefa. Para algumas tarefas, pode ser melhor usar outros algoritmos de classificação.
Veja também:
Comparação da classificação rápida com outros algoritmos de classificação
Comparação de heap-sort com outros algoritmos de classificação
fonte
O segundo motivo é que ele executa a
in-place
classificação e funciona muito bem com ambientes de memória virtual.ATUALIZAÇÃO:: (Após os comentários de Janoma e Svick)
Para ilustrar isso melhor, deixe-me dar um exemplo usando a Classificação por Mesclagem (porque a classificação por Mesclagem é o próximo algoritmo de classificação amplamente adotado após a classificação rápida, eu acho) e dizer de onde vêm as constantes extras (até onde eu sei e por que eu penso) A classificação rápida é melhor):
Considere a seguinte seqência:
Se você observar atentamente como está ocorrendo o último estágio, os 12 primeiros serão comparados com os 8 e 8 serão menores, portanto serão os primeiros. Agora, 12 é NOVAMENTE em comparação com 21 e 12 passa a seguir e assim por diante. Se você fizer a mesclagem final, ou seja, 4 elementos com 4 outros elementos, ocorrerá muitas comparações EXTRA como constantes que NÃO são incorridas na Classificação Rápida. Essa é a razão pela qual a classificação rápida é preferida.
fonte
in-place
, ou seja, não é necessária memória extra.Minha experiência trabalhando com dados do mundo real é que o quicksort é uma má escolha . O Quicksort funciona bem com dados aleatórios, mas os dados do mundo real geralmente não são aleatórios.
Em 2008, rastreei um bug de software suspenso até o uso do quicksort. Algum tempo depois, escrevi implicações simples de classificação por inserção, classificação rápida, classificação por heap e classificação por mesclagem e testei-as. Minha classificação de mesclagem superou todas as outras enquanto trabalhava em grandes conjuntos de dados.
Desde então, a classificação por mesclagem é o meu algoritmo de escolha. É elegante. É simples de implementar. É um tipo estável. Não degenera para comportamento quadrático como o quicksort. Eu mudo para a inserção de classificação para classificar pequenas matrizes.
Em muitas ocasiões, achei que uma determinada implementação funciona surpreendentemente bem para o quicksort apenas para descobrir que na verdade não é o quicksort. Às vezes, a implementação alterna entre o quicksort e outro algoritmo e, às vezes, não usa o quicksort. Como um exemplo, as funções qsort () do GLibc realmente usam a classificação de mesclagem. Somente se a alocação do espaço de trabalho falhar, ele voltará ao quicksort no local, que um comentário de código chama de "o algoritmo mais lento" .
Editar: linguagens de programação como Java, Python e Perl também usam classificação de mesclagem ou, mais precisamente, uma derivada, como Timsort ou classificação de mesclagem para conjuntos grandes e classificação de inserção para conjuntos pequenos. (O Java também usa o quicksort de pivô duplo, mais rápido que o quicksort simples.)
fonte
1 - A classificação rápida está no local (não precisa de memória extra, exceto uma quantidade constante).
2 - A classificação rápida é mais fácil de implementar do que outros algoritmos de classificação eficientes.
3 - A classificação rápida possui fatores constantes menores no tempo de execução do que outros algoritmos de classificação eficientes.
Atualização: para classificação de mesclagem, você precisa fazer algumas "mesclagens", que precisam de matriz (s) extra (s) para armazenar os dados antes da mesclagem; mas de maneira rápida, você não. É por isso que a classificação rápida está no lugar. Também existem algumas comparações extras feitas para mesclar, que aumentam os fatores constantes na classificação de mesclagem.
fonte
Em que condições um algoritmo de classificação específico é realmente o mais rápido?
3) A estrutura de dados subjacente consiste em elementos vinculados? Sim -> use sempre a classificação de mesclagem no local. Existem ambos, fáceis de implementar, de tamanho fixo ou de baixo para cima adaptáveis (também conhecidos como naturais), que mesclam tipos de aridades diferentes para estruturas de dados vinculadas e, como nunca exigem a cópia de todos os dados em cada etapa e tampouco exigem recursões, também são mais rápido que qualquer outra classificação geral baseada em comparação, mais rápido que a classificação rápida.
5) O tamanho dos dados subjacentes pode ser vinculado a um tamanho pequeno a médio? por exemplo, n <10.000 ... 100.000.000 (dependendo da arquitetura e estrutura de dados subjacentes)? Sim -> use classificação bitônica ou ímpares pares mesclados do Batcher. Ir para 1)
Dicas de implementação para quicksort:
2) Existem variantes iterativas de baixo para cima e iterativas do quicksort, mas o AFAIK possui os mesmos limites de espaço e tempo assintóticos que os de cima para baixo, sendo os aspectos negativos adicionais difíceis de implementar (por exemplo, gerenciar explicitamente uma fila). Minha experiência é que, para quaisquer fins práticos, nunca vale a pena considerar.
Dicas de implementação para mergesort:
1) a fusão de baixo para cima é sempre mais rápida que a fusão de cima para baixo, pois não requer chamadas de recursão.
2) a fusão muito ingênua pode ser acelerada usando um buffer duplo e alterne o buffer em vez de copiar os dados de volta da matriz temporal após cada etapa.
3) Para muitos dados do mundo real, o mergesort adaptável é muito mais rápido do que um mergesort de tamanho fixo.
Pelo que escrevi, fica claro que o quicksort geralmente não é o algoritmo mais rápido, exceto quando todas as seguintes condições se aplicam:
1) existem mais do que "poucos" valores possíveis
2) a estrutura de dados subjacente não está vinculada
3) não precisamos de uma ordem estável
4) os dados são grandes o suficiente para que o leve tempo de execução assintótico subótimo de um classificador bitônico ou ímpar de Batcher mescla pares
5) os dados não estão quase classificados e não consistem em partes já classificadas maiores
6) podemos acessar a sequência de dados simultaneamente de vários lugares
ps: Alguém precisa me ajudar com a formatação do texto.
fonte
A maioria dos métodos de classificação precisa mover dados em etapas curtas (por exemplo, a classificação por mesclagem faz alterações localmente, depois mescla esses pequenos dados e depois os maiores ...). Em conseqüência, você precisa de muitos movimentos de dados se os dados estiverem longe de seu destino.
fonte