Clasificación de series de clasificación por inserción de algoritmo y prueba de rendimiento

Primer contacto con la ordenación por inserción

La ordenación por inserción también es un algoritmo de ordenación relativamente simple. La idea es tomar el primer elemento de la matriz como una secuencia que ha sido ordenada, y luego tomar los elementos restantes de la matriz de izquierda a derecha, un elemento a la vez, e insertarlos en la secuencia ordenada de la izquierda Continuar para expandir la longitud de la secuencia ordenada a la izquierda hasta que se ordene toda la matriz.

El diagrama es el siguiente

Suponga que va a insertar una especie de la siguiente matriz

Primero agregue el primer elemento a la secuencia ordenada

Luego, tome el primer elemento de la parte sin clasificar e inserte el elemento en la posición apropiada en la secuencia ordenada a la izquierda.

El primer elemento de la parte sin clasificar es 3, y se encuentra que 3 debe insertarse antes que 9

Entonces los resultados de la primera ronda de inserción son los siguientes

Continúe con la siguiente ronda, tome el primer elemento de la parte sin clasificar , 6

Inserte 6 en la posición apropiada en la secuencia ordenada a la izquierda, es decir, entre 3 y 9

Continúe con la siguiente ronda, tome el primer elemento 1 de la parte sin clasificar de la matriz y encuentre que 1 debe insertarse a la izquierda de 3

Continuar con la siguiente ronda, se inserta 4 entre 3 y 6

En la siguiente ronda, se inserta 2 entre 1 y 3

… Finalmente, se ordena toda la matriz

Nota: Inserte un número en una posición apropiada en la secuencia ordenada. Este proceso se puede implementar de varias maneras. La más simple es utilizar un proceso similar al burbujeo, comparando e intercambiando constantemente dos números adyacentes, y siempre intercambiando los elementos a insertar en la posición adecuada. De acuerdo con esta idea, el código para la ordenación por inserción es el siguiente

public void insertSort(int[] array) {
    
    
		/*不断地将数字插入到合适的位置,扩大有序序列的边界 */
		for (int sortedSize = 1; sortedSize < array.length; sortedSize++) {
    
    
			for (int i = sortedSize - 1; i >= 0 ; i--) {
    
    
				if (array[i + 1] < array[i]) {
    
    
					swap(array, i + 1, i);
				} else {
    
    
					break;
				}
			}
		}
	}

Ideas de optimización

Idea uno

Cambiar comparación e intercambio a asignación unidireccional

El tipo de inserción anterior tiene espacio para la optimización. Porque en cada ronda de clasificación, en realidad solo necesita encontrar una posición adecuada. En la implementación anterior, el proceso de encontrar una posición de inserción adecuada se logra mediante el intercambio constante de elementos. Cada vez que se intercambian dos números, se requieren tres operaciones de asignación . Dicho de otro modo, podemos poner los elementos que se insertarán hasta el principio y luego insertarlos en los elementos y elementos en una secuencia ordenada de derecha a izquierda para comparar, siempre que la secuencia ordenada de elementos sea superior a los elementos que se van a insertar. insertado en el grande Si es así, mueva su posición en la secuencia ordenada hacia la derecha un bit, de modo que cada vez que el lugar que necesita intercambiarse se cambie a asignación unidireccional , solo se requiera una operación de asignación. Finalmente, después de encontrar una posición adecuada, asigne directamente el elemento almacenado temporalmente para ser insertado. El diagrama es el siguiente

Suponga que el estado de la matriz en un momento determinado durante el proceso de ordenación por inserción es el siguiente

El siguiente elemento que se insertará es 5, guárdelo temporalmente y luego, comenzando desde el elemento más a la derecha en la secuencia ordenada, compárelo con 5 a su vez, y encuentre que 9 es mayor que 5, luego asigne 9 unidireccionalmente a la derecha, sobrescribiendo it 5. Lógicamente hablando, la posición original del 9 es realmente inútil, porque el 9 se ha movido un lugar a la derecha y, dado que almacenamos temporalmente el 5, no hay necesidad de preocuparse por perderlo. Los elementos inútiles están marcados en violeta en la siguiente imagen

Luego vaya a la izquierda y observe el siguiente elemento en la secuencia ordenada, 8

Compare 8 y 5, encuentre que 8 es mayor que 5, luego 8 también se asigna a la derecha, cubriendo el 9 púrpura

Continuar hacia la izquierda

Comparando 2 y 5, se encuentra que 2 es menor que 5, lo que indica que se ha encontrado una posición de inserción adecuada, y el 5 previamente almacenado temporalmente se asigna a la posición del elemento púrpura en una dirección.

Esto completa esta ronda de ordenación por inserción

