Matriz de classificação LeetCode912 (explicação detalhada da classificação rápida e sua otimização)
Diretório de artigos
Abstrato
Tentei primeiro usar o algoritmo de classificação rápida para resolver esse problema, mas não consegui exceder o limite de tempo. Os dois casos de teste que excedem o limite de tempo são: 1. Todos os elementos do array são iguais 2. O array já está classificado em ordem crescente. Para o primeiro caso de teste, o método do ponteiro de três direções pode ser usado (ou seja, os elementos iguais ao elemento pivô são listados em um bloco separado). Para o segundo caso de teste, você pode usar o método de seleção aleatória do elemento pivô (usando a função Rand() de C++% o comprimento da matriz a ser classificada + o subscrito inicial da matriz a ser classificada). Neste artigo, primeiro apresento o método básico de classificação rápida e, em seguida, apresento duas ideias de otimização. No final, usei duas ideias de otimização ao mesmo tempo e finalmente passei no Leetcode912.
Classificação rápida básica
Assim como a classificação por mesclagem, a classificação rápida também usa a ideia de dividir e conquistar. A seguir está um processo de divisão e conquista em três etapas para classificação rápida de um subarray típico A[p...r].
Decomposição: A matriz A[p…r] é dividida em duas submatrizes (possivelmente vazias) A[p…q-1] e A[q+1…r], de modo que em A[p…q-1] Cada elemento of é menor ou igual a A[q], e A[q] também é menor ou igual a cada elemento em A[q+1…r]. Dentre eles, o cálculo do subscrito q também faz parte do processo de divisão.
**Solução:** Classifique as submatrizes A[p...q-1] e A[q+1...r] chamando recursivamente a classificação rápida.
Mesclar: como os subarrays são classificados no local, não há necessidade de uma operação de mesclagem: o array A[p...r] já está classificado.
Pseudocódigo para classificação rápida
Classificação rápida (A, p, r)
se p<r
q = PARTIÇÃO(A, p,r)
QuickSort(A, p, q-1)
QuickSort(A, q+1, r)
Para classificar todos os elementos de um array A, a chamada inicial é QUICKSORT(A, 1, A.length)
Divisão de matriz
A parte principal do algoritmo é o processo PARTITION, que implementa o rearranjo local do subarranjo A[p...r].
PARTIÇÃO(A, p,r)
x = UMA[r]
eu = p-1
para j = p para r-1
se(A[j]<=x)
eu++;
troca(A[j], A[i])
troca(A[i+1], A[r])
retornar i+1;
O ponto chave para dominar este algoritmo é saber quais quantidades mudam e quais quantidades não mudam. O que muda são os dois ponteiros i e j, e o que permanece inalterado é p e r. Só precisamos saber como alterar i e j durante o processo de digitalização.
O que precisamos garantir é:
- A[p…i]是<=x
- A[i+1…j-1]是>=x.
- A[j…r-1] é desconhecido
- A[r] é igual a x.
Se A[j]<=x, você precisa colocar o elemento na parte menor ou igual a x, primeiro i++, depois trocar A[j] e A[i], depois j++;
Se A[j]>x, então apenas j++ será necessário.
Quando digitalizamos para A[r], precisamos colocar o elemento divisor na posição que definimos para o elemento divisor, ou seja, a posição de i+1. Então troque A[r] e A[i+1] e retorne i+1;
Resumo e otimização de ideias de classificação rápida
-
Ideia básica: a classificação rápida organiza um elemento de cada vez, depois classifica recursivamente a parte esquerda e a parte direita e prossegue em sequência até que a matriz seja classificada.
-
Conforme mostrado na imagem: Esta imagem reflete bem que a essência da classificação rápida é na verdade uma árvore recursiva.
Problemas com esta divisão
1. Se todos os elementos da matriz forem iguais e o número de elementos for particularmente grande, é provável que ocorra um tempo limite. Por exemplo, [2,2,2,2,2], após a chamada, haverá [2,2,2,2], depois [2,2,2], depois [2,2] e, finalmente [2].Então a complexidade do tempo é O ( ∑ i = 1 n ) = O ( n 2 ) O(\sum_{i=1}^n) = O(n^2)O ( ∑eu = 1não)=Ó ( n2 )Neste momento, a árvore basicamente classificada é uma árvore com apenas um lado.
2. Se os elementos da matriz foram basicamente classificados, por exemplo, uma matriz de 1 a 50.000 foi classificada em ordem crescente. Para esta matriz, a classificação rápida básica é dividida em:
Ideias de otimização
Para esta estrutura de árvore, nossa otimização pode partir de dois aspectos: um é equilibrar ao máximo as duas extremidades da árvore e o outro é reduzir ao máximo a altura da árvore.
Para matrizes ordenadas progressivamente
- Ao obter aleatoriamente o pivô, as duas pontas da árvore ficarão mais equilibradas.
Para matrizes com muitos elementos idênticos
Fila rápida de três vias
- A classificação rápida de três vias (método de três ponteiros) coloca todos os elementos iguais ao elemento pivô no meio do intervalo dividido. Ou seja, o que determinamos recursivamente a cada vez é a posição desse elemento e de seus elementos iguais. Quando um grande número de elementos é igual. Abaixo, o intervalo de recursão é bastante reduzido.
- Particionamento de matriz:
- A[p…i] < x, A[i+1…j-1]==x, A[j…k-1] 未知, A[k…r-1]>x. UMA[r] == x.
Código
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
srand((unsigned)time(NULL));
int size_nums = nums.size();
quicksort(nums, 0, size_nums-1);
return nums;
}
vector<int> partition(vector<int>& nums, int p, int r){
int rand_index = rand() % (r-p+1)+p;
int x = nums[rand_index];
swap(nums[rand_index], nums[r]);
int i = p -1;
int j = p;
int k = r;
//循环不变量
// nums[p..i] < x
// nums[i+1..j-1] equals x
// nums[j..k-1] unknown
// nums[k..r-1] >x
// nums[r] equals x
// 循环不变量在第一轮迭代之前是成立的。
while(j<=k-1){
if(nums[j]<x){
i++;
swap(nums[j], nums[i]);
j++;
}
else if(nums[j] == x){
j++;
}
else{
k--;
swap(nums[j], nums[k]);
}
}
swap(nums[k], nums[r]);
vector<int> results;
results.push_back(i);
results.push_back(k);
return results;
}
void quicksort(vector<int>& nums, int p, int r){
if(p>=r){
return;
}
if(p<r){
//随机选取法
vector<int> partitions = partition(nums, p, r);
quicksort(nums, p, partitions[0]);
quicksort(nums, partitions[1], r);
}
}
};
Outra implementação de código de classificação rápida básica
A classificação rápida tem dois pontos principais, nomeadamente "divisão sentinela" e "recursão".
Operação de divisão sentinela: Tomando um determinado elemento da matriz (geralmente o primeiro elemento é selecionado) como o número base, mova todos os elementos menores que o número base para a esquerda e mova os elementos maiores que o número base para a direita.
Através de uma rodada de divisão sentinela, o problema de classificação de matrizes pode ser dividido no problema de classificação de duas matrizes mais curtas (chamadas de submatrizes esquerda (direita) neste artigo).
Recursão: execute recursivamente a divisão sentinela no subarray esquerdo e no subarray direito até que o comprimento do subarray seja 1. A recursão é encerrada e todo o array pode ser classificado.
Código de amostra
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
quickSort(strs, 0, nums.size() - 1);
return nums;
}
private:
void quickSort(vector<string>& strs, int l, int r) {
if(l >= r) return;
int i = l, j = r;
while(i < j) {
while(nums[j] >= nums[l] && i < j) j--;
while(nums[i] <= nums[l] && i < j) i++;
swap(nums[i], nums[j]);
}
swap(nums[i], nums[l]);
quickSort(nums, l, i - 1);
quickSort(nums, i + 1, r);
}
};
referências
[1] https://leetcode.cn/problems/sort-an-array/solution/kuai-su-pai-xu-you-hua-zhen-dui-duo-zhon-ryq4/
[2] A segunda implementação de código de classificação rápida https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/solution/mian-shi-ti-45 - ba-shu-zu-pai-cheng-zui-xiao-de-s-4/