[Lenguaje C] Clasificación rápida


Insertar descripción de la imagen aquí

1. versión ronca

La clasificación rápida es un método de clasificación de intercambio de estructura de árbol binario propuesto por Hoare en 1962. Su idea básica es:Tome cualquier elemento de la secuencia de elementos que se ordenarán como valor de referencia y divida el conjunto que se ordenará en dos subsecuencias de acuerdo con el código de clasificación. Todos los elementos de la subsecuencia izquierda son menores que el valor de referencia y todos los elementos de la La subsecuencia derecha es mayor que el valor de referencia. Luego, el proceso se repite para las subsecuencias izquierda y derecha hasta que todos los elementos estén dispuestos en las posiciones correspondientes.

Idea algorítmica:

  1. Defina una clavei y almacene un número aleatorio. El subíndice de la clave se cambia al primer elemento de la matriz. Aquí, la clave se establece directamente de forma predeterminada en el primer elemento de la matriz.
  2. Defina una izquierda y una derecha, y almacene los subíndices del primer y último elemento de la matriz respectivamente para movimiento e intercambio.
  3. En orden ascendente, dejamos que el lado derecho se mueva primero hacia la izquierda, si se encuentra un elemento menor que el valor de la clave, se detendrá y se moverá hacia la izquierda.
  4. left se mueve hacia la derecha y se detiene cuando encuentra un elemento mayor que el valor clave.
  5. Intercambiar elementos con subíndices izquierdo y derecho
  6. Repita las operaciones anteriores hasta que la izquierda y la derecha se encuentren (iguales)
  7. Intercambiar clave y elemento con subíndice a la izquierda
  8. En este momento, el lado izquierdo de la clave es un número menor que él y el lado derecho es un número mayor que él.
  9. Luego, realice la clasificación de un solo paso anterior en las secuencias izquierda y derecha respectivamente, y repita la operación hasta que las secuencias izquierda y derecha tengan solo uno o ningún elemento, detenga la operación y se pueda ordenar la secuencia.

Diagrama de clasificación de un solo paso de la versión Hoare:

Insertar descripción de la imagen aquí

código de versión hoare:

//交换
void Swap(int* a, int* b)
{
    
    
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//hoare版本
void QuickSort1(int* a, int begin, int end)
{
    
    
	//递归结束条件
	if (begin >= end)
	{
    
    
		return;
	}
	int keyi = begin;
	int left = begin;
	int right = end;
	//每趟排序直到左右相遇
	while (left < right)
	{
    
    
		//右边先走,找到比key值小的
		while (left < right && a[right] >= a[keyi])
		{
    
    
			right--;
		}
		//right找到比key值小的之后换到left走,找到比key值大的
		while (left < right && a[left] <= a[keyi])
		{
    
    
			left++;
		}
		//交换
		Swap(&a[left], &a[right]);
	}
	//将key值换到中间
	Swap(&a[keyi], &a[left]);
	//更新key
	keyi = left;
	//对左右序列继续排序
	QuickSort1(a, begin, keyi - 1);
	QuickSort1(a, keyi + 1, end);
}

Diagrama de flujo general:

Insertar descripción de la imagen aquí

2. Método de excavación

La idea de cavar hoyos:

  1. Primero almacene los primeros datos en la clave variable, utilícela como la posición inicial del pozo y regístrela con el subíndice agujero.
  2. Luego, la derecha comienza a avanzar, se detiene después de encontrar un elemento más pequeño que el valor clave y coloca el elemento en el hoyo (el subíndice es hoyo), y luego este lugar se convierte en un hoyo, y el hoyo se vuelve a la derecha en este momento.
  3. Luego, la izquierda comienza a moverse hacia atrás, se detiene después de encontrar un elemento mayor que el valor clave y coloca este elemento en el hoyo (el subíndice es hoyo), y luego el lugar se convierte en un hoyo, y el hoyo se convierte en la izquierda en este momento.
  4. Luego vuelva al movimiento de la derecha y repita hasta que la izquierda y la derecha se encuentren (el lugar donde se encuentran la izquierda y la derecha debe ser un hoyo).
  5. Luego coloque la llave en la posición donde se encuentran la izquierda y la derecha, que es la posición del hoyo. En este momento, el lado izquierdo del agujero es menor o igual que él y el lado derecho es mayor o igual que él. .
  6. De esta manera, la clasificación de un solo paso finaliza y luego continúa realizando las operaciones anteriores repetidamente en las secuencias izquierda y derecha del hoyo. La operación se detiene cuando las secuencias izquierda y derecha tienen solo uno o ningún elemento, y la secuencia puede ser ordenado.

Ilustración de la clasificación de un solo viaje del método de excavación de pozos:

Insertar descripción de la imagen aquí

Código del método de excavación:

//挖坑法
void QuickSort2(int* a, int begin, int end)
{
    
    
	//递归结束条件
	if (begin >= end)
	{
    
    
		return;
	}
	int left = begin;
	int right = end;
	int key = a[left];
	//坑最初与left一样在开始位置
	int hole = left;
	//每趟排序直到左右相遇
	while (left < right)
	{
    
    
		//右边先走,找到比key值小的
		while (left < right && a[right] >= key)
		{
    
    
			right--;
		}
		//将right找到的比key小的元素放进坑中
		a[hole] = a[right];
		//更新坑的位置
		hole = right;

		//然后左边走找到比key值大的元素停下来
		while (left < right && a[left] <= key)
		{
    
    
			left++;
		}
		//将left找到的比key大的元素放进坑中
		a[hole] = a[left];
		//更新坑的位置
		hole = left;
	}
	//将key放入坑中
	a[hole] = key;
	//对左右序列继续排序
	QuickSort2(a, begin, hole - 1);
	QuickSort2(a, hole+1, end);
}

3. Método de puntero de ida y vuelta

La idea del método del puntero frontal y posterior:

  1. Defina una clavei y almacene un número aleatorio. El subíndice de la clave se cambia al primer elemento de la matriz. Aquí, la clave se establece directamente de forma predeterminada en el primer elemento de la matriz.
  2. Defina un prev como el subíndice del primer elemento y defina un cur como el subíndice del siguiente elemento en prev.
  3. El valor en el subíndice cur se compara con la clave y cur se detiene hasta que encuentra un valor menor que la clave.
  4. El subíndice anterior se mueve hacia atrás en uno y luego se intercambia con el valor en el subíndice cur, y luego cur se mueve hacia atrás en uno (anterior es equivalente al último subíndice de los números anteriores más pequeños que la clave, por lo que debe desplazarse uno y luego intercambiado)
  5. Cur continúa buscando un valor menor que key y lo ejecuta repetidamente hasta que el valor de cur sea mayor que n.
  6. Intercambie la clave con el valor en el subíndice anterior. En este momento, el lado izquierdo de la clave es menor o igual y el lado derecho de la clave es mayor o igual.
  7. De esta manera, la clasificación de un solo paso finaliza y luego continúa realizando las operaciones anteriores repetidamente en las secuencias izquierda y derecha de la clave. La operación se detiene cuando las secuencias izquierda y derecha tienen solo uno o ningún elemento, y la secuencia puede ser ordenado.

Ilustración de clasificación de una sola pasada mediante el método del puntero frontal y posterior:

Insertar descripción de la imagen aquí

Código del método de puntero antes y después:

//交换
void Swap(int* a, int* b)
{
    
    
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//前后指针
void QuickSort3(int* a, int begin, int end)
{
    
    
	//递归结束条件
	if (begin >= end)
	{
    
    
		return;
	}

	int keyi = begin;
	int prev = begin;
	int cur = begin + 1;
	//每趟排序直到cur下标大于end
	while (cur <= end)
	{
    
    
		//cur找比key小的值
		if (a[cur] < a[keyi] && ++prev != cur)
		{
    
    
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	//将key换到中间
	Swap(&a[keyi], &a[prev]);
	//更新key的下标
	keyi = prev;
	//对左右序列继续排序
	QuickSort3(a, begin, keyi - 1);
	QuickSort3(a, keyi + 1, end);
}

La clasificación rápida es una clasificación inestable. Su complejidad temporal es O(N*logN), pero en el peor de los casos puede alcanzar O(N 2 ), y su complejidad espacial es O(logN).

4. Clasificación rápida no recursiva

Los tres métodos anteriores son una clasificación rápida implementada de forma recursiva utilizando el método de divide y vencerás. De hecho, la clasificación rápida también se puede implementar de forma no recursiva y la clasificación rápida no recursiva debe implementarse mediante una pila.

Idea:

Almacene el primer y último subíndice de la matriz en la pila, sáquelos como izquierdo y derecho en el bucle para ordenar la matriz y luego realice la misma operación en las secuencias izquierda y derecha de la clave obtenida, donde está el lado izquierdo. izquierda a keyi-1, y el lado derecho es keyi+1 a derecha, el orden en que estos subíndices se insertan en la pila depende del orden en que se sacan. Por ejemplo, en el siguiente código, los subíndices de los elementos posteriores se sacan primero, por lo que al empujar hacia la pila, los elementos posteriores deben colocarse primero, porque la característica de la pila es que es el primero en entrar, el último en salir.

Insertar descripción de la imagen aquí

Código de clasificación rápida no recursiva:

(La pila utilizada en este código debe implementarla usted mismo. Para la implementación de la pila en lenguaje C, consulte: Implementación de la pila )

//非递归快速排序
void QuickSortNonR(int* a, int begin, int end)
{
    
    
	//创建一个栈
	ST st;
	//初始化栈
	STInit(&st);
	//插入尾元素下标
	STPush(&st, end);
	//插入首元素下标
	STPush(&st, begin);
	//栈为空停下
	while (!STEmpty(&st))
	{
    
    
		//取出栈顶元素作为left
		int left = STTop(&st);
		//取出后在栈中删除
		STPop(&st);

		//取出栈顶元素作为right
		int right = STTop(&st);
		//取出后在栈中删除
		STPop(&st);

		int keyi = begin;
		//每趟排序直到左右相遇
		while (left < right)
		{
    
    
			//右边先走,找到比key值小的
			while (left < right && a[right] >= a[keyi])
			{
    
    
				right--;
			}
			//right找到比key值小的之后换到left走,找到比key值大的
			while (left < right && a[left] <= a[keyi])
			{
    
    
				left++;
			}
			//交换
			Swap(&a[left], &a[right]);
		}
		//将key值换到中间
		Swap(&a[keyi], &a[left]);
		//更新key的下标
		keyi = left;
		// 当前数组下标样子  [left,keyi-1] keyi [keyi+1, right]

		//右边还有元素,按顺序插入right和keyi+1
		if (keyi + 1 < right)
		{
    
    
			STPush(&st, right);
			STPush(&st, keyi + 1);
		}
		//左边还有元素,按顺序插入keyi-1和left
		if (left < keyi - 1)
		{
    
    
			STPush(&st, keyi - 1);
			STPush(&st, left);
		}
	}

	STDestroy(&st);
}

5. Optimización de clasificación rápida

1. Seleccione el valor clave entre tres números.

Los primeros tres métodos de clasificación rápida tienen que seleccionar aleatoriamente un valor como clave al principio. Solíamos utilizar directamente de forma predeterminada el primer elemento de la matriz. Esto no es lo suficientemente aleatorio y es propenso a los peores escenarios, lo que complica su tiempo. cerca de O (N 2 ), por lo que podemos escribir una función para seleccionar esta clave para que sea más aleatoria en lugar de ser directamente el primer elemento.

Acierta el número correcto entre tres números:

Seleccione el número en el medio de las tres posiciones al frente, último y medio de una matriz

// 三数取中
int GetMidi(int* a, int left, int right)
{
    
    
	int mid = (left + right) / 2;
	if (a[left] > a[right])
	{
    
    
		if (a[right] > a[mid])
		{
    
    
			return right;
		}
		else if(a[mid]>a[right]&&a[mid]<a[left])
		{
    
    
			return mid;
		}
		else
		{
    
    
			return left;
		}
	}
	else
	{
    
    
		if (a[left] > a[mid])
		{
    
    
			return left;
		}
		else if (a[mid] > a[left] && a[mid] < a[right])
		{
    
    
			return mid;
		}
		else
		{
    
    
			return right;
		}
	}
}

Durante la clasificación rápida, utilice el método del medio de tres para seleccionar el valor clave y luego reemplácelo al comienzo de la matriz, lo que puede evitar efectivamente el peor de los casos y mejorar en gran medida la eficiencia del algoritmo.

2. Optimización entre celdas

Cuando los datos recursivos son pequeños, se puede utilizar la ordenación por inserción para evitar que las celdas se dividan de forma recursiva y reducir el número de recursiones.

6. Prueba de código

//打印数组
void PrintArray(int* a, int n)
{
    
    
	for (int i = 0; i < n; i++)
	{
    
    
		printf("%d ", a[i]);
	}
	printf("\n");
}

void TestQuickSort1()
{
    
    
	int a[] = {
    
     9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
	QuickSort1(a, 0, sizeof(a) / sizeof(int) - 1);
	printf("hoare版本快速排序:\n");
	PrintArray(a, sizeof(a) / sizeof(int));
}
void TestQuickSort2()
{
    
    
	int a[] = {
    
     9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
	QuickSort2(a, 0, sizeof(a) / sizeof(int) - 1);
	printf("挖坑法快速排序:\n");
	PrintArray(a, sizeof(a) / sizeof(int));
}
void TestQuickSort3()
{
    
    
	int a[] = {
    
     9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
	QuickSort3(a, 0, sizeof(a) / sizeof(int) - 1);
	printf("前后指针法快速排序:\n");
	PrintArray(a, sizeof(a) / sizeof(int));
}
int main()
{
    
    
	TestQuickSort1();
	TestQuickSort2();
	TestQuickSort3();
	return 0;
}

Insertar descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/zcxyywd/article/details/133270717
Recomendado
Clasificación