[Estrutura de dados] Oito algoritmos de classificação

Índice

1. Classificação por inserção direta

2. Tipo de colina

3. Classificação de seleção

4. Classificação da pilha

Cinco, tipo de bolha

Seis, classificação rápida

  1. Versão recursiva

     1.1 Método Hoare

     1.2 Método de escavação

     1.3 Método do ponteiro para frente e para trás

  2. Versão não recursiva

  3. Otimização da classificação rápida

     3.1 Pegue o meio de três números

     3.2 Otimização entre células

Sete, classificação por mesclagem

  1. Versão recursiva

  2. Versão não recursiva

8. Contagem e classificação

 


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:

  1. Hill sort é uma otimização da ordenação por inserção direta.
  2. 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.
  3. 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 O(n^{2}) O(1) estabilizar
tipo de seleção O(n^{2}) O(1) instável
tipo de inserção O(n^{2}) O(1) estabilizar
Tipo de colina O(n^{1.3}) O(1) instável
tipo de pilha O(N*logN) O(1) instável
classificação de mesclagem O(N*logN) SOBRE) estabilizar
ordenação rápida O(N*logN) O(logN) instável
tipo de contagem SOBRE) O(Máx-Mín+1) instável

 


 

Se houver deficiências neste artigo, sinta-se à vontade para comentar abaixo e corrigirei o mais rápido possível.

 

 Ferros velhos, lembre-se de curtir e prestar atenção!!!  

Acho que você gosta

Origin blog.csdn.net/m0_63198468/article/details/131147889
Recomendado
Clasificación