Classificação rápida, classificação rápida de duas vias, classificação rápida de três vias
1. Classificação rápida
1. Conceito
A classificação rápida usa a ideia de dividir e conquistar para classificar os dados
-
Escolha um benchmark
-
Coloque o valor menor que o valor de referência à esquerda do valor de referência e o resto (maior ou igual a) à direita
-
Em seguida, continue a dividir os lados esquerdo e direito até que a duração do intervalo dividido seja 1.
2. Complexidade de tempo
A classificação rápida é O (logN) ao dividir o intervalo, e cada vez que leva O (N) para classificar
Portanto, a complexidade de tempo da classificação rápida é O (NlogN) , que é o caso quando o intervalo pode ser dividido em partes iguais para o valor de referência
Se os dados já estão ordenados, cada vez que você divide e conquista, não há dados de um lado, mas dados do outro lado. Isso é o mesmo que classificação por bolha, e a complexidade de tempo é O (N ^ 2)
Em suma:
A melhor complexidade de tempo: O (NlogN)
Pior complexidade de tempo: O (N ^ 2)
3. Cenários adequados
A classificação rápida é adequada para situações em que os dados estão fora de ordem. Quando os dados estão em ordem, a complexidade do tempo é provavelmente O (N ^ 2), o que não reflete as vantagens da classificação rápida.
4. Otimização
-
Quando dividido em pequenas áreas, a classificação por inserção é usada em vez da classificação rápida
-
Para a implementação recursiva da linha rápida pode ser alterado para não recursivo, usando a pilha para alcançar
-
Cada vez que você seleciona o valor de referência, você não seleciona mais diretamente o primeiro ou o último elemento.
Você pode usar o método do meio de três números ou escolher um valor de referência aleatoriamente
5. Realização
Existem três métodos de implementação para a função de partição de classificação rápida:
-
Método do ponteiro esquerdo e direito
-
Método de escavação
-
Método de ponteiro frontal e traseiro
O método de ponteiros para frente e para trás é usado aqui para alcançar
//前后指针法
int Partion(int array[], int left, int right)
{
int cur = left;
int prev = cur - 1;
while (cur < right)
{
if (array[cur] <= array[right] && ++prev != cur)
{
std::swap(array[cur], array[prev]);
}
cur++;
}
std::swap(array[++prev], array[right]);
return prev;
}
void QuickSort(int array[], int left, int right)
{
if (array == nullptr)
{
return;
}
if (left < right)
{
int index = Partion(array, left, right);
QuickSort(array, left, index - 1);
QuickSort(array, index + 1, right);
}
}
Outros métodos de implementação e código otimizado: https://github.com/YKitty/LinuxDir/blob/master/DS/Sort/Sort.c
2. Fila rápida bidirecional
1. O motivo
Para classificação rápida, se houver muitos elementos de dados e o tamanho dos elementos estiver muito próximo, quando a esquerda e a direita dividem e conquistam, isso fará com que todos os dados de um lado e nenhum dado do outro lado , o que levará a uma eficiência reduzida e complexidade de tempo Torna-se O (N ^ 2)
Exemplo: Use um modelo para testar o tempo de classificação por mesclagem e classificação rápida, defina uma matriz de 1000000, os elementos da matriz são selecionados aleatoriamente entre 0-10, então leva 0,290727s para usar a fusão e 171,151s para classificação rápida, sim, você não tem Errado. Quando a classificação rápida é ótima, é o (nlgn) e, nesse momento, obviamente degenera ao nível de o (n ^ 2). Por que é que?
Para a classificação rápida que escrevi acima, todos os dados menores ou iguais ao valor do benchmark são colocados à esquerda, e os dados maiores do que o benchmark são colocados à direita, então haverá problemas. Não importa quando a condição é maior ou igual ou menor ou igual a, quando há muitos elementos repetidos na matriz, há muitos elementos iguais ao valor de referência, a matriz será dividida em duas partes que são extremamente desequilibradas, porque a parte igual ao valor de referência está sempre concentrada Ao lado da matriz.
Neste momento, o uso da linha rápida bidirecional pode ser otimizado para evitar a redução da eficiência.
Problemas resolvidos pela linha rápida bidirecional:
Não deixe todos os elementos iguais ao valor de referência se concentrarem em um lado da matriz.
2. Pensamento
-
Colocamos todos os elementos menores que o valor de referência no lado esquerdo da matriz e os elementos maiores que o valor de referência no lado direito da matriz
-
Para a esquerda com um índice à esquerda , à direita há um índice à direita
-
Quando a esquerda é menor que o valor de referência , ele continua retrocedendo ++ até encontrar uma esquerda maior ou igual ao valor de referência ; quando a direita é maior que o valor de referência , continua avançando - até encontrar um direito inferior ou igual ao valor de referência
-
Neste momento , os elementos de dados à esquerda e à direita são trocados e, em seguida, à esquerda ++, à direita -
-
Continue a executar o No. 2 até que a esquerda == a direita pare
-
Neste caso, os elementos de valor de referência de troca e posição esquerda , retornados ao valor de referência, podem ser
Esse tipo de pensamento, mesmo que haja muitos elementos de dados repetidos, eles podem ser separados quase igualmente, e não causará uma grande quantidade de dados de um lado e nenhum dado do outro.
3. Cenários adequados
Quando há muitos elementos repetidos na matriz , você não pode usar a classificação rápida, apenas use a classificação rápida bidirecional
4. Realização
Ao implementar, só precisa mudar, a função de partição
int Partion_Two(int array[], int left, int right)
{
int l = left;
int r = right - 1;
while (1)
{
while (l <= r && array[l] < array[right])
{
l++;
}
while (l <= r && array[r] > array[right])
{
r--;
}
if (l > r)
{
break;
}
std::swap(array[r], array[l]);
}
std::swap(array[l], array[right]);
return r;
}
Nota: Encontre o que não é menor do que à esquerda e o que não é maior à direita e, em seguida, execute o julgamento e a troca
3. Fila rápida de três vias
1. Pensamento
Divida a matriz em três partes, menor que o valor de referência, maior que o valor de referência e igual ao valor de referência
Registre os três subscritos:
lt: O último subscrito menor que o valor de referência
gt: o primeiro subscrito maior que o valor de referência
índice: o subscrito sendo percorrido
índice é menor que o valor de referência: índice de câmbio e lt + 1, lt ++
índice é maior que o valor de referência: índice de troca e gt-1, gt--
índice é igual ao valor de referência: índice ++
Condição final: índice == gt
A última etapa é trocar os valores de referência:
troca (arr [lt], arr [direita])
Intervalo contínuo:
[esquerda, lt-1]
[gt, certo]
2. Realização
void QuickSort_Three(int array[], int left, int right)
{
if (array == nullptr)
{
return;
}
if (right <= left)
{
return;
}
int lt = left - 1;
int gt = right;
int index = left;
int key = array[right];
while (index < gt)
{
if (array[index] < key)
{
std::swap(array[index], array[lt + 1]);
lt++;
index++;
}
else if (array[index] > key)
{
std::swap(array[index], array[gt - 1]);
gt--;
}
else
{
index++;
}
}
std::swap(array[index], array[right]);
QuickSort_Three(array, left, lt );
QuickSort_Three(array, gt, right);
}