Ao implementar o Quicksort, uma das coisas que você deve fazer é escolher um pivô. Mas quando vejo um pseudocódigo como o mostrado abaixo, não fica claro como devo escolher o pivô. Primeiro elemento da lista? Algo mais?
function quicksort(array)
var list less, greater
if length(array) ≤ 1
return array
select and remove a pivot value pivot from array
for each x in array
if x ≤ pivot then append x to less
else append x to greater
return concatenate(quicksort(less), pivot, quicksort(greater))
Alguém pode me ajudar a entender o conceito de escolher um pivô e se diferentes cenários exigem ou não estratégias diferentes.
algorithm
sorting
pseudocode
quicksort
Jacob T. Nielsen
fonte
fonte
Respostas:
A escolha de um pivô aleatório minimiza a chance de você encontrar o desempenho O (n 2 ) de pior caso (sempre escolher o primeiro ou o último causaria o desempenho de pior caso para dados classificados quase ou classificados de forma reversa). A escolha do elemento intermediário também seria aceitável na maioria dos casos.
Além disso, se você estiver implementando isso sozinho, existem versões do algoritmo que funcionam no local (ou seja, sem criar duas novas listas e depois concatená-las).
fonte
Depende dos seus requisitos. A escolha de um pivô aleatoriamente torna mais difícil criar um conjunto de dados que gere desempenho O (N ^ 2). 'Média de três' (primeiro, último, meio) também é uma forma de evitar problemas. No entanto, tenha cuidado com o desempenho relativo das comparações; se suas comparações forem caras, o Mo3 fará mais comparações do que escolher (um único valor de pivô) aleatoriamente. Os registros do banco de dados podem ser caros para comparar.
Atualização: Colocando comentários em resposta.
mdkess afirmou:
Ao que respondi:
Análise do algoritmo Find de Hoare com partição de mediana de três (1997) por P Kirschenhofer, H Prodinger, C Martínez apóia sua tese (que 'mediana de três' é três itens aleatórios).
Há um artigo descrito em portal.acm.org que é sobre 'The Worst Case Permutation for Median-of-Three Quicksort' por Hannu Erkiö, publicado no The Computer Journal, Vol 27, No 3, 1984. [Atualização 2012-02- 26: Recebi o texto do artigo . Seção 2 'O algoritmo' começa: ' Usando a mediana do primeiro, do meio e do último elemento de A [L: R], partições eficientes em partes de tamanhos razoavelmente iguais podem ser obtidas na maioria das situações práticas. 'Portanto, está discutindo a abordagem Mo3 primeiro-meio-último.]
Outro artigo curto que é interessante é de MD McIlroy, "A Killer Adversary for Quicksort" , publicado em Software-Practice and Experience, Vol. 29 (0), 1–4 (0 1999). Ele explica como fazer quase qualquer Quicksort se comportar quadraticamente.
AT&T Bell Labs Tech Journal, outubro de 1984 "Teoria e prática na construção de uma rotina de classificação de trabalho" afirma "Hoare sugeriu o particionamento em torno da mediana de várias linhas selecionadas aleatoriamente. Sedgewick [...] recomendou escolher a mediana das primeiras [. ..] passado [...] e meio ". Isso indica que ambas as técnicas para 'mediana de três' são conhecidas na literatura. (Atualização 2014-11-23: O artigo parece estar disponível no IEEE Xplore ou na Wiley - se você for membro ou estiver disposto a pagar uma taxa).
'Engineering a Sort Function' por JL Bentley e MD McIlroy, publicado em Software Practice and Experience, Vol 23 (11), novembro de 1993, entra em uma ampla discussão sobre os problemas e eles escolheram um algoritmo de particionamento adaptativo baseado em parte no tamanho do conjunto de dados. Há muita discussão sobre compensações para várias abordagens.
Uma pesquisa no Google por 'mediana de três' funciona muito bem para rastreamento posterior.
Obrigado pela informação; Eu só havia encontrado a 'mediana de três' determinística antes.
fonte
Heh, acabei de dar essa aula.
Existem várias opções.
Simples: escolha o primeiro ou o último elemento do intervalo. (ruim na entrada parcialmente classificada) Melhor: Escolha o item no meio do intervalo. (melhor na entrada parcialmente classificada)
No entanto, escolher qualquer elemento arbitrário corre o risco de particionar mal o array de tamanho n em dois arrays de tamanho 1 e n-1. Se você fizer isso com frequência suficiente, seu quicksort corre o risco de se tornar O (n ^ 2).
Uma melhoria que vi foi escolher a mediana (primeiro, último, meio); No pior caso, ele ainda pode ir para O (n ^ 2), mas probabilisticamente, este é um caso raro.
Para a maioria dos dados, escolher o primeiro ou o último é suficiente. Mas, se você descobrir que está se deparando com os piores cenários com frequência (entrada parcialmente classificada), a primeira opção seria escolher o valor central (que é um pivô estatisticamente bom para dados parcialmente classificados).
Se ainda tiver problemas, siga o caminho do meio.
fonte
Nunca, jamais escolha um pivô fixo - ele pode ser atacado para explorar o pior caso de tempo de execução O (n ^ 2) do seu algoritmo, que está apenas pedindo problemas. O pior caso de tempo de execução do Quicksort ocorre quando o particionamento resulta em uma matriz de 1 elemento e uma matriz de n-1 elementos. Suponha que você escolha o primeiro elemento como sua partição. Se alguém alimentar seu algoritmo com uma matriz que está em ordem decrescente, seu primeiro pivô será o maior, de modo que todo o resto da matriz se moverá para a esquerda. Então, quando você recursa, o primeiro elemento será o maior novamente, então, mais uma vez, você coloca tudo à esquerda dele e assim por diante.
Uma técnica melhor é o método da mediana de 3, em que você escolhe três elementos aleatoriamente e escolhe o meio. Você sabe que o elemento que você escolher não será o primeiro ou o último, mas também, pelo teorema do limite central, a distribuição do elemento do meio será normal, o que significa que você tenderá para o meio (e, portanto, , n lg n tempo).
Se você realmente deseja garantir o tempo de execução O (nlgn) para o algoritmo, o método de colunas de 5 para encontrar a mediana de uma matriz é executado no tempo O (n), o que significa que a equação de recorrência para quicksort no pior caso seja T (n) = O (n) (encontre a mediana) + O (n) (partição) + 2T (n / 2) (retroceda à esquerda e à direita.) Pelo Teorema Mestre, este é O (n lg n) . No entanto, o fator constante será enorme e se o desempenho do pior caso for sua preocupação principal, use uma classificação por mesclagem, que é apenas um pouco mais lenta do que a classificação rápida em média e garante o tempo O (nlgn) (e será muito mais rápido do que este quicksort mediano coxo).
Explicação do Algoritmo da Mediana das Medianas
fonte
Não tente ser muito inteligente e combinar estratégias de pivô. Se você combinou mediana de 3 com pivô aleatório escolhendo a mediana do primeiro, último e um índice aleatório no meio, então você ainda estará vulnerável a muitas das distribuições que enviam mediana de 3 quadrático (então é realmente pior do que pivô aleatório simples)
Por exemplo, uma distribuição de órgão de tubos (1,2,3 ... N / 2..3,2,1) primeiro e último serão ambos 1 e o índice aleatório será algum número maior que 1, tomando a mediana resulta em 1 ( seja o primeiro ou o último) e você obterá um particionamento extremamente desequilibrado.
fonte
É mais fácil quebrar o quicksort em três seções fazendo isso
É apenas um pouco mais ineficiente do que uma função longa, mas é muito mais fácil de entender.
Código a seguir:
fonte
Para começar, é totalmente dependente de como seus dados são classificados. Se você acha que será pseudoaleatório, sua melhor aposta é escolher uma seleção aleatória ou escolher o meio.
fonte
Se você estiver classificando uma coleção acessível aleatoriamente (como uma matriz), geralmente é melhor escolher o item físico do meio. Com isso, se o array estiver todo ordenado (ou quase ordenado), as duas partições ficarão quase iguais e você obterá a melhor velocidade.
Se você estiver classificando algo apenas com acesso linear (como uma lista vinculada), é melhor escolher o primeiro item, porque é o item mais rápido de acessar. Aqui, porém, se a lista já estiver ordenada, você está ferrado - uma partição sempre será nula e a outra terá tudo, produzindo o pior momento.
No entanto, para uma lista vinculada, escolher qualquer coisa além do primeiro só tornará as coisas piores. Ele escolhe o item do meio em uma lista listada, você terá que passar por ele em cada etapa de partição - adicionando uma operação O (N / 2) que é feita logN vezes fazendo com que o tempo total O (1,5 N * log N) e isso se soubermos quanto tempo a lista é antes de começarmos - geralmente não sabemos, então teríamos que percorrer todo o caminho para contá-los, depois percorrer meio caminho para encontrar o meio e, em seguida, percorrer um terceira vez para fazer a partição real: O (2,5N * log N)
fonte
Idealmente, o pivô deve ser o valor do meio em toda a matriz. Isso reduzirá as chances de obter o pior desempenho possível.
fonte
A complexidade da classificação rápida varia muito com a seleção do valor de pivô. por exemplo, se você sempre escolher o primeiro elemento como um pivô, a complexidade do algoritmo se torna tão pior quanto O (n ^ 2). aqui está um método inteligente para escolher o elemento pivô - 1. escolha o primeiro, o meio e o último elemento do array. 2. compare esses três números e encontre o número que é maior que um e menor que o outro, ou seja, a mediana. 3. faça este elemento como elemento pivô.
escolher o pivô por este método divide o array em quase duas metades e, portanto, a complexidade se reduz a O (nlog (n)).
fonte
Em média, a mediana de 3 é boa para n pequeno. A mediana de 5 é um pouco melhor para n maior. O ninther, que é a "mediana de três medianas de três", é ainda melhor para n muito grande.
Quanto mais alto você vai com a amostragem, melhor você obtém à medida que n aumenta, mas a melhoria diminui drasticamente conforme você aumenta as amostras. E você incorre na sobrecarga de amostragem e classificação de amostras.
fonte
Eu recomendo usar o índice do meio, pois pode ser calculado facilmente.
Você pode calculá-lo arredondando (array.length / 2).
fonte
Em uma implementação verdadeiramente otimizada, o método para escolher o pivô deve depender do tamanho do array - para um array grande, vale a pena gastar mais tempo escolhendo um bom pivô. Sem fazer uma análise completa, eu diria que "meio de elementos O (log (n))" é um bom começo, e isso tem a vantagem adicional de não exigir nenhuma memória extra: usar a chamada final na partição maior e Para colocar o particionamento, usamos a mesma O (log (n)) memória extra em quase todos os estágios do algoritmo.
fonte