[Introdução ao algoritmo MIT] Algoritmo de classificação rápida e randomização

Prefácio

Este artigo é as notas que acompanham a aula aberta "Introdução aos Algoritmos do MIT" na estação B. Amigos que tiverem dúvidas sobre o conteúdo e formato das notas podem se comunicar em mensagens privadas ou na área de comentários ~

O algoritmo de classificação rápida também é uma das aplicações do algoritmo de divisão e conquista , e é classificado no local (reorganizando os elementos na área de dados original), e a classificação rápida também é muito prática .


1. Descrição da classificação rápida

  1. Divida e conquiste estratégia na fila rápida

①Dividir

Selecione um elemento-chave e divida a matriz em duas partes de submatriz de acordo com o tamanho do elemento-chave, a submatriz esquerda é menor que o elemento chave; o elemento na submatriz direita ≥ o elemento-chave.
Insira a descrição da imagem aqui

②Conquer

Lidar recursivamente com o problema de classificação de duas submatrizes

③ Combinar

No problema de classificação rápida, combinar essa etapa não é muito importante, porque quando a divisão for concluída e o problema de classificação dos dois submatrizes for processado recursivamente, todo o array já estará em ordem.

A etapa mais importante na classificação rápida é a etapa de partição - encontrar um elemento de segmentação e dividir o array inteiro em dois subarrays.

A partir dessa perspectiva, você pode entender o problema de classificação rápida como uma divisão contínua da matriz de forma recursiva.

  1. Representação de pseudocódigo de classificação rápida

(1) partição ()

