[Estructura de datos] Ocho algoritmos de clasificación

Tabla de contenido

1. Clasificación por inserción directa

2. Clasificación de colinas

3. Clasificación de selección

4. Ordenar montones

Cinco, tipo burbuja

Seis, clasificación rápida

  1. Versión recursiva

     1.1 Método Hoare

     1.2 Método de excavación

     1.3 Método de puntero de ida y vuelta

  2. Versión no recursiva

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

     3.1 Tomar el medio de tres números

     3.2 Optimización entre celdas

Siete, ordenar por fusión

  1. Versión recursiva

  2. Versión no recursiva

8. Contar y clasificar

 


1. Clasificación por inserción directa

Demostración de animación:

Ideas: (1) Primero escriba de acuerdo con un tipo de inserción, el último elemento de la matriz es el final, los datos que se insertarán son datos temporales tmp = a[end+1], si tmp es menor que los datos en la posición final , la posición final será reemplazada Los datos se mueven de nuevo a la posición de final+1 hasta que se insertan en ella; (2) y luego se insertan de acuerdo con el orden de inserción general, asumiendo que solo hay un dato en la matriz para la primera vez, por lo que la posición final es 0 y la condición final es menor que n -1; de lo contrario, está fuera de los límites.

Nota: primero guarde los datos al final + 1, de lo contrario se sobrescribirá cuando se mueva.

 

Dos demostraciones de animación se dan arriba para una mejor comprensión.

//插入排序    时间复杂度: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. Clasificación de colinas

La clasificación por colinas puede reducir la clasificación incremental, también conocida como método incremental de reducción. La idea básica del método de clasificación de Hill es: primero seleccione un número entero, divida todos los registros en el archivo para clasificarlos en varios grupos, clasifique todos los registros con una distancia de 0 en el mismo grupo y clasifique los registros en cada uno. grupo. Luego, tome y repita el trabajo de agrupación y clasificación anterior. Cuando se alcanza gap=1, todos los registros se ordenan en el mismo grupo. 

Resumen de las características de Hill sorting:

  1. Hill sort es una optimización de la ordenación por inserción directa.
  2. Cuando gap > 1, se ordena previamente, el propósito es hacer que la matriz esté más cerca del orden. Cuando gap == 1, la matriz ya está cerca del orden, por lo que será muy rápido. De esta manera, se puede lograr el efecto de optimización general. Después de implementarlo, podemos comparar las pruebas de rendimiento.
  3. La complejidad temporal de la clasificación por colinas no es fácil de calcular, porque hay muchas formas de valorar la brecha, lo que dificulta su cálculo. Por lo tanto, la complejidad temporal de la clasificación por colinas dada en muchos árboles no es fija.
//希尔排序      时间复杂度: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. Clasificación de selección

Demostración de animación:

Idea: establezca dos variables mini y maxi para almacenar el subíndice de la posición mínima de datos y el subíndice de los datos de posición máxima respectivamente, marque el subíndice de los datos máximos y el subíndice de los datos de posición mínima cada vez que se recorre la matriz, y compararlos con los últimos datos y se intercambian datos de cabecera.

//选择排序     时间复杂度: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 situación es un caso especial. La posición del valor máximo (maxi) es en begin. Después de intercambiar a[begin] y a[mini], en este momento, la posición de maxi se intercambia a la posición de mini, así que la posición de maxi Algo ha cambiado y tenemos que ocuparnos de la posición de maxi.

4. Ordenar montones

Heap sort hace referencia a un algoritmo de clasificación diseñado utilizando la estructura de datos de un árbol apilado (heap), que es un tipo de clasificación por selección. Selecciona datos a través del montón. Cabe señalar que debe crear un montón grande para el orden ascendente y un montón pequeño para el orden descendente . Este artículo construye una gran pila, ordenada en orden ascendente.

//堆排序     时间复杂度: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 burbuja

Demostración de animación:

Idea: compare los datos antes y después, e intercambie si los datos en el frente son mayores que los datos en la parte posterior, de modo que después de la primera ronda de clasificación, el valor máximo se cambia a la última posición y luego la segunda ronda se realiza la ordenación, etc.. Los datos grandes se colocan en la penúltima posición hasta que se ordenan todos los datos.

//冒泡排序    时间复杂度: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, clasificación rápida

La idea básica es: cualquier elemento en la secuencia de elementos a ordenar se toma como valor de referencia, y el conjunto a ordenar se divide en dos subsecuencias de acuerdo con el código de clasificación. Todos los elementos en la subsecuencia izquierda son menores que el valor de referencia , y todos los elementos en la subsecuencia derecha son mayores que el valor de referencia, y luego la subsecuencia más a la izquierda repite el proceso hasta que todos los elementos estén dispuestos en las posiciones correspondientes.

Las características del resultado por fases de la clasificación rápida: cuando se completa la i-ésima pasada, aparecerán más de i datos en su posición final.

  1. Versión recursiva

     1.1 Método Hoare

 Demostración de animación:

Ideas: (1) Seleccione la posición clave, generalmente la primera posición a la izquierda (o la última posición a la derecha).

            (2) Si la llave se selecciona en la posición izquierda, la derecha irá primero y se detendrá cuando se encuentren datos más pequeños que la posición de la llave.

            (3) Vaya a la izquierda y siga, encuentre los datos más grandes que la posición clave y deténgase.

            (4) Intercambie los datos de las posiciones izquierda y derecha y repita las operaciones anteriores hasta que la izquierda y la derecha se encuentren.

            (5) Una vez finalizada la reunión, intercambie los datos de la posición clave y la posición izquierda (es decir, la posición de la reunión). En este momento, los datos a la izquierda de la posición de la reunión son menores o iguales a los datos de la posición de la reunión, y los datos de la derecha son mayores o iguales que los datos de la posición de la reunión. Entonces, estos datos se ajustan a su posición correcta.

//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 excavación

 Demostración de animación:

Ideas: (1) Asigne los datos en la posición izquierda de la matriz a la clave, formando el primer orificio, que es la posición izquierda, y guarde el valor en la posición clave.

           (2) La derecha va primero, encuentra un valor más pequeño que los datos de la posición clave, se detiene y coloca los datos aquí en el agujero, y la posición derecha forma un nuevo agujero.

           (3) Luego, vaya a la izquierda nuevamente, encuentre un valor mayor que los datos de posición clave, deténgase, coloque los datos aquí en el orificio y, en este momento, la posición izquierda forma un nuevo orificio.

           (4) Cuando la izquierda y la derecha se encuentren, coloque los datos en la posición clave en el foso y, en este momento, los datos clave se colocan en la posición correcta.

//挖坑法
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 de puntero de ida y vuelta

 Demostración de animación:

Ideas: (1) Primero asuma que la clave es la posición inicial de la matriz, y luego use el método de puntero hacia adelante y hacia atrás, prev apunta al primer elemento y cur apunta al segundo elemento.

           (2) cur se mueve primero y se detiene cuando encuentra que los datos de posición son más pequeños que la tecla.

           (3)++prev, intercambia los datos de la posición anterior y actual.

           (4) Cuando cur apunta a la posición próxima a la última posición de la matriz, el bucle se detiene.

           (5) Intercambiar los datos del subíndice clave y el subíndice 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. Versión no recursiva

Idea: si se implementa de forma no recursiva, necesitamos usar la estructura de memoria de la pila para permitir que el primero entre y luego salga, por lo que primero debemos comenzar y luego ingresar fin.

//非递归版本
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. Optimización de clasificación rápida

     3.1 Tomar el medio de tres números

Tomar el medio de tres números es un algoritmo de optimización. Para evitar que los datos en la posición clave sean el valor mínimo en la matriz, después de una ordenación rápida, no hay cambios. Usamos el método del medio de tres números para seleccionar en una matriz Se utiliza un valor intermedio como clave para realizar una clasificación rápida, lo que mejorará en gran medida la eficiencia.

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 Optimización entre celdas

La idea de la clasificación rápida es dividir continuamente pequeñas secuencias y luego realizarlas recursivamente. El número de recursiones en cada capa aumenta 2 veces. Es bueno implementar recursivamente cuando hay muchos elementos, pero cuando hay pocos elementos de secuencia, no es necesario usar la recursividad.Podemos optar por usar otros métodos de clasificación para realizar la clasificación de secuencias pequeñas.

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);
}

Siete, ordenar por fusión

  1. Versión recursiva

 Demostración de animación:

Pensamientos combinados:

        Merge sort es  un algoritmo de clasificación eficaz basado en la operación de combinación , que es una aplicación muy típica del método divide y vencerás.
        Combine las subsecuencias ordenadas para obtener una secuencia completamente ordenada;
        es decir, primero haga cada subsecuencia en orden y luego haga los segmentos de subsecuencia en orden. La combinación de dos listas ordenadas en una lista ordenada se denomina combinación bidireccional.

//归并排序    时间复杂度: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. Versión no recursiva

 Podemos fusionar controlando cada intervalo, porque la fusión es dicotómica.

rangeN significa: el número de datos combinados en 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: Al escribir código, debe controlar estrictamente el intervalo; de lo contrario, se producirá un desbordamiento y el programa fallará.

Existen las tres situaciones siguientes: (1) final1 fuera de los límites, comienzo2 fuera de los límites, final2 fuera de los límites; (2) inicio2 fuera de los límites, final2 fuera de los límites; (3) final2 fuera de los límites.

8. Contar y clasificar

La idea central de contar y ordenar: de hecho, es un mapeo. Los datos que se ordenarán se asignan a la posición correspondiente en el espacio auxiliar, luego se cuenta el número de ocurrencias y finalmente se vuelven a colocar en la 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);
}

Nueve, clasificación de comparación:

método de clasificación complejidad del tiempo complejidad del espacio estabilidad
Ordenamiento de burbuja O(n^{2}) O(1) estabilizar
clasificación de selección O(n^{2}) O(1) inestable
tipo de inserción O(n^{2}) O(1) estabilizar
clasificación de colinas O(n^{1.3}) O(1) inestable
ordenar en montón O(N*registroN) O(1) inestable
ordenar por fusión O(N*registroN) EN) estabilizar
ordenación rápida O(N*registroN) O(registroN) inestable
tipo de conteo EN) O(Máx.-Mín.+1) inestable

 


 

Si hay deficiencias en este artículo, puede comentar a continuación y lo corregiré lo antes posible.

 

 Hierros viejos, recuerden darle like y atentos!!!  

Supongo que te gusta

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