[Oito algoritmos de classificação clássicos] Classificação rápida


Insira a descrição da imagem aqui


I. Visão geral

Falando em tipo rápido, temos que mencionar seu fundador Hoare. Na década de 1950, os cientistas da computação começaram a estudar como classificar os dados para tornar os programas de computador mais eficientes. Naquela época, os algoritmos de classificação comumente usados ​​incluíam classificação por bolha, classificação por inserção e classificação por seleção.

No entanto, estes algoritmos são relativamente ineficientes, especialmente quando processam grandes quantidades de dados. Como resultado, as pessoas começaram a procurar algoritmos de classificação mais rápidos. Em sua pesquisa, Tony Hoare descobriu um método de classificação baseado na ideia de dividir para conquistar, ou seja, classificação rápida.

2. Implementação de ideias

A ideia da classificação rápida é pegar qualquer elemento na sequência de elementos a serem classificados como valor de referência e dividir o conjunto a ser classificado 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 menores que o valor de referência.

código mostrado abaixo:

// 假设按照升序对array数组中[left, right]区间中的元素进行排序
void QuickSort(int* a, int begin, int end)
{
    
    
	if (begin >= end)
		return;
	// 按照基准值对数组的 [left, right)区间中的元素进行划分
	int keyi = PartSort(a, begin, end);
	//分成[begin,keyi-1] keyi [keyi+1,end]
	
	 // 递归排[left, div)
	QuickSort(a, begin, keyi - 1);
	 // 递归排[div+1, right)
	QuickSort(a, keyi + 1, end);
}

A descrição acima é a estrutura principal da implementação recursiva da classificação rápida. Verifica-se que ela é muito semelhante à regra de passagem de pré-ordem da árvore binária. Em seguida, só precisamos analisar como dividir os dados no intervalo de acordo com ao valor de referência.
Quando o conjunto classificado é dividido, existem três maneiras comuns de dividir o intervalo nas metades esquerda e direita de acordo com o valor de referência.

Versão 2.1 hoare

Idéia :

  1. Selecione um elemento base (chave), que pode ser o elemento mais à esquerda ou mais à direita.
  2. Defina dois ponteiros, um apontando para o primeiro elemento do array (ponteiro esquerdo) e outro apontando para o último elemento do array (ponteiro direito). ( Deve-se notar que se você selecionar os dados mais à esquerda como chave, você precisará ir primeiro para a direita; se você selecionar os dados mais à direita como chave, você precisará ir primeiro para a esquerda )
  3. Mova o ponteiro esquerdo até encontrar um elemento maior ou igual ao elemento base (chave); mova o ponteiro direito até encontrar um elemento menor ou igual ao elemento base (chave). Em seguida, troque os elementos apontados pelos ponteiros esquerdo e direito. Em seguida, continue repetindo as etapas acima até que o ponteiro esquerdo seja maior que o ponteiro direito
  4. Finalmente, o elemento base é trocado pelo elemento apontado pelo ponteiro direito. Neste momento, o elemento base está na posição correta. Neste momento, o elemento esquerdo >= chave e o elemento direito <= chave.

Dicas: O blogueiro explica aqui por que " se você selecionar os dados mais à esquerda como chave, precisará ir primeiro para a direita; se selecionar os dados mais à direita como chave, precisará ir primeiro para a esquerda. " O mesmo se aplica a outros métodos subsequentes . .
①: Faça a chave à esquerda e vá primeiro à direita, garantindo que o valor da posição do encontro seja menor que a chave ou a posição da chave.
②: Faça a chave à direita e mova primeiro à esquerda, garantindo que o valor da posição do encontro seja maior que a chave ou a posição da chave.

Tomando ① como exemplo, existem apenas duas situações em que L e R se encontram: L encontra R e R encontra L.
Situação 1: L encontra R. Depois que R parou, L ainda estava andando. Como R vai primeiro, a posição onde R para deve ser menor que Key. A posição de encontro é a posição onde R para, que deve ser menor que a chave.
Situação 2: R encontra L. Na próxima rodada do encontro, L está parado e R está em movimento. A posição de encontro é a posição de L. A posição de L é a posição da chave ou foi trocada por algumas rodadas, neste momento a posição de encontro deve ser menor que a chave.

[Demonstração de Animação]:
Insira a descrição da imagem aqui
O código é o seguinte:

 //[left, right]--采用左闭右闭