int Partition(int A[],int p,int q){
    
    
  x ← A[p]
  i ← p
  for j ← P+1 to q
    do if A[j]≤x
      then i ← i+1
      exch(A[i],A[j])
  exch(A[p],A[i])
  return i

A ideia principal do código acima:

  • Cada vez que o primeiro elemento da matriz é selecionado como o elemento dividido, o ponteiro i e o anterior salvam todos os elementos que são menores ou iguais ao elemento dividido; os ponteiros i a j armazenam todos os elementos maiores ou iguais ao elemento dividido; os elementos após o ponteiro j É um elemento que ainda não foi comparado
  • Cada vez que um elemento é revisado, se o elemento for maior do que o elemento segmentado, o ponteiro j pode continuar a se mover para trás; se o elemento atual for menor do que o elemento segmentado, o elemento atual e o elemento apontado por i + 1 são trocado (de acordo com o primeiro ponto, o ponteiro i O elemento apontado por +1 deve ser o primeiro elemento atualmente comparado que é maior que o elemento segmentado), e então mover os ponteiros iej um bit para trás.
  • Depois que o loop final é concluído, o elemento segmentado é trocado pelo elemento apontado pelo ponteiro i, e o índice i é retornado como o índice após a rodada de segmentação.

A ideia do pseudocódigo dada no livro "Introdução aos Algoritmos" é a mesma, exceto que o último elemento da matriz é selecionado como o elemento de segmentação.
Insira a descrição da imagem aqui

Os exemplos de partição com os quais tive contato na classe de algoritmo têm aproximadamente os mesmos elementos de divisão de seleção de ideia e, em seguida, mantêm ponteiros para comparar com elementos de divisão e elementos de troca que não correspondem à relação de tamanho. Mas naquela época ele estava digitalizando ao mesmo tempo

int partition(int a[],int lo, int hi){
    
    
    int i = lo,j = hi+1;
    int v = a[lo];//切分元素
    while(true){
    
    
      while(less(a[++i],v) if(i == hi) break;
      while(less(v,a[--j])) if( j == lo) break;
      if( i >= j) break;//当指针i和j相遇时主循环就会退出
      exch(a,i,j);
      }
    exch(a,lo,j);//将v = a[j]放入正确的位置
    return j;
    }
  • No loop, quando a [i] é menor que v, aumentamos i, quando a [j] é maior que v, diminuímos j e, em seguida, trocamos a [i] e a [j] para garantir que nenhum dos elementos à esquerda de i Maiores que v, os elementos do lado direito de j não são menores que v.
  • Quando os ponteiros se encontram, troque a [lo] e a [j], e a divisão termina - de modo que o valor da divisão permaneça em a [j].

O código de implementação de partição extraído no livro de Algoritmos, a fim de buscar simetria, tem alguma inicialização de subscrito especial, portanto, não há necessidade de entrar nele.

Independentemente da implementação, a complexidade de tempo do processo de segmentação é provavelmente mantida em θ (n), porque todo o processo é equivalente a percorrer todos os elementos do array.


(2) quicksort ()
Insira a descrição da imagem aqui


(3) Melhorar dicas

①Ajuste o código "if (p <q)":

Quando o código é if (p <q), significa que não há carga de trabalho quando o array processado tem 0 ou 1 elemento.

Isso pode ser melhorado para encontrar um algoritmo adequado para melhorar a eficiência da classificação quando o número de elementos é pequeno.
[Este ponto também é implementado no aprimoramento do algoritmo de mesclagem " Implementação e análise do algoritmo de classificação de mesclagem" ]

②De acordo com o pseudocódigo de classificação rápida, podemos saber que o algoritmo é recursivo na cauda, ​​portanto, alguns procedimentos de otimização recursiva na cauda podem ser usados.

Para alguns conceitos de recursão de cauda, ​​consulte [Allen Chou] o artigo deste blogueiro "Explicando o que é recursão de cauda (fácil de entender, explicação de exemplo)"


  1. Exemplo de fila rápida

Insira a descrição da imagem aqui
Nota: O processo acima é baseado na implementação da partição, ou seja, o último elemento da matriz é selecionado como resultado do elemento dividido.


2. Análise da complexidade da classificação rápida

Em primeiro lugar, darei uma conclusão. Esta seção realizará muitos processos de derivação "interessantes" para

O tempo de execução da classificação rápida está relacionado a se a partição é simétrica e se a partição é simétrica em grande medida depende de qual elemento de partição está selecionado.

Se a divisão for simétrica, a classificação rápida terá a mesma complexidade que a classificação por mesclagem no sentido progressivo;
se a divisão for assimétrica, a classificação rápida terá a mesma complexidade que a classificação por inserção no sentido progressivo.

1. Classificação do pior caso

O pior caso de comportamento de divisão de classificação rápida ocorre quando as duas regiões geradas pelo processo de divisão contêm n-1 elementos e 1 0 elemento respectivamente (da perspectiva de toda a matriz, ou seja, quando toda a matriz está em ordem ou reversa pedido).

Supondo que cada divisão use divisão assimétrica, e o custo de tempo da divisão seja θ (n), e o custo da chamada recursiva para uma matriz de elemento zero T (0) seja definido como θ (1), então:
Insira a descrição da imagem aqui
então o algoritmo é executado A expressão recursiva do tempo é mostrada na figura abaixo:
Insira a descrição da imagem aqui

Intuitivamente, se o custo de cada camada de recursão for adicionado, deve-se obter uma progressão aritmética, de modo que se possa deduzir que o custo é quadrado.

Pode-se ver que o desempenho médio da classificação rápida é consistente com o desempenho médio da classificação por inserção no pior caso. Mesmo quando os elementos estão todos ordenados, a classificação por inserção requer apenas complexidade de tempo linear O (n).

A árvore de recursão é semelhante a esta:
Insira a descrição da imagem aqui

O gráfico ps é um pouco alto e ambíguo, perdoe

a árvore recursiva obtida na figura acima (a estrutura é muito desequilibrada), a altura da árvore é n (porque cada camada é reduzida em um), a soma da complexidade de todos os nós raiz é adicionado. É o nível do quadrado, a complexidade de todos os nós folha (um total de n) é θ (1) e a soma é o nível linear.

Portanto, a soma total é θ (n 2 ) + θ (n) = θ (n 2 ).

2. Análise do melhor caso

(1) Fórmula de recorrência de complexidade

Na divisão mais equilibrada obtida na etapa de divisão, o tamanho dos dois subproblemas obtidos não pode ser maior que n / 2. Nesse caso, a velocidade da operação de classificação rápida é muito mais rápida.
Para derivar a fórmula recursiva da complexidade neste momento, existem:
Insira a descrição da imagem aqui

(2) Divisão equilibrada

Mesmo quando sabemos que o pior cenário de classificação rápida é a complexidade do nível do quadrado, ainda aprovamos fortemente a eficiência da classificação rápida, porque a partir da situação média, a eficiência da classificação rápida também é de nível logarítmico linear.

O caso médio de classificação rápida está próximo do tempo de execução do melhor caso, e não do tempo de execução do pior caso; para entender isso, é necessário entender como o equilíbrio da divisão se reflete na expressão recursiva que caracteriza a execução Tempo.

Assumindo que o processo de divisão sempre produz uma divisão 9: 1
, a fórmula recursiva da complexidade é escrita como:
T (n) = T (n / 10) + T (9n / 10) + θ (n) ≤ T (n / 10)) + T (9n / 10) + cn

Para construir uma árvore recursiva para a fórmula recursiva acima, a altura da árvore e o custo de cálculo de cada camada são mostrados na figura abaixo.
Insira a descrição da imagem aqui
De acordo com a estrutura da árvore, toda a complexidade pode ser analisada, T (n) ≤c · n · log 10/9 n + θ (n), a complexidade desta fórmula no sentido assintótico é O (nlogn)

Do ponto de vista dos cálculos matemáticos, não importa qual seja a base das operações logarítmicas, a base pode ser convertida em 2 por meio da natureza das operações logarítmicas e, em seguida, os termos excedentes são adicionados a todo o termo logarítmico como coeficientes.

3. Análise da situação média

Para se ter uma compreensão mais clara da situação média de classificação rápida, é necessário fazer uma suposição sobre a frequência de ocorrência de várias entradas.

Mas ao aplicar a classificação rápida a uma matriz de entrada aleatória, é impossível supor que cada camada tenha a mesma divisão e distribuição. Podemos apenas esperar que algumas divisões sejam mais equilibradas e algumas divisões sejam desequilibradas.

Suponha que partições boas e partições ruins apareçam alternadamente em cada nível da árvore:
podemos obter dois conjuntos de árvores recursivas e recursivas

L (n) = 2U (n / 2) + θ (n) -------
U sortudo (n) = L (n-1) + θ (n) ------- Azarado

Insira a descrição da imagem aqui
【Usar Método de Substituição e Teorema Principal]
Insira a descrição da imagem aqui
Como garantir que sempre tenhamos a complexidade assintoticamente ótima?

  • Embaralhe os elementos da matriz aleatoriamente
  • Escolha aleatoriamente dividir o pivô