Lógicamente hablando, equivale a mover los elementos más grandes en la secuencia ordenada una posición a la derecha a su vez, dejando un espacio para que se inserte el elemento. Dado que es una asignación unidireccional, es menor que el intercambio de comparación anterior Se han realizado muchas operaciones de asignación, por lo que el rendimiento mejorará. Pero se necesita un espacio extra para almacenar temporalmente los elementos a insertar. Según esta idea, el código se escribe de la siguiente manera

	public void insertSortV1(int[] array) {
    
    
		for (int sortedSize = 1; sortedSize < array.length; sortedSize++) {
    
    
			int temp = array[sortedSize];
			int i = sortedSize;
			while (i > 0 && array[i - 1] > temp) {
    
    
				array[i] = array[i - 1];
				i--;
			}
			array[i] = temp;
		}
	}

Idea dos

Cambiar búsqueda lineal a búsqueda binaria

Debido a cada ronda de clasificación de inserción, la clave es encontrar la posición de inserción adecuada , que en realidad es un proceso de búsqueda. La implementación anterior utiliza una búsqueda lineal, es decir, desde el extremo derecho de la secuencia ordenada, y luego se compara con la izquierda hasta que se encuentra una posición adecuada. La complejidad temporal de esta búsqueda es lineal, es decir, O (n). Podemos optimizar este proceso de búsqueda reemplazando la búsqueda lineal con la búsqueda binaria . La búsqueda binaria es una búsqueda de salto. Cada vez que se toma la posición media de la secuencia a buscar y se compara con el valor objetivo. Si es menor que el valor objetivo, continúe en Buscar en la mitad derecha, si es mayor que el valor objetivo, continúe buscando en la mitad izquierda. La búsqueda binaria puede reducir el espacio de búsqueda a la mitad cada vez, y su complejidad de tiempo es O (log (n))

El código optimizado mediante la búsqueda binaria es el siguiente

	/**
	 * 二分查找
	 * @param left 左边界(inclusive)
	 * @param right 有边界(inclusive)
	 * */
	private int binarySearch(int[] array, int left, int right, int target) {
    
    
		while (left <= right) {
    
    
            /* 取中间位置 */
			int mid = (left + right) >> 1;
			/*
			* 临界情况有2种
			* 1. 待查找区间还剩2个数 ->  此时 right = left + 1 , mid = left
			*  1.1 若判断 arr[mid] > target, 则该查找左半部分,此时应该插入的位置是mid,也就是left
			*  1.2 若判断 arr[mid] < target, 则该查找右半部分,此时应该插入的位置是mid + 1,也就是更新后的left
			* 2. 待查找区间还剩3个数 -> 此时 right = left + 2 , mid = left + 1
			*  2.1 若判断 arr[mid] > target, 则查找左半部分,回到情况1
			*  2.2 若判断 arr[mid] < target,则查找右半部分,更新完后下一轮循环 left = right = mid,
			*      若arr[mid] > target,则应插入的位置是left,若arr[mid] < target,则更新完后的left是应插入的位置
			* */
			if (array[mid] > target) {
    
    
				/* 往左半边查找 */
				right = mid - 1;
			} else if (array[mid] < target) {
    
    
				/* 往右半边查找 */
				left = mid + 1;
			} else {
    
    
				/* 相等了,返回待插入位置为 mid + 1 */
				return mid + 1;
			}
		}
		return left;
	}

	@Override
	public void insertSortBinarySearch(int[] array) {
    
    
		for (int sortedSize = 1; sortedSize < array.length; sortedSize++) {
    
    
			int temp = array[sortedSize];
			/* 获得待插入的位置 */
			int insertPos = binarySearch(array, 0, sortedSize - 1, temp);
			/* 将待插入位置之后的有序序列,全部往后移一位 */
			for (int i = sortedSize; i > insertPos ; i--) {
    
    
				array[i] = array[i - 1];
			}
			array[insertPos] = temp;
		}
	}

Idea 3

Tipo de colina

La ordenación de Hill es una variante de la ordenación por inserción. Su idea central es introducir el concepto de tamaño de paso . La ordenación de Hill usa el tamaño de paso para dividir la matriz en muchas matrices pequeñas, usa la ordenación por inserción en cada matriz pequeña y luego reduce gradualmente el paso. Largo , el último paso se reduce a uno, y el orden Hill con un paso de uno es el orden de inserción simple anterior. En comparación con la ordenación por inserción simple, la ventaja de la ordenación Hill es que los elementos se pueden mover a través de varias posiciones, mientras que la ordenación por inserción simple original debe compararse uno por uno para encontrar una posición de inserción adecuada.

El diagrama de clasificación Hill es el siguiente, el tamaño del paso inicial generalmente se establece en la mitad de la longitud de la matriz

La matriz anterior se divide en 4 grupos de elementos según gap = 4. Los elementos cuyo intervalo de posición es igual a gap se dividen en el mismo grupo. El primer grupo es [1,5,9], (5 y 1 están separados por 4 posiciones, 9 y 5 están separados por 4 posiciones ...) están marcados en azul claro, y el segundo grupo es [4,7] , Está marcado como amarillo claro, el tercer grupo es [2,6], que está marcado como violeta claro, y el cuarto grupo es [8,3], que está marcado como naranja. Para cada grupo de elementos, se realiza una ordenación de inserción simple, y luego se reduce el valor de la brecha y la ordenación de inserción continúa para cada grupo hasta que la brecha se reduce a 1. La última ronda es equivalente a una ordenación de inserción simple. Debido a que la clasificación por inserción puede lograr una alta eficiencia cuando la matriz está básicamente ordenada, la clasificación por Hill introduce el concepto de tamaño de paso, que permite que los elementos se inserten en múltiples posiciones sin tener que operar uno por uno. Después de cada ronda de clasificación Hill, toda la matriz se vuelve más ordenada como un todo (se puede entender que después de una ronda de clasificación Hill, los elementos en la posición frontal de toda la matriz son los elementos más pequeños de cada grupo, y las filas Los siguientes elementos son los elementos más grandes de cada grupo)

Después de la primera ronda de clasificación Hill, el estado de la matriz es el siguiente (solo se intercambian las posiciones de 3 y 8)

En la segunda ronda de clasificación Hill, la brecha se reduce a la mitad a 2

Inserta y ordena los 2 grupos de elementos respectivamente, y los resultados son los siguientes (el primer grupo, marcado en azul claro, sin ningún cambio; el segundo grupo, marcado en naranja, solo se intercambian las posiciones de 3 y 4)

Finalmente, gap = 1, realice la última ronda de clasificación Hill (esto es equivalente a una clasificación de inserción simple)

A simple vista, podemos ver que la última ronda solo necesita intercambiar 2 y 3, 6 y 7. Dos intercambios, es decir, clasificación completa

La matriz anterior está ordenada por Hill, y la clasificación se completa después de realizar un total de 4 operaciones de intercambio, lo cual es muy eficiente. Si se utiliza una ordenación por inserción simple, el número de operaciones de intercambio es mayor que 4. Esto muestra el poder de la clasificación Hill. Con base en el contenido anterior, también podemos comprender mejor el significado de la clasificación Hill, que también se denomina clasificación incremental reducida . El tamaño del paso, el incremento y el espacio son lo mismo. La elección de la secuencia de incremento también afectará la eficiencia de la clasificación de Hill. Por lo general, el incremento inicial es la mitad de la longitud de la matriz, y luego la secuencia de incremento se reduce a la mitad cada vez.

La implementación del código de clasificación Hill es la siguiente

	public void shellSort(int[] array) {
    
    
		for (int gap = array.length / 2; gap > 0; gap /= 2) {
    
    
			for (int i = gap; i < array.length; i++) {
    
    
				int j = i;
				int temp = array[i];
                /* 这里没有采用二分查找,直接使用的线性查找 */
				while (j - gap >= 0 && array[j - gap] > temp) {
    
    
					array[j] = array[j - gap];
					j -= gap;
				}
				array[j] = temp;
			}
		}
	}

Tenga en cuenta que en la implementación del código, es atravesar directamente desde la posición después del espacio hasta el último elemento de la matriz, y para cada elemento, el espacio se incrementa y la clasificación de inserción directa se realiza hacia adelante. Esto es algo diferente de la descripción de la ordenación por inserción para cada grupo de elementos en el diagrama. La ventaja de escribir de esta manera es que todo el código de clasificación de Hill solo necesita 3 capas de bucles y es más fácil de entender. De acuerdo con la descripción del diagrama, cada vez que el tipo de inserción de un grupo de elementos se utiliza como un bucle, el código escrito requiere 4 capas de bucles, lo que no es fácil de entender. Los lectores interesados ​​pueden consultar el siguiente código

	public void shellSort(int[] array) {
    
    
		int gap = array.length / 2;
		while (gap >= 1) {
    
    
			for (int i = 0; i < gap; i++) {
    
    
				/* 这一层循环就是对每一组元素进行插入排序 */
				for (int j = i; j + gap < array.length; j += gap) {
    
    
					int pos = j + gap;
					while (pos - gap >= 0) {
    
    
						if (array[pos] < array[pos - gap]) {
    
    
							swap(array, pos, pos - gap);
							pos -= gap;
						} else {
    
    
							break;
						}
					}
				}
			}
			gap /= 2;
		}
	}

Pruebas de rendimiento

Usando matrices aleatorias que van desde 10,000 a 500,000, se realiza la prueba de rendimiento de cada algoritmo de ordenación por inserción y el gráfico de líneas se dibuja de la siguiente manera

Se puede ver que el rendimiento de la clasificación Hill se ha disparado en otras versiones. La segunda está optimizada por búsqueda binaria y la segunda está optimizada por asignación unidireccional. El rendimiento no optimizado es el peor.

Supongo que te gusta

Origin blog.csdn.net/vcj1009784814/article/details/109026483
Recomendado
Clasificación