Quicksort: Escolhendo o pivot
-
03-07-2019 - |
Pergunta
Ao implementar Quicksort, uma das coisas que você tem que fazer é escolher um pivô. Mas quando eu olho para pseudocódigo como a abaixo, não está claro como eu deveria escolher o pivô. Primeiro elemento da lista? Outra coisa?
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 ou não diferentes cenários exigem estratégias diferentes.
Solução
A escolha de um pivot aleatória minimiza a chance que você vai encontrar de pior caso O (n 2 ) desempenho (escolhendo sempre primeiro ou último causaria desempenho pior caso para quase-ordenadas ou quase-reverse dados -sorted). Escolher o elemento do meio também seria aceitável na maioria dos casos.
Além disso, se você estiver implementando isso sozinho, existem versões do algoritmo que o trabalho no local (ou seja, sem a criação de duas novas listas e, em seguida, concatenando-los).
Outras dicas
Depende de suas necessidades. Escolhendo um pivô em marcas aleatórias mais difícil criar um conjunto de dados que gera O desempenho (n ^ 2). 'Median-de-três' (primeiro, último, no meio) é também uma forma de evitar problemas. Cuidado com o desempenho relativo de comparações, embora; se suas comparações são caros, então Mo3 faz mais comparações do que escolher (um valor único pivot) de forma aleatória. registros de banco de dados pode ser caro para comparar.
Update:. Puxando comentários em resposta ??p>
mdkess afirmou:
'mediana de 3' não é o primeiro último meio. Escolha três índices aleatórios, e tomar o valor médio deste. A questão toda é para se certificar de que sua escolha de pivôs não é determinista -. Se for, piores dados de casos pode ser facilmente gerado
Ao que eu respondi:
-
Análise de Find de Hoare Algoritmo Com Median-Of partição -três (1997) por P Kirschenhofer, H Prodinger, C Martínez suporta a sua contenção (que 'mediana-de-três' é de três itens aleatórios).
-
Há um artigo descrito em portal .acm.org que é sobre 'o pior caso Permutation para Median-de-três Quicksort' por Hannu Erkiö, publicado no The Computer Journal, Vol 27, no 3, 1984. [Update 2012-02-26: tem o texto para o artigo . Seção 2 'The Algorithm' começa: ' Usando a mediana das primeiras, médio e último elementos de A [L: R], divisórias eficientes em partes de tamanhos bastante iguais pode ser alcançado na maioria das situações práticas ' Assim, é discutir a abordagem Mo3 primeira meia-passado.]
-
Outro pequeno artigo que é interessante é por MD McIlroy, "A Killer adversário para Quicksort ", publicado em Software-prática e experiência, Vol. 29 (0), 1-4 (0 1999). Ele explica como fazer quase qualquer Quicksort se comportar de forma quadrática.
-
AT & T Bell Labs Tech Journal, Out 1984 "Teoria e Prática na construção de um Trabalho Classificar rotina" estados "Hoare sugeriu particionar em torno da mediana das várias linhas selecionadas aleatoriamente. Sedgewick [...] recomendado escolher o mediana do primeiro [...] última [...] e do meio". Isto indica que ambas as técnicas para 'mediana-de-três' são conhecidos na literatura. (Atualização 2014/11/23: O artigo parece estar disponível em IEEE Xplore ou de Wiley -. Se você tem membros ou estão dispostos a pagar uma taxa)
-
'Engenharia de Ordenar Função' por JL Bentley e MD McIlroy, publicado em Software Prática e Experiência, Vol 23 (11), novembro de 1993, vai para uma ampla discussão das questões, e eles escolheram um algoritmo de particionamento adaptativo baseado em parte do tamanho dos dados conjunto. Há muita discussão de trade-offs para várias abordagens.
-
uma pesquisa no Google para 'mediana-de-três' funciona muito bem para posterior monitoramento.
Obrigado pela informação; Eu só tinha encontrado o determinista 'média-de-três' antes.
Heh, eu só ensinou esta classe.
Existem várias opções.
Simples: Escolha o primeiro ou o último elemento da gama. (Mau na entrada parcialmente ordenado)
Melhor: Escolha o item no meio da faixa. (Entrada de melhor em parcialmente ordenado)
No entanto, escolhendo qualquer elemento arbitrário corre o risco de mal particionamento da matriz de tamanho n em duas matrizes de tamanho 1 e n-1. Se você fizer isso com bastante frequência, o quicksort corre o risco de se tornar O (n ^ 2).
Uma melhoria que eu vi é pegar mediana (primeiro, último, médio); Na pior das hipóteses, ele ainda pode ir para O (n ^ 2), mas probabilisticamente, este é um caso raro.
Para a maioria dos dados, escolher a primeira ou a última é suficiente. Mas, se você achar que você está correndo para os piores cenários, muitas vezes (entrada parcialmente ordenados), a primeira opção seria a de escolher o valor central (que é uma estatística boa pivot para dados parcialmente ordenados).
Se você ainda está correndo em problemas, em seguida, ir a rota mediano.
Nunca escolher um pivô fixo - este pode ser atacado de explorar do seu algoritmo de pior caso O (n ^ 2) tempo de execução, que é apenas a pedir sarilhos. do tempo de execução de Quicksort pior caso ocorre quando o particionamento resultados em uma matriz de um elemento, e um conjunto de n-1 elementos. Suponha que você escolher o primeiro elemento como sua partição. Se alguém alimenta um array para o seu algoritmo que é em ordem decrescente, o seu primeiro pivô será o maior, então tudo o resto na matriz irá se mover para a esquerda dele. Então, quando você recurse, o primeiro elemento será o maior de novo, então uma vez mais você colocar tudo para a esquerda dele, e assim por diante.
A melhor técnica é o método média-of-3, onde você escolhe três elementos de forma aleatória, e escolher o meio. Você sabe que o elemento que você escolher não vai 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 quiser garantir O (NLGN) tempo de execução para o algoritmo, o método colunas-of-5 para encontrar a mediana de uma matriz é executado em O (n), o que significa que a equação de recorrência para quicksort na pior caso será T (n) = o (n) (encontrar a mediana) + o (n) (partição) + 2T (n / 2) (recurse esquerda e direita.) Pelo Teorema Mestre, este é o (n lg n). No entanto, o fator constante será enorme, e se pior desempenho caso é a sua principal preocupação, use um merge sort em vez disso, que é apenas um pouco mais lento do que quicksort em média, e garantias O (NLGN) tempo (e será muito mais rápido do que isso quicksort coxo mediana).
Não tente ficar muito inteligente e combinar estratégias articuladas. Se você combinou média de 3 com pivot aleatória escolhendo a mediana da primeira, última e uma aleatória índice no meio, então você ainda estará vulnerável a muitas das distribuições que enviam média de 3 quadrático (pelo que a sua realmente pior do que pivot aleatória simples)
Por exemplo, um órgão de distribuição de tubo (1,2,3 ... N / 2..3,2,1) primeira e última vontade tanto ser um e o índice aleatório será algum número maior do que 1, tendo a mediana dá 1 ( o primeiro ou o último) e você terá uma partição extermely desequilibrado.
É inteiramente dependente da forma como os seus dados são ordenados para começar. Se você acha que vai ser pseudo-aleatório, em seguida, sua melhor aposta, quer seja para escolher uma seleção aleatória ou escolher o meio.
Se você está classificando um conjunto aleatório acessível (como um array), é em geral melhor para escolher o item do meio físico. Com isso, se a matriz está tudo pronto classificadas (ou quase classificadas), as duas partições será perto mesmo, e você vai ter a melhor velocidade.
Se você está classificando algo com apenas o acesso linear (como uma lista ligada), então é melhor escolher o primeiro item, porque é o item mais rápida de acesso. Aqui, no entanto, se a lista já está classificado, você está ferrado -. Uma partição será sempre nula, eo outro tem tudo, produzindo o pior momento
No entanto, para uma lista ligada, pegando qualquer coisa além da primeira, só vai piorar a situação. Ele escolhe o item do meio em uma lista na lista, você teria que passo por ele em cada etapa partição - adicionando uma operação O (N / 2), que é feito vezes logN fazendo tempo total de O (1,5 N * log N) e isso é se sabemos quanto tempo a lista é, antes de começar - geralmente nós não, então teríamos para o passo todo o caminho através de contá-las, em seguida, passo a meio de encontrar o meio, então passo através de um terceira vez para fazer a partição real: o (2,5 N * N log N)
É mais fácil quebrar o quicksort em três seções fazer isso
- função Exchange ou elemento de dados de swap
- A função de partição
- Processamento as partições
É apenas um pouco mais ineficiente do que uma função muito tempo, mas é muito mais fácil de entender.
código a seguir:
/* This selects what the data type in the array to be sorted is */
#define DATATYPE long
/* This is the swap function .. your job is to swap data in x & y .. how depends on
data type .. the example works for normal numerical data types .. like long I chose
above */
void swap (DATATYPE *x, DATATYPE *y){
DATATYPE Temp;
Temp = *x; // Hold current x value
*x = *y; // Transfer y to x
*y = Temp; // Set y to the held old x value
};
/* This is the partition code */
int partition (DATATYPE list[], int l, int h){
int i;
int p; // pivot element index
int firsthigh; // divider position for pivot element
// Random pivot example shown for median p = (l+h)/2 would be used
p = l + (short)(rand() % (int)(h - l + 1)); // Random partition point
swap(&list[p], &list[h]); // Swap the values
firsthigh = l; // Hold first high value
for (i = l; i < h; i++)
if(list[i] < list[h]) { // Value at i is less than h
swap(&list[i], &list[firsthigh]); // So swap the value
firsthigh++; // Incement first high
}
swap(&list[h], &list[firsthigh]); // Swap h and first high values
return(firsthigh); // Return first high
};
/* Finally the body sort */
void quicksort(DATATYPE list[], int l, int h){
int p; // index of partition
if ((h - l) > 0) {
p = partition(list, l, h); // Partition list
quicksort(list, l, p - 1); // Sort lower partion
quicksort(list, p + 1, h); // Sort upper partition
};
};
O ideal seria o pivô deve ser o valor médio em toda a matriz. Isso irá reduzir as chances de ficar pior desempenho caso.
complexidade do tipo Breve varia muito com a seleção de valor pivô. por exemplo, se você sempre escolher primeiro elemento como um pivô, a complexidade do algoritmo torna-se tão pior como O (n ^ 2). aqui é um método inteligente para escolher pivot element- 1. Escolha o primeiro, meio, último elemento do array. 2. comparar estas três números e encontrar o número que é maior do que um e menor do que o outro ou seja mediana. 3. fazer este elemento como elemento pivô.
escolher o pivô por este método divide a matriz em quase duas meia e, portanto, a complexidade reduz a O (nlog (n)).
Em média, mediana de 3 é bom para a pequena n. A mediana de cinco é um pouco melhor para n maior. O ninther, que é a "média de três medianas de três" é ainda melhor para muito grande n.
Quanto maior você vai com a amostragem do melhor que você começa quando n aumenta, mas a melhora dramaticamente diminui à medida que aumenta as amostras. E você provoca a sobrecarga de amostragem e triagem de amostras.
Eu recomendo usar o índice de média, como pode ser facilmente calculado.
Você pode calcular-lo por arredondamento (array.length / 2).
Em uma implementação verdadeiramente otimizado, o método para a escolha de pivot deve depender do tamanho da matriz - para uma grande variedade, vale a pena gastar mais tempo escolhendo um bom pivô. Sem fazer uma análise completa, eu acho "meio de O (log (n)) elementos" é um bom começo, e isso tem a vantagem adicional de não requerer qualquer memória extra: Usando tail-call sobre a maior partição e in- lugar de particionamento, usamos o mesmo o (log (n)) de memória extra em quase todas as fases do algoritmo.