Índice
1. Classificação por inserção direta
1.3 Método do ponteiro para frente e para trás
3. Otimização da classificação rápida
3.1 Pegue o meio de três números
Sete, classificação por mesclagem
1. Classificação por inserção direta
Demonstração de animação:
Ideias: (1) Primeiro escreva de acordo com uma classificação de inserção, o último elemento da matriz é final, os dados a serem inseridos são dados temporários tmp = a[end+1], se tmp for menor que os dados na posição final , a posição final será substituída Os dados são movidos de volta para a posição final+1 até serem inseridos nela; (2) e então inseridos de acordo com a classificação de inserção geral, assumindo que há apenas um dado na matriz para na primeira vez, então a posição final é 0 e a condição final é menor que n -1, caso contrário, fora dos limites.
Nota: salve os dados no final+1 primeiro, caso contrário, eles serão substituídos ao mover.
Duas demonstrações de animação são dadas acima para melhor compreensão.
//插入排序 时间复杂度:O(N^2)
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;
int tmp = a[end + 1];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
2. Tipo de colina
A classificação em colina pode reduzir a classificação incremental, também conhecida como método incremental de redução. A ideia básica do método de classificação de Hill é: primeiro selecione um número inteiro, divida todos os registros no arquivo a serem classificados em vários grupos e classifique todos os registros com distância 0 no mesmo grupo e classifique os registros em cada grupo. Em seguida, pegue e repita o trabalho de agrupamento e classificação acima. Quando gap=1 é atingido, todos os registros são classificados no mesmo grupo.
Resumo das características do Hill sorting:
- Hill sort é uma otimização da ordenação por inserção direta.
- Quando gap > 1, ele é pré-ordenado, o objetivo é deixar o array mais próximo da ordem. Quando gap == 1, o array já está próximo da ordem, então será muito rápido. Desta forma, o efeito de otimização geral pode ser alcançado. Depois de implementá-lo, podemos comparar os testes de desempenho.
- A complexidade temporal do Hill sorting não é fácil de calcular, pois existem muitas formas de valorar o gap, o que dificulta o cálculo, portanto, a complexidade temporal do Hill sorting dada em muitas árvores não é fixa.
//希尔排序 时间复杂度:O(N^1.3)
void ShellSort(int* a, int n)
{
//gap > 1 预排序
//gap越大,大的数可以更快的到后面,小的数可以更快的到前面。越不接近有序。
//gap越小,数据跳动越慢,越接近有序。
//gap == 1 直接插入排序
int gap = n;
while (gap > 1)
{
gap = gap / 2;
//gap = gap / 3 + 1;
for (int j = 0; j < gap; j++)//分gap组排序
{
for (int i = j; i < n - gap; i += gap)//每一个组进行插入排序
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
}
3. Classificação de seleção
Demonstração de animação:
Ideia: Defina duas variáveis mini e maxi para armazenar o subscrito da posição mínima dos dados e o subscrito dos dados da posição máxima, respectivamente, marque o subscrito dos dados máximos e o subscrito dos dados da posição mínima cada vez que a matriz é percorrida e compare-os com os últimos Dados e dados de cabeçalho são trocados.
//选择排序 时间复杂度:O(N^2)
//和直接插入排序相比,插入排序更好
//插入适应性很强,对于有序,局部有序,都能效率提升
void SelectSort(int* a, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int mini = begin, maxi = begin;
for (int i = begin + 1; i <= end; i++)
{
if (a[i] < a[mini])
{
mini = i;
}
if (a[i] > a[maxi])
{
maxi = i;
}
}
//此时,已经选出了最大的,和最小的
//位置交换
Swap(&a[begin], &a[mini]);
//begin跟maxi重叠了,第一步交换之后maxi位置变了
if (maxi == begin)
{
maxi = mini;
}
Swap(&a[end], &a[maxi]);
begin++;
end--;
}
Nota: Esta situação é um caso especial. A posição do valor máximo (maxi) está no início. Após a troca de a[begin] e a[mini], neste momento, a posição de maxi é trocada para a posição de mini, então a posição do maxi Algo mudou e precisamos lidar com a posição do maxi.
4. Classificação da pilha
Heap sort refere-se a um algoritmo de classificação projetado usando a estrutura de dados de uma árvore empilhada (heap), que é um tipo de classificação por seleção. Ele seleciona os dados por meio do heap. Deve-se notar que você precisa construir um heap grande para ordem crescente e um heap pequeno para ordem decrescente . Este artigo forma uma grande pilha, classificada em ordem crescente.
//堆排序 时间复杂度:O(N*logN)
//交换
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//向下调整
void AdjustDown(int* a, int n, int parent)//n为数组的大小
{
int child = parent * 2 + 1;//左孩子
while (child < n)
{
//确认child指向大的那个孩子
if (child + 1 < n && a[child + 1] > a[child])//child+1 < n 右孩子存在的情况
{
++child;//默认指向左孩子,++child就指向右孩子
}
//孩子大于父亲,交换,继续向下调整
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;//孩子小于父亲,跳出循环
}
}
}
void HeapSort(int* a, int n)
{
//向下调整建堆--- O(N) --- 好一点
//升序:建大堆
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
//O(N*logN)
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
Cinco, tipo de bolha
Demonstração de animação:
Ideia: compare os dados antes e depois, e troque se os dados da frente forem maiores que os dados de trás, para que após a primeira rodada de classificação, o valor máximo seja alterado para a última posição e depois a segunda rodada de classificação é executada e assim por diante. Dados grandes são colocados na penúltima posição até que todos os dados sejam classificados.
//冒泡排序 时间复杂度:O(N^2)
void BubbleSort(int* a, int n)
{
for (int j = 0; j < n; j++)
{
int exchange = 0;
for (int i = 1; i < n - j; i++)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
exchange = 1;
}
}
//一趟冒泡过程中,没有发生交换,说明已经有序了,不需要进行处理
if (exchange == 0)
{
break;
}
}
}
Seis, classificação rápida
A ideia básica é: qualquer elemento na sequência de elementos a ser classificado é considerado como valor de referência e o conjunto a ser classificado é dividido em duas subsequências de acordo com o código de classificação. Todos os elementos na subsequência à esquerda são menores que o valor de referência , e todos os elementos na subsequência à direita são maiores que o valor de referência e, em seguida, a subsequência mais à esquerda repete o processo até que todos os elementos estejam dispostos nas posições correspondentes.
As características do resultado faseado da classificação rápida: quando a i-ésima passagem for concluída, mais de i dados aparecerão em sua posição final.
1. Versão recursiva
1.1 Método Hoare
Demonstração de animação:
Idéias: (1) Selecione a posição chave, geralmente a primeira posição à esquerda (ou a última posição à direita).
(2) Se a chave for selecionada na posição esquerda, a direita irá primeiro e parará quando os dados menores que a posição da chave forem encontrados.
(3) Vá para a esquerda e vá, encontre os dados maiores que a posição da chave e pare.
(4) Troque os dados das posições esquerda e direita e repita as operações acima até que a esquerda e a direita se encontrem.
(5) Após o término da reunião, troque os dados da posição-chave e da posição esquerda (ou seja, a posição da reunião). Neste momento, os dados à esquerda da posição da reunião são menores ou iguais aos dados da posição da reunião, e os dados à direita são maiores ou iguais aos dados da posição da reunião. Assim, este dado é ajustado para sua posição correta.
//Hoare
int PartSort1(int* a, int begin, int end)
{
int left = begin, right = end;
int key = left;
while (left < right)
{
//右边先走,找小
while (left < right && a[right] >= a[key])//left < right条件是防止越界
{
right--;
}
//左边再走,找大
while (left < right && a[left] <= a[key])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[key]);
key = left;
return key;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
if ((end - begin + 1) < 15)
{
//优化方法:小区间用直接插入替代,减少递归调用次数
InsertSort(a + begin, end - begin + 1);
}
else
{
int key = PartSort1(a, begin, end);
//[begin,key-1] key [key+1 , end] 三段位置
QuickSort(a, begin, key - 1);
QuickSort(a, key + 1, end);
}
}
1.2 Método de escavação
Demonstração de animação:
Idéias: (1) Atribuir os dados na posição esquerda do array à chave, formando o primeiro buraco, que é a posição esquerda, e salvar o valor na posição chave.
(2) A direita vai primeiro, encontra um valor menor que os dados de posição chave, para e coloca os dados aqui no buraco, e a posição direita forma um novo buraco.
(3) Em seguida, vá para a esquerda novamente, encontre um valor maior que os dados da posição da chave, pare, coloque os dados aqui no buraco e, neste momento, a posição da esquerda forma um novo buraco.
(4) Quando esquerda e direita se encontram, coloque os dados na posição chave no poço e, neste momento, os dados chave são colocados na posição correta.
//挖坑法
int PartSort2(int* a, int begin, int end)
{
int left = begin, right = end;
int key = left;
int hole = left;
while (left < right)
{
//右边找小,填到左边的坑里面
if (left < right && a[right] >= a[key])
{
right--;
}
a[hole] = a[right];
hole = right;
//左边找大,填到右边的坑里面
if (left < right && a[left] <= a[key])
{
left++;
}
a[hole] = a[left];
hole = left;
}
a[hole] = a[key];
return hole;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
if ((end - begin + 1) < 15)
{
//优化方法:小区间用直接插入替代,减少递归调用次数
InsertSort(a + begin, end - begin + 1);
}
else
{
int key = PartSort2(a, begin, end);
//[begin,key-1] key [key+1 , end] 三段位置
QuickSort(a, begin, key - 1);
QuickSort(a, key + 1, end);
}
}
1.3 Método do ponteiro para frente e para trás
Demonstração de animação:
Idéias: (1) Primeiro suponha que a chave é a posição inicial da matriz e, em seguida, use o método de ponteiro para frente e para trás, prev aponta para o primeiro elemento e cur aponta para o segundo elemento.
(2) cur se move primeiro e para quando encontra os dados de posição menores que a chave.
(3)++prev, troca os dados das posições anterior e atual.
(4) Quando cur aponta para a posição próxima à última posição do array, o loop para.
(5) Troque os dados do subscrito chave e do subscrito anterior.
//前后指针法
int PartSort3(int* a, int begin, int end)
{
int prev = begin;
int cur = begin + 1;
int key = begin;
while (cur <= end)
{
//找到比key小的值时,跟++prev位置交换,小的往前翻,大的往后翻
while (a[cur] < a[key] && ++prev != cur)
{
//满足条件,进行交换
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[key], &a[prev]);
key = prev;
return key;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
if ((end - begin + 1) < 15)
{
//优化方法:小区间用直接插入替代,减少递归调用次数
InsertSort(a + begin, end - begin + 1);
}
else
{
int key = PartSort3(a, begin, end);
//[begin,key-1] key [key+1 , end] 三段位置
QuickSort(a, begin, key - 1);
QuickSort(a, key + 1, end);
}
}
2. Versão não recursiva
Ideia: Se for implementado de forma não recursiva, precisamos usar a estrutura de memória da pilha para deixar o primeiro entrar e depois sair, então precisamos começar primeiro e depois entrar fim.
//非递归版本
void QuickSortNonR(int* a, int begin, int end)
{
//借助栈实现非递归
ST st;
StackInit(&st);
StackPush(&st, begin);
StackPush(&st, end);
while (!StackEmpty(&st))
{
int right = StackTop(&st);
StackPop(&st);
int left = StackTop(&st);
StackPop(&st);
int key = PartSort1(a, left, right);
//[left,key-1] key [key+1,right]
if (key + 1 < right)
{
StackPush(&st, key + 1);
StackPush(&st, right);
}
if (left < key - 1)
{
StackPush(&st, left);
StackPush(&st, key - 1);
}
}
StackDestory(&st);
}
3. Otimização da classificação rápida
3.1 Pegue o meio de três números
Tomar o meio de três números é um algoritmo de otimização. Para evitar que os dados na posição chave sejam o valor mínimo na matriz, após uma classificação rápida, não há alteração. Usamos o método do meio de três números para selecionar em uma matriz Um valor intermediário é usado como uma chave para executar a classificação rápida, o que melhorará muito a eficiência.
int GetMidIndex(int* a, int begin, int end)
{
int mid = (begin + end) / 2;
if (a[begin] < a[mid])
{
if (a[mid] < a[end])
{
return mid;
}
else if (a[begin] > a[end])
{
return begin;
}
else
{
return end;
}
}
else //a[begin] > a[mid]
{
if (a[mid] > a[end])
{
return mid;
}
else if (a[begin] > a[end])
{
return end;
}
else
{
return begin;
}
}
}
3.2 Otimização entre células
A ideia da classificação rápida é dividir continuamente pequenas sequências e, em seguida, realizá-las recursivamente. O número de recursões em cada camada aumenta em 2 vezes. É bom implementar recursivamente quando há muitos elementos, mas quando há poucos elementos de sequência é desnecessário usar recursão.Podemos optar por usar outros métodos de ordenação para realizar a ordenação de pequenas sequências.
void QuickSort1(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
if ((end - begin + 1) < 10)
{
InsertSort(a, (end - begin + 1));
}
int mid = GetMidIndex(a, begin, end);
swep(&a[begin], &a[mid]);
int keyi = PartSort3(a, begin, end);
QuickSort1(a, begin, keyi - 1);
QuickSort1(a, keyi+1, end);
}
Sete, classificação por mesclagem
1. Versão recursiva
Demonstração de animação:
Pensamentos combinados:
Merge sort é um algoritmo de classificação eficaz baseado na operação de mesclagem , que é uma aplicação muito típica do método de divisão e conquista.
Combine as subsequências ordenadas para obter uma sequência completamente ordenada;
isto é, primeiro faça cada subsequência em ordem e depois faça os segmentos da subsequência em ordem. Mesclar duas listas classificadas em uma lista classificada é chamado de mesclagem bidirecional.
//归并排序 时间复杂度:O(N*logN) 空间复杂度:O(N)
void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin >= end)
{
return;
}
int mid = (begin + end) / 2;
//[begin,mid] [mid+1,end] 递归让子区间有序
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid + 1, end, tmp);
//归并
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
tmp = NULL;
}
2. Versão não recursiva
Podemos mesclar controlando cada intervalo, porque a mesclagem é dicotômica.
rangeN significa: o número de dados mesclados em cada grupo.
//非递归归并排序
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
//归并每组数据个数,从1开始,因为1个认为是有序的,可以直接归并
int rangeN = 1;
while (rangeN < n)
{
for (int i = 0; i < n; i += rangeN * 2)
{
//[begin1,end1] [begin2,end2] 归并
int begin1 = i, end1 = i + rangeN - 1;
int begin2 = i + rangeN, end2 = i + 2 * rangeN - 1;
int j = i;
//end1 begin2 end2越界
if (end1 >= n)
{
break;
}
else if (begin2 >= n)//begin2 end2 越界
{
break;
}
else if (end2 >= n)//end2 越界
{
//修正区间
end2 = n - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[j++] = a[begin1++];
}
else
{
tmp[j++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
//归并一部分,拷贝一部分
memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
}
rangeN *= 2;
}
free(tmp);
tmp = NULL;
}
Nota: Ao escrever o código, você precisa controlar estritamente o intervalo, caso contrário, ocorrerá um estouro e o programa travará.
Existem as três situações a seguir: (1) end1 fora dos limites, begin2 fora dos limites, end2 fora dos limites; (2) begin2 fora dos limites, end2 fora dos limites; (3) end2 fora dos limites
8. Contagem e classificação
A ideia central de contagem e classificação: na verdade, é um mapeamento. Os dados a serem classificados são mapeados para a posição correspondente no espaço auxiliar e, em seguida, o número de ocorrências é contado e, finalmente, colocado de volta na matriz original .
//计数排序
void CountSort(int* a, int n)
{
assert(a);
int min = a[0];
int max = a[0];
for (int i = 1; i < n; ++i)
{
//求最大值和最小值
if (a[i] < min)
min = a[i];
if (a[i] > max)
max = a[i];
}
int range = max - min + 1;
int* countArr = (int*)malloc(sizeof(int) * range);
memset(countArr, 0, sizeof(int) * range);
//统计次数
for (int i = 0; i < n; ++i)
{
countArr[a[i] - min]++;
}
//排序
int index = 0;
for (int j = 0; j < range; ++j)
{
while (countArr[j]--)
{
//相对位置
a[index++] = j + min;
}
}
free(countArr);
}
Nove, comparação de classificação:
método de classificação | complexidade de tempo | complexidade do espaço | estabilidade |
Tipo de bolha | estabilizar | ||
tipo de seleção | instável | ||
tipo de inserção | estabilizar | ||
Tipo de colina | instável | ||
tipo de pilha | instável | ||
classificação de mesclagem | estabilizar | ||
ordenação rápida | instável | ||
tipo de contagem | instável |
Se houver deficiências neste artigo, sinta-se à vontade para comentar abaixo e corrigirei o mais rápido possível.