Normalmente consideramos o último porque é mais conveniente e intuitivo de analisar.


3. Versão aleatória de classificação rápida

"Em muitos casos, podemos adicionar componentes de randomização a um algoritmo para que ele possa obter melhor desempenho médio para todas as entradas."
-A versão aleatória de classificação rápida é ideal para entradas grandes o suficiente.

[Amostragem aleatória]
Em vez de sempre usar A [r] como o pivô, um elemento é selecionado aleatoriamente da submatriz A [p ... r], ou seja: A [r] e A [p ... r ] são selecionados aleatoriamente Troque um elemento para fora e, em seguida, execute uma classificação rápida de acordo com a lógica anterior.

Na operação de randomização acima, amostramos aleatoriamente a partir do intervalo de p, ... r, de modo a garantir que entre os r-p + 1 elementos da submatriz, o elemento pivô x = A [r], etc. .Pegue qualquer um deles.

Como os elementos do pivô são selecionados aleatoriamente, esperamos que, em média, a divisão da matriz de entrada possa ser mais simétrica.

1. As vantagens da classificação rápida aleatória:

①O tempo de execução não depende da ordem da sequência de entrada.
② Não há necessidade de fazer suposições sobre a distribuição da sequência de entrada.
③ Não haverá nenhuma entrada específica que leve ao pior caso. O pior caso é determinado apenas por o gerador de números aleatórios.

2. Randomize o pseudo-código de classificação rápida
Insira a descrição da imagem aqui

Acho que você gosta

Origin blog.csdn.net/kodoshinichi/article/details/108975398
Recomendado
Clasificación