int PartSort(int* a, int left, int right)
{
    
    
	int keyi = left;
	while (left < right)
	{
    
    
		//找到右边比key小的数
		while (left < right && a[right] <= a[keyi])
		{
    
    
			right--;
		}

		//找到左边比key大的数
		while (left < right && a[left] >= a[keyi])
		{
    
    
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[keyi], &a[left]);
	return left;
}

void Swap(int* p1, int* p2)
{
    
    
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

2.2 Método de escavação

Idéia :

  1. .Selecione um dado (geralmente o mais à esquerda ou à direita) e armazene-o na variável chave e, ao mesmo tempo, a posição dos dados forma um poço.
  2. Ou os ponteiros esquerdo e direito, esquerda e direita, a esquerda vai da esquerda para a direita e a direita vai da direita para a esquerda. (Se você cavar um buraco na extrema esquerda, você precisa de R para ir primeiro; se você cavar um buraco na extrema direita, você precisa de L para ir primeiro)
  3. Mova o ponteiro direito para encontrar o primeiro número menor que a chave e preencha-o no buraco. Neste momento, a posição do ponteiro direito se torna um novo buraco. Em seguida, mova o ponteiro esquerdo para encontrar o primeiro número maior que a chave e preencha-o no poço. Neste momento, a posição do ponteiro esquerdo torna-se um novo poço. Em seguida, continue repetindo as etapas acima, como na versão hoare.

[Demonstração de Animação]:
Insira a descrição da imagem aqui
O código é o seguinte:

//挖坑法
int PartSort(int* a, int left, int right)
{
    
    
	int key = a[left];
	int hole = left;
	while (left < right)
	{
    
    
		//找到右边比key小的值
		while (left < right && a[right] >= key)
		{
    
    
			right--;
		}
		a[hole] = a[right];
		hole = right;

		//左边比key大的值
		while (left < right && a[left] <= key)
		{
    
    
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;
	return hole;
}

void Swap(int* p1, int* p2)
{
    
    
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

2.3 Antes e depois da versão do ponteiro

Idéia :

  1. Selecione uma tecla, geralmente a mais à esquerda ou à direita.
  2. No início, o ponteiro prev aponta para o início da sequência e o ponteiro cur aponta para prev+1.
  3. Se o conteúdo apontado por cur for menor que key, então prev primeiro retrocede um bit, depois troca o conteúdo apontado pelos ponteiros prev e cur, e então o ponteiro cur ++; se o conteúdo apontado por cur for maior que key, então o ponteiro cur diretamente ++. Continue desta forma até que cur atinja a posição final, momento em que a chave e o conteúdo apontado pelo ponteiro ++prev podem ser trocados.

[Demonstração de Animação]:
Insira a descrição da imagem aqui
O código é o seguinte:

//前后指针法
int PartSort(int* a, int left, int right)
{
    
    
	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
    
    
		if (a[cur] < a[keyi] && ++prev != cur)
		{
    
    
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	return keyi;
}

void Swap(int* p1, int* p2)
{
    
    
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

3. Otimização

Embora o problema possa ser resolvido, ainda há um problema:
quando a chave selecionada é sempre a mediana, a eficiência é a melhor e a complexidade do tempo é O(N*logN); mas quando a matriz é ordenada, ela se torna o mais eficiente Ruim, a complexidade do tempo torna-se O(N^2)!
Para a situação acima, existem dois métodos de otimização:

  1. Método de três números para selecionar a chave
  2. Ao recorrer a um pequeno subintervalo, você pode considerar o uso da classificação por inserção

3.1 Encontre o número certo entre três números

O blogueiro aqui fornece o método mais fácil:

int GetMidIndix(int* a, int left, int right)
{
    
    
	int mid = left + (right - left) / 2;
	if (a[left] < a[mid])
	{
    
    
		if (a[mid] < a[right])
			return mid;
		else if (a[mid] > a[right])
			return right;
		else
			return left;
	}
	else//a[left]>=a[mid]
	{
    
    
		if (a[mid] > a[right])
			return mid;
		else if (a[mid] < a[right])
			return right;
		else
			return left;
	}
}

3.1.1 Código final

void Swap(int* p1, int* p2)
{
    
    
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

int GetMidIndix(int* a, int left, int right)
{
    
    
	int mid = left + (right - left) / 2;
	if (a[left] < a[mid])
	{
    
    
		if (a[mid] < a[right])
			return mid;
		else if (a[mid] > a[right])
			return right;
		else
			return left;
	}
	else//a[left]>=a[mid]
	{
    
    
		if (a[mid] > a[right])
			return mid;
		else if (a[mid] < a[right])
			return right;
		else
			return left;
	}
}

// hoare
// [left, right]
//int PartSort(int* a, int left, int right)
//{
    
    
//	int midi = GetMidIndix(a, left, right);
//	Swap(&a[left], &a[midi]);
//
//	int keyi = left;
//	while (left < right)
//	{
    
    
//		//找到右边比key小的数
//		while (left < right && a[right] <= a[keyi])
//		{
    
    
//			right--;
//		}
//
//		//找到左边比key大的数
//		while (left < right && a[left] >= a[keyi])
//		{
    
    
//			left++;
//		}
//		Swap(&a[left], &a[right]);
//	}
//	Swap(&a[keyi], &a[left]);
//	return left;
//}


//挖坑法
//int PartSort(int* a, int left, int right)
//{
    
    
//	int midi = GetMidIndix(a, left, right);
//	Swap(&a[left], &a[midi]);
//
//	int key = a[left];
//	int hole = left;
//	while (left < right)
//	{
    
    
//		//找到右边比key小的值
//		while (left < right && a[right] >= key)
//		{
    
    
//			right--;
//		}
//		a[hole] = a[right];
//		hole = right;
//
//		//左边比key大的值
//		while (left < right && a[left] <= key)
//		{
    
    
//			left++;
//		}
//		a[hole] = a[left];
//		hole = left;
//	}
//	a[hole] = key;
//	return hole;
//}


//前后指针法
int PartSort(int* a, int left, int right)
{
    
    
	int midi = GetMidIndix(a, left, right);
	Swap(&a[left], &a[midi]);

	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
    
    
		if (a[cur] < a[keyi] && ++prev != cur)
		{
    
    
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	return keyi;
}

void QuickSort(int* a, int begin, int end)
{
    
    
	if (begin >= end)
		return;
	int keyi = PartSort(a, begin, end);
	//分成[begin,keyi-1] keyi [keyi+1,end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

3.1.2 Resumo dos recursos de classificação rápida

  1. Complexidade de tempo: O(N*logN)

Insira a descrição da imagem aqui

  1. Complexidade do espaço: O(logN)
  2. Estabilidade: Instável

4. Implementação não recursiva de classificação rápida

Ideia:

  1. Defina uma pilha e, em seguida, adicione o índice inicial e o índice final da matriz a ser classificada na pilha.
  2. Os elementos entre o índice inicial e o índice final da matriz são divididos em duas partes através dos três métodos de divisão do intervalo.A parte esquerda é menor ou igual ao elemento de referência e a parte direita é maior ou igual a o elemento de referência.
  3. Como na implementação não recursiva, mantemos os subscritos do array a serem classificados, retirando-os da pilha em pares, então o próximo passo é colocar o índice inicial e o índice final da parte esquerda na pilha se o comprimento da parte esquerda for maior que 1; se Se o comprimento da parte direita for maior que 1, o índice inicial e o índice final da parte direita serão colocados na pilha. Finalmente, esta operação é repetida até que a pilha esteja vazia.

código mostrado abaixo:

//前后指针法
int PartSort(int* a, int left, int right)
{
    
    
	int midi = GetMidIndix(a, left, right);
	Swap(&a[left], &a[midi]);

	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
    
    
		if (a[cur] < a[keyi] && ++prev != cur)
		{
    
    
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	return keyi;
}

//快排非递归
void QuickSortNonR(int* a, int begin, int end)
{
    
    
	ST st;
	STInit(&st);
	STPush(&st, end);
	STPush(&st, begin);

	while (!STEmpty(&st))
	{
    
    
		int left = STTop(&st);
		STPop(&st);

		int right = STTop(&st);
		STPop(&st);

		int keyi = PartSort(a, left, right);
		//[left,keyi-1] keyi [keyi+1,right]
		if (keyi + 1 < right)
		{
    
    
			STPush(&st, right);
			STPush(&st, keyi + 1);
		}

		if (keyi - 1 > left)
		{
    
    
			STPush(&st, keyi - 1);
			STPush(&st, left);
		}
	}

	STDestroy(&st);
}

Insira a descrição da imagem aqui
Insira a descrição da imagem aqui

Acho que você gosta

Origin blog.csdn.net/Zhenyu_Coder/article/details/132920906
Recomendado
Clasificación