Estructuras de datos y algoritmos (5) Algoritmos de clasificación

imagen-20220821224607720

Algoritmo de clasificación

Felicitaciones a todos mis amigos por venir a la última parte: Algoritmos de clasificación . El estudio de estructuras de datos y algoritmos está llegando a su fin. ¡La persistencia es la victoria!

Los datos en una matriz originalmente estaban desordenados, pero debido a las necesidades, debemos organizarlos en orden. Para ordenar la matriz, hemos explicado previamente la clasificación por burbujas y la clasificación rápida (opcional) en el artículo de programación en lenguaje C.

Antes de comenzar, comencemos con la clasificación de burbujas.

Clasificación básica

Ordenamiento de burbuja

La clasificación de burbujas se ha explicado en el capítulo de programación en lenguaje C. El núcleo de la clasificación de burbujas es el intercambio. A través del intercambio continuo, los elementos grandes se empujan hacia un extremo poco a poco. En cada ronda, el elemento más grande se organizará en la posición correspondiente. y finalmente formar orden. Sitio web de demostración de algoritmos: https://visualgo.net/zh/sorting?slide=2-2

Supongamos que la longitud de la matriz es N, el proceso detallado es:

  • Se realizan un total de N rondas de clasificación.
  • Cada ronda de clasificación comienza desde el elemento más a la izquierda de la matriz y compara los dos elementos. Si el elemento de la izquierda es mayor que el elemento de la derecha, entonces las posiciones de los dos elementos se intercambian; de lo contrario, permanecen sin cambios.
  • Cada ronda de clasificación empujará el mayor de los elementos restantes hacia el extremo derecho, y la siguiente clasificación ya no considerará estos elementos que ya están en la posición correspondiente.

Por ejemplo, la siguiente matriz:

imagen-20220904212453328

Luego, en la primera ronda de clasificación, primero compare los dos primeros elementos:

imagen-20220904212608834

Descubrimos que el primero es más grande, por lo que debemos intercambiarlo en este momento. Después del intercambio, continúe comparando los siguientes dos elementos hacia atrás:

imagen-20220904212637156

Descubrimos que este último es más grande y no ha cambiado. Continúe observando los dos últimos:

imagen-20220904212720898

En este momento, el primero es más grande, se intercambia y continúa comparando los elementos posteriores:

imagen-20220904212855292

Si este último es más grande, continúe intercambiando y luego compare al revés:

imagen-20220904212942212

Este último es aún más grande. Descubrimos que mientras sea el elemento más grande, será rechazado en cada comparación:

imagen-20220904213034375

Finalmente, el elemento más grande en la matriz actual se arroja al frente y esta ronda de clasificación termina. Debido a que el elemento más grande se ha colocado en la posición correspondiente, en la segunda ronda solo necesitamos considerar los elementos anteriores, es decir es poder:

imagen-20220904213115671

De esta manera, podemos continuar arrojando el más grande hacia el extremo derecho, después de las últimas N rondas de clasificación, tendremos una matriz ordenada.

El código del programa es el siguiente:

void bubbleSort(int arr[], int size){
    
    
    for (int i = 0; i < size; ++i) {
    
    
        for (int j = 0; j < size - i - 1; ++j) {
    
    
            //注意需要到N-1的位置就停止,因为要比较j和j+1
            //这里减去的i也就是已经排好的不需要考虑了
            if(arr[j] > arr[j + 1]) {
    
       //如果后面比前面的小,那么就交换
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}

Es solo que este código sigue siendo el tipo de burbuja más primitivo y podemos optimizarlo:

  1. De hecho, ordenar no requiere N rondas, sino N-1 rondas, debido a que solo un elemento no está ordenado en la última ronda, es equivalente a estar ordenado, por lo que no es necesario considerarlo nuevamente.
  2. Si no hay intercambio en toda la ronda de clasificación, significa que la matriz ya está en orden y no existe una situación en la que la anterior sea más grande que la última.

Entonces, mejorémoslo:

void bubbleSort(int arr[], int size){
    
    
    for (int i = 0; i < size - 1; ++i) {
    
       //只需要size-1次即可
        _Bool flag = 1;   //这里使用一个标记,默认为1表示数组是有序的
        for (int j = 0; j < size - i - 1; ++j) {
    
    
            if(arr[j] > arr[j + 1]) {
    
    
                flag = 0;    //如果发生交换,说明不是有序的,把标记变成0
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
        if(flag) break;   //如果没有发生任何交换,flag一定是1,数组已经有序,所以说直接结束战斗
    }
}

De esta manera, hemos terminado de escribir una versión optimizada de clasificación de burbujas.

Por supuesto, al final necesitamos introducir un concepto adicional: la estabilidad de la clasificación . Entonces, ¿qué es la estabilidad? Si el orden de dos elementos del mismo tamaño permanece sin cambios antes y después de la clasificación, el algoritmo de clasificación es estable. La clasificación de burbujas que acabamos de presentar solo realizará el intercambio si el primero es mayor que el segundo, por lo que no afectará el orden de los dos elementos originalmente iguales. Por lo tanto, la clasificación de burbujas es un algoritmo de clasificación estable .

tipo de inserción

Introduzcamos un nuevo algoritmo de clasificación, clasificación por inserción, que para ser precisos debería llamarse clasificación por inserción directa. Su idea central es como cuando jugamos a Landlord.

imagen-20220904214541199

Creo que todos lo habéis jugado. Antes del comienzo de cada ronda del juego, tenemos que robar cartas de la pila de cartas. Después de sacar las cartas, el orden de las cartas en nuestras manos puede ser caótico. Esto definitivamente no funcionará. .No hay cartas.Aclare ¿cómo sabemos qué cartas tienen cuántas? Para que quede ordenado, insertaremos las cartas recién extraídas en las posiciones correspondientes según el orden de las cartas, para no tener que ordenar las cartas que tenemos en la mano más tarde.

La clasificación por inserción en realidad tiene el mismo principio: de forma predeterminada, las tarjetas anteriores ya están ordenadas (solo la primera tarjeta está en orden al principio), atravesaremos las partes restantes una al lado de la otra y luego las insertaremos. posición correspondiente al frente, dirección de demostración de animación: https://visualgo.net/zh/sorting

Supongamos que la longitud de la matriz es N, el proceso detallado es:

  • Se realizan un total de N rondas de clasificación.
  • Cada ronda de clasificación seleccionará un elemento de la parte posterior y lo comparará con los elementos previamente ordenados de atrás hacia adelante hasta que se encuentre un elemento que no sea más grande que el elemento actual, y el elemento actual se insertará delante de este elemento.
  • Después de insertar un elemento, todos los elementos siguientes retroceden una posición.
  • Cuando todos los elementos posteriores se atraviesan y se insertan en las posiciones correspondientes, se completa la clasificación.

Por ejemplo, la siguiente matriz:

imagen-20220904212453328

En este punto asumimos que el primer elemento ya está en orden y comenzamos a mirar el segundo elemento:

imagen-20220904221510897

Sácalo y compáralo con la secuencia ordenada anterior de atrás hacia adelante. La primera comparación es 4 y resulta que es menor que 4. Continúa avanzando y descubrirás que has llegado al final, por lo que puedes simplemente colóquelo al frente. Nota Antes de traerlo al frente, mueva los elementos siguientes hacia atrás para hacer espacio:

imagen-20220904221648492

Luego insértelo:

imagen-20220904221904359

Ahora que los dos primeros elementos están en un estado ordenado, continuemos mirando el tercer elemento:

imagen-20220904221938583

Aún mirando de atrás hacia adelante, encontramos que encontramos 7 y 4 cuando subimos, así que lo colocamos directamente en esta posición:

imagen-20220904222022949

Ahora que los primeros tres elementos están en orden, sigamos mirando el cuarto elemento:

imagen-20220904222105375

Comparando hacia adelante, encontramos que no se encontró ningún elemento menor que 1 al final, por lo que movimos los primeros tres elementos hacia atrás:

imagen-20220904222145903

Inserte 1 en la posición correspondiente:

imagen-20220904222207544

Ahora que los primeros cuatro elementos están todos en un estado ordenado, solo necesitamos completar el recorrido de los elementos posteriores de la misma manera. El resultado final es una matriz ordenada. Intentemos escribir algo de código:

void insertSort(int arr[], int size){
    
    
    for (int i = 1; i < size; ++i) {
    
       //从第二个元素开始看
        int j = i, tmp = arr[i];   //j直接变成i,因为前面的都是有序的了,tmp相当于是抽出来的牌暂存一下
        while (j > 0 && arr[j - 1] > tmp) {
    
       //只要j>0并且前一个还大于当前待插入元素,就一直往前找
            arr[j] = arr[j - 1];   //找的过程中需要不断进行后移操作,把位置腾出来
            j--;
        }
        arr[j] = tmp;  //j最后在哪个位置,就是是哪个位置插入
    }
}

Por supuesto, este código también se puede mejorar, porque pasamos demasiado tiempo comparando cada uno para encontrar la posición de inserción, debido a que la parte anterior de los elementos ya está en un estado ordenado, podemos considerar usar el algoritmo de búsqueda binaria para encontrar el posición de inserción correspondiente. , lo que ahorra tiempo en encontrar el punto de inserción:

int binarySearch(int arr[], int left, int right, int target){
    
    
    int mid;
    while (left <= right) {
    
    
        mid = (left + right) / 2;
        if(target == arr[mid]) return mid + 1;   //如果插入元素跟中间元素相等,直接返回后一位
        else if (target < arr[mid])  //如果大于待插入元素,说明插入位置肯定在左边
            right = mid - 1;   //范围划到左边
        else   
            left = mid + 1;   //范围划到右边
    }
    return left;   //不断划分范围,left也就是待插入位置了
}

void insertSort(int arr[], int size){
    
    
    for (int i = 1; i < size; ++i) {
    
    
        int tmp = arr[i];
        int j = binarySearch(arr, 0, i - 1, tmp);   //由二分搜索来确定插入位置
        for (int k = i; k > j; k--) arr[k] = arr[k - 1];   //依然是将后面的元素后移
        arr[j] = tmp;
    }
}

Finalmente, analicemos la estabilidad del algoritmo de ordenación por inserción. Entonces, la ordenación por inserción sin optimización en realidad sigue esperando encontrar un elemento que no sea más grande que el elemento que se va a insertar, por lo que cuando se encuentra un elemento igual, solo se insertará después de él y el orden original de los mismos elementos. no se cambiará. Por lo tanto, se dice que la ordenación por inserción también es un algoritmo de ordenación estable (pero se vuelve inestable después de que se usa la optimización de búsqueda binaria más adelante. Por ejemplo, si hay dos elementos iguales consecutivos en una matriz ordenada, y ahora otro igual Cuando llega el elemento central, se acaba de encontrar el del medio. Es el elemento igual clasificado en primer lugar y regresa a la siguiente posición. El elemento recién insertado empujará hacia atrás el elemento igual originalmente clasificado en segundo lugar.)

clasificación de selección

Echemos un vistazo a la última clasificación de selección (para ser precisos, debería ser una clasificación de selección directa). Esta clasificación también es más fácil de entender: simplemente vamos hacia atrás cada vez para encontrar el más pequeño y lo colocamos al frente. Sitio web de demostración de algoritmos: https://visualgo.net/en/sorting

Supongamos que la longitud de la matriz es N, el proceso detallado es:

  • Se realizan un total de N rondas de clasificación.
  • Cada ronda de clasificación encontrará el elemento más pequeño de todos los elementos posteriores y luego lo intercambiará con la siguiente posición que se haya ordenado.
  • Después de N rondas de intercambio, se obtiene una matriz ordenada.

Por ejemplo, la siguiente matriz:

imagen-20220904212453328

La primera clasificación requiere encontrar el elemento más pequeño de toda la matriz e intercambiarlo con el primer elemento:

imagen-20220905141347927

Después del intercambio, el primer elemento ya está en orden, y continuamos buscando el más pequeño entre los elementos restantes:

imagen-20220905141426011

En este momento pasa que 2 está en la segunda posición, pretendamos intercambiarlo para que los dos primeros elementos ya estén en orden, veamos el resto:

imagen-20220905141527050

En este momento, se descubre que 3 es el más pequeño, por lo que se cambia directamente a la posición del tercer elemento:

imagen-20220905141629207

De esta manera, los primeros tres elementos están todos ordenados. Al intercambiar continuamente de esta manera, la matriz que finalmente obtenemos es una matriz ordenada. Intentemos escribir algo de código:

void selectSort(int arr[], int size){
    
    
    for (int i = 0; i < size - 1; ++i) {
    
       //因为最后一个元素一定是在对应位置上的,所以只需要进行N - 1轮排序
        int min = i;   //记录一下当前最小的元素,默认是剩余元素中的第一个元素
        for (int j = i + 1; j < size; ++j)   //挨个遍历剩余的元素,如果遇到比当前记录的最小元素还小的元素,就更新
            if(arr[min] > arr[j])
                min = j;
        int tmp = arr[i];    //找出最小的元素之后,开始交换
        arr[i] = arr[min];
        arr[min] = tmp;
    }
}

Por supuesto, también podemos optimizar la clasificación de selección, porque cada vez que necesitamos seleccionar el más pequeño, también podemos seleccionar el más grande, tirar el pequeño a la izquierda y tirar el grande a la derecha, de modo que puede tener el doble Se logra el doble de eficiencia.

void swap(int * a, int * b){
    
    
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void selectSort(int arr[], int size){
    
    
    int left = 0, right = size - 1;   //相当于左端和右端都是已经排好序的,中间是待排序的,所以说范围不断缩小
    while (left < right) {
    
    
        int min = left, max = right;
        for (int i = left; i <= right; i++) {
    
    
            if (arr[i] < arr[min]) min = i;   //同时找最小的和最大的
            if (arr[i] > arr[max]) max = i;
        }
        swap(&arr[max], &arr[right]);   //这里先把大的换到右边
        //注意大的换到右边之后,有可能被换出来的这个就是最小的,所以说需要判断一下
        //如果遍历完发现最小的就是当前右边排序的第一个元素
        //此时因为已经被换出来了,所以说需要将min改到换出来的那个位置
        if (min == right) min = max;
        swap(&arr[min], &arr[left]);   //接着把小的换到左边
        left++;    //这一轮完事之后,缩小范围
        right--;
    }
}

Finalmente, analicemos la estabilidad de la clasificación por selección. Primero, la clasificación por selección selecciona el más pequeño cada vez. Al insertar hacia adelante, la operación de intercambio se realizará directamente. Por ejemplo, la secuencia original es 3,3,1 y se selecciona 1. En este momento, es el elemento más pequeño y se intercambia con los primeros 3. Después del intercambio, los 3 originalmente clasificados primero van al final, destruyendo el orden original, por lo que la clasificación por selección es un algoritmo de clasificación inestable .

Resumamos los tres algoritmos de clasificación que aprendimos anteriormente. Supongamos que la longitud de la matriz a ordenar es n:

  • Clasificación de burbujas (versión optimizada):
    • Complejidad temporal en el mejor de los casos: O ( n ) O (n)O ( n ) , si está ordenado, entonces solo necesitamos un recorrido, cuando la marca detecta que no se ha producido ningún intercambio, finalizará directamente, por lo que se puede realizar una vez.
    • Complejidad temporal en el peor de los casos: O ( n 2 ) O(n^2)O ( n.2 ), es decir, cada ronda se llena abruptamente, como una matriz completamente invertida.
    • ** Complejidad del espacio: ** Debido a que solo se necesita una variable para almacenar temporalmente las variables que deben intercambiarse, la complejidad del espacio es O ( 1 ) O (1)o ( 1 )
    • **Estabilidad: **Estable
  • Tipo de inserción:
    • Complejidad temporal en el mejor de los casos: O ( n ) O (n)O ( n ) , si está ordenado, debido a que la posición de inserción también es la misma posición, cuando la matriz en sí está ordenada, no necesitamos cambiar ningún otro elemento en cada ronda.
    • Complejidad temporal en el peor de los casos: O ( n 2 ) O(n^2)O ( n.2 ), por ejemplo, una matriz completamente invertida será así: en cada ronda, debes encontrar completamente la inserción frontal.
    • Complejidad espacial : solo se necesita una variable para almacenar los elementos extraídos, por lo que la complejidad espacial es O (1) O (1)o ( 1 )
    • **Estabilidad: **Estable
  • Seleccione ordenar:
    • Complejidad temporal en el mejor de los casos: O ( n 2 ) O(n^2)O ( n.2 ), incluso si la matriz en sí está ordenada, cada ronda aún tiene que encontrar las partes restantes una por una antes de poder determinar el elemento más pequeño, por lo que aún se requiere el orden del cuadrado.
    • Complejidad temporal en el peor de los casos: O ( n 2 ) O(n^2)O ( n.2 ), no hace falta decir más.
    • Complejidad espacial : cada ronda solo necesita registrar la posición del elemento más pequeño, por lo que la complejidad espacial es O (1) O (1)o ( 1 )
    • **Estabilidad: **Inestable

La tabla es la siguiente, recuerde:

Algoritmo de clasificación en el mejor de los casos peor de los casos complejidad espacial estabilidad
Ordenamiento de burbuja O (n) O(n)O ( n ) O (n 2) O(n^2)O ( n.2 ) O(1) O(1)o ( 1 ) Estabilizar
tipo de inserción O (n) O(n)O ( n ) O (n 2) O(n^2)O ( n.2 ) O(1) O(1)o ( 1 ) Estabilizar
clasificación de selección O (n 2) O(n^2)O ( n.2 ) O (n 2) O(n^2)O ( n.2 ) O(1) O(1)o ( 1 ) inestable

Clasificación avanzada

Anteriormente presentamos tres algoritmos de clasificación básicos, y su complejidad de tiempo promedio alcanzó O ( n 2 ) O (n ^ 2)O ( n.2 ), entonces ¿se puede encontrar un algoritmo de clasificación más rápido? En esta parte, continuaremos presentando las versiones avanzadas de los tres algoritmos de clasificación anteriores.

Ordenación rápida

En el capítulo de programación en lenguaje C, también introdujimos la clasificación rápida. La clasificación rápida es una versión avanzada de la clasificación de burbujas. En la clasificación de burbujas, la comparación y el intercambio de elementos se realizan entre elementos adyacentes. Cada elemento de intercambio solo puede moverse una posición, por lo que el El número de comparaciones y movimientos es alto y la eficiencia es relativamente baja. En la clasificación rápida, la comparación y el intercambio de elementos se realizan desde ambos extremos hacia el centro. Los elementos más grandes se pueden intercambiar a las posiciones posteriores en una ronda, mientras que los elementos más pequeños se pueden intercambiar a las posiciones delanteras en una ronda. Cada movimiento avanza más, por lo que hay menos comparaciones y movimientos, y al igual que su nombre, es más rápido.

De hecho, el propósito de cada ronda de clasificación rápida es colocar los grandes a la derecha del punto de referencia y los pequeños a la izquierda del punto de referencia.

Supongamos que la longitud de la matriz es N, el proceso detallado es:

  • Al principio, el rango de clasificación es toda la matriz.
  • Antes de ordenar, seleccionamos el primer elemento en todo el rango de clasificación como base y clasificamos rápidamente los elementos en el rango de clasificación.
  • Primero mire desde el extremo derecho hacia la izquierda y compare cada elemento con el elemento de referencia por turno. Si se encuentra que es más pequeño que el elemento de referencia, cámbielo con el elemento en la posición transversal izquierda (la posición del elemento de referencia al principio), y retenerlo en este momento La posición actual atravesada a la derecha.
  • Después del intercambio, el elemento se recorre de izquierda a derecha. Si se encuentra que es más grande que el elemento base, se intercambia con el elemento en la posición atravesada por la derecha previamente reservada. La posición actual a la izquierda también se conserva, y el paso anterior se ejecuta en un bucle.
  • Cuando los recorridos izquierdo y derecho chocan, esta ronda de clasificación rápida se completa y la posición final en el medio es la posición del elemento base.
  • Tomando la posición de referencia como centro, divida los lados izquierdo y derecho y realice una clasificación rápida de la misma manera.

Por ejemplo, la siguiente matriz:

imagen-20220904212453328

Primero, seleccionamos el primer elemento 4 como elemento base. Inicialmente, los punteros izquierdo y derecho están ubicados en ambos extremos:

imagen-20220905210056432

En este momento, comience a mirar de derecha a izquierda hasta que encuentre un elemento menor que 4. El primero es 6, lo cual definitivamente no es el caso. Mueva el puntero hacia atrás:

imagen-20220905210625181

En este momento, continúe comparando 3 y 4 y descubra que es menor que 4. Luego intercambie 3 directamente (de hecho, simplemente sobrescríbalo directamente) a la posición del elemento señalada por el puntero izquierdo:

imagen-20220905210730105

En este punto, giramos para mirar de izquierda a derecha. Si encontramos un elemento mayor que 4, lo cambiamos al puntero de la derecha. 3 definitivamente ya no está allí, porque simplemente se desaceleró, y luego está 2:

imagen-20220905210851474

2 no es tan grande como 4, por lo que si continuamos mirando hacia atrás, 7 es más grande que 4 en este momento, así que continúe intercambiando:

imagen-20220905211300102

Luego, volvió a mirar de derecha a izquierda:

imagen-20220905211344027

En este momento, 5 es mayor que 4. Si continuamos avanzando, encontramos que 1 es menor que 4, por lo que continuamos intercambiando:

imagen-20220905211427939

Luego gira para mirar de izquierda a derecha, en este momento los dos punteros chocan, la clasificación finaliza y las posiciones señaladas por los dos últimos punteros son las posiciones de los elementos base:

imagen-20220905211543845

Después de esta ronda de clasificación rápida, es posible que el lado izquierdo no esté todo en orden, pero debe ser más pequeño que el elemento base y el lado derecho debe ser más grande que el elemento base. Luego tomamos el punto de referencia como centro y lo dividimos en dos partes para ordenarlo rápidamente nuevamente:

imagen-20220905211741787

De esta manera, finalmente podemos ordenar toda la matriz. Por supuesto, hay otras formas de decir clasificación rápida. Algunas son encontrar las partes izquierda y derecha y luego intercambiarlas. Lo que tenemos aquí es desecharlas. tan pronto como se encuentren. Ahora que la idea está clara, intentemos implementar una clasificación rápida:

void quickSort(int arr[], int start, int end){
    
    
    if(start >= end) return;    //范围不可能无限制的划分下去,要是范围划得都没了,肯定要结束了
    int left = start, right = end, pivot = arr[left];   //这里我们定义两个指向左右两个端点的指针,以及取出基准
    while (left < right) {
    
         //只要两个指针没相遇,就一直循环进行下面的操作
        while (left < right && arr[right] >= pivot) right--;   //从右向左看,直到遇到比基准小的
        arr[left] = arr[right];    //遇到比基准小的,就丢到左边去
        while (left < right && arr[left] <= pivot) left++;   //从左往右看,直到遇到比基准大的
        arr[right] = arr[left];    //遇到比基准大的,就丢到右边去
    }
    arr[left] = pivot;    //最后相遇的位置就是基准存放的位置了
    quickSort(arr, start, left - 1);   //不包含基准,划分左右两边,再次进行快速排序
    quickSort(arr, left + 1, end);
}

De esta forma, implementamos la clasificación rápida. Analicemos la estabilidad de la clasificación rápida. La clasificación rápida consiste en intercambiar directamente elementos que son más pequeños o más grandes que el punto de referencia. Por ejemplo, la matriz original es: 2, 2, 1. En este momento, el primer elemento se utiliza como punto de referencia . Primero, el 1 derecho se descartará y se convertirá en: 1, 2, 1, y luego de izquierda a derecha, porque solo se cambiará cuando encuentre un elemento mayor que la referencia 2, por lo que se colocará la referencia final. en la última posición: 1, 2, 2. En este momento, 2 que debería haber estado al frente ha pasado a la parte de atrás, por lo que el algoritmo de clasificación rápida es un algoritmo de clasificación inestable .

Clasificación rápida de doble eje (opcional)

Aquí necesitamos agregar una versión mejorada adicional de clasificación rápida, clasificación rápida de doble eje . La clase de herramienta de matriz en el lenguaje Java utiliza este método de clasificación para ordenar matrices grandes. Echemos un vistazo a las mejoras que ha realizado en comparación con la clasificación rápida. En primer lugar, el algoritmo de clasificación rápida ordinario puede verse así cuando se encuentra en situaciones extremas:

imagen-20220906131959909

Resulta que toda la matriz está en orden inverso, por lo que equivale a buscar primero en toda la matriz y luego colocar 8 en la última posición. En este momento, finaliza la primera ronda:

imagen-20220906132112592

Dado que 8 va directamente al extremo derecho, en este momento no hay la mitad derecha, solo la mitad izquierda, y la mitad izquierda continúa clasificándose rápidamente en este momento:

imagen-20220906132244369

En este momento, 1 vuelve a ser el elemento más pequeño, por lo que cuando finaliza el recorrido, todavía está en esa posición 1. En este momento, no hay la mitad izquierda, solo la mitad derecha:

imagen-20220906132344525

En este momento, el punto de referencia es 7, que es el más grande. Es realmente desafortunado. Después de arreglar, 7 fue al extremo izquierdo y todavía no hay la mitad derecha:

imagen-20220906132437765

Descubrimos que en este caso extremo, cada ronda debe atravesar completamente todo el rango, y cada ronda tendrá un elemento más grande o más pequeño empujado hacia ambos lados. ¿No es esto una clasificación de burbujas? Por lo tanto, en casos extremos, la clasificación rápida degenerará en clasificación de burbujas, por lo que alguna clasificación rápida seleccionará aleatoriamente el elemento de referencia. Para solucionar este problema que ocurre en casos extremos, podemos agregar otro elemento base, de modo que incluso si ocurre una situación extrema, a menos que ambos lados sean elementos mínimos o elementos máximos, al menos una base se puede segmentar normalmente, y las situaciones extremas ocurrir La probabilidad también se reducirá mucho:

imagen-20220906132945691

En este momento, el primer elemento y el último elemento se utilizan como elementos de referencia y todo el retorno se divide en tres segmentos. Suponiendo que la línea de base 1 es más pequeña que la línea de base 2, entonces todos los elementos almacenados en el primer segmento deben ser más pequeños que línea base 1, y todos los elementos almacenados en el segundo segmento deben ser menores que la línea base 1. No deben ser menores que la base 1 ni mayores que la base 2. Todos los elementos almacenados en la tercera sección deben ser mayores que la base 2:

imagen-20220906133219853

Por lo tanto, después de dividir en tres segmentos, después de cada ronda de clasificación rápida de doble eje, los tres segmentos deben continuar con la clasificación rápida de doble eje. Finalmente, se puede ordenar toda la matriz. Por supuesto, ¿qué cantidad es este algoritmo de clasificación? ¿Más adecuado para matrices relativamente grandes, si la cantidad es relativamente pequeña, considerando que la clasificación rápida de doble eje tiene que realizar tantas operaciones, en realidad no es tan rápida como la clasificación por inserción?

Simulemos cómo funciona la clasificación rápida de doble eje:

imagen-20220906140255444

Primero, tomamos el primer elemento y el último elemento como dos puntos de referencia, y luego debemos compararlos. Si el punto de referencia 1 es mayor que el punto de referencia 2, entonces los dos puntos de referencia deben intercambiarse primero. Sin embargo, debido a que 4 es menor que 6 , no es necesario realizar cambios.

En este punto necesitamos crear tres punteros:

imagen-20220906140538076

Debido a que hay tres áreas, la posición del puntero azul y el área a su izquierda son más pequeñas que el punto de referencia 1, el área desde la izquierda del puntero naranja hasta el puntero azul no es más pequeña que el punto de referencia 1 ni más grande que el punto de referencia 2, el posición del puntero verde y el área a la derecha es mayor que el punto de referencia 2, y el área entre el puntero naranja y el puntero verde es el área a ordenar.

Primero, partimos del elemento señalado por el puntero naranja para juzgar, que se puede dividir en tres situaciones:

  • Si es menor que la base 1, primero debe mover el puntero azul hacia atrás, cambiar los elementos al puntero azul y luego mover el puntero naranja hacia atrás.
  • Si no es menor que la base 1 ni mayor que la base 2, entonces no necesita hacer nada, simplemente mueva el puntero naranja hacia adelante, porque está dentro de este rango.
  • Si es mayor que el punto de referencia 2, entonces debe lanzarse hacia la derecha. Primero mueva el puntero derecho hacia la izquierda y siga avanzando hasta encontrar uno que no sea más grande que el punto de referencia 2, para que pueda intercambiarse sin problemas.

Primero, echemos un vistazo. En este momento, el puntero naranja apunta a 2, por lo que 2 es menor que la base 1. Primero debemos mover el puntero azul hacia atrás y luego intercambiar los elementos en los punteros naranja y azul, pero Aquí porque son el mismo Uno, por lo que permanece sin cambios. En este momento, ambos punteros han retrocedido una posición:

imagen-20220906141556398

De manera similar, sigamos mirando el elemento señalado por el puntero naranja, en este momento es 7, que es mayor que la base 2. Luego necesitamos encontrar un elemento a la derecha que no sea mayor que la base 2:

imagen-20220906141653453

El puntero verde busca de derecha a izquierda, en este momento encuentra 3 e intercambia directamente los elementos del puntero naranja y el puntero azul:

imagen-20220906141758610

En la siguiente ronda, continúe mirando el elemento del puntero naranja. En este momento, se descubre que es más pequeño que la referencia 1, así que mueva el puntero azul hacia adelante primero y descubra que él y el naranja están juntos nuevamente. El intercambio es lo mismo que no. En este momento, ambos punteros retroceden una posición:

imagen-20220906141926006

La nueva ronda continúa mirando el elemento señalado por el puntero naranja. En este momento, encontramos que 1 también es más pequeño que la línea base 1. Primero mueva el puntero azul, luego intercambie y luego mueva el puntero naranja. Igual que arriba , cambia la soledad:

imagen-20220906142041202

En este momento, el puntero naranja apunta a 8, que es mayor que la base 2. Luego, también necesitas encontrar otro a la derecha que no sea mayor que la base 2 para el intercambio:

imagen-20220906142134949

En este momento, encuentre 5, cumpla las condiciones e intercambie:

imagen-20220906142205055

Continuamos mirando el puntero naranja y encontramos que el elemento del puntero naranja no es menor que la base 1 ni mayor que la base 2. Luego, de acuerdo con las reglas anteriores, solo necesitamos mover el puntero naranja hacia adelante:

imagen-20220906142303329

En este momento el puntero naranja y el puntero verde chocan, y no quedan elementos por ordenar, finalmente intercambiamos los dos elementos de referencia ubicados en ambos puntos finales con los punteros correspondientes, la referencia 1 se intercambia con el puntero azul, y la referencia 2 se intercambia con el puntero verde. :

imagen-20220906142445417

Las tres áreas separadas en este momento simplemente cumplen con las condiciones. Por supuesto, con buena suerte aquí, toda la matriz estará en orden. Sin embargo, de acuerdo con la ruta normal, tenemos que continuar realizando una clasificación rápida de doble eje en el resto. tres áreas Finalmente, la clasificación está completa.

Ahora intentemos escribir el código para la clasificación rápida de doble eje:

void dualPivotQuickSort(int arr[], int start, int end) {
    
    
    if(start >= end) return;     //首先结束条件还是跟之前快速排序一样,因为不可能无限制地分下去,分到只剩一个或零个元素时该停止了
    if(arr[start] > arr[end])    //先把首尾两个基准进行比较,看看谁更大
        swap(&arr[start], &arr[end]);    //把大的换到后面去
    int pivot1 = arr[start], pivot2 = arr[end];    //取出两个基准元素
    int left = start, right = end, mid = left + 1;   //因为分了三块区域,此时需要三个指针来存放
    while (mid < right) {
    
        //因为左边冲在最前面的是mid指针,所以说跟之前一样,只要小于right说明mid到right之间还有没排序的元素
        if(arr[mid] < pivot1)     //如果mid所指向的元素小于基准1,说明需要放到最左边
            swap(&arr[++left], &arr[mid++]);   //直接跟最左边交换,然后left和mid都向前移动
        else if (arr[mid] <= pivot2) {
    
        //在如果不小于基准1但是小于基准2,说明在中间
            mid++;   //因为mid本身就是在中间的,所以说只需要向前缩小范围就行
        } else {
    
        //最后就是在右边的情况了
            while (arr[--right] > pivot2 && right > mid);  //此时我们需要找一个右边的位置来存放需要换过来的元素,注意先移动右边指针
            if(mid >= right) break;   //要是把剩余元素找完了都还没找到一个比基准2小的,那么就直接结束,本轮排序已经完成了
            swap(&arr[mid], &arr[right]);   //如果还有剩余元素,说明找到了,直接交换right指针和mid指针所指元素
        }
    }
    swap(&arr[start], &arr[left]);    //最后基准1跟left交换位置,正好左边的全部比基准1小
    swap(&arr[end], &arr[right]);     //最后基准2跟right交换位置,正好右边的全部比基准2大
    dualPivotQuickSort(arr, start, left - 1);    //继续对三个区域再次进行双轴快速排序
    dualPivotQuickSort(arr, left + 1, right - 1);
    dualPivotQuickSort(arr, right + 1, end);
}

Esta parte es sólo opcional y no es obligatoria.

clasificación de colinas

La clasificación Hill es una versión avanzada de la clasificación por inserción directa (la clasificación Hill también se llama clasificación incremental por reducción ). Aunque la clasificación por inserción es fácil de entender, en casos extremos hará que todos los elementos ordenados se muevan hacia atrás (por ejemplo, si solo desea insertar (es un elemento particularmente pequeño)) Para resolver este problema, la clasificación Hill mejora la clasificación por inserción: agrupa toda la matriz según el tamaño del paso y compara primero los elementos más alejados.

Este tamaño de paso está determinado por una secuencia incremental. Esta secuencia incremental es muy crítica. Una gran cantidad de estudios han demostrado que cuando la secuencia incremental dlta[k] = 2^(t-k+1)-1(0<=k<=t<=(log2(n+1)))es pero por simplicidad, generalmente usamos n 2 \frac {n } {2}2nortenorte 4 \frac {n} {4}4nortenorte 8 \frac {n} {8}8norte,...,1 tal secuencia incremental.

Supongamos que la longitud de la matriz es N, el proceso detallado es:

  • Primero encuentre el tamaño del paso inicial, que es n/2.
  • Agrupamos todo el array según el tamaño del paso, es decir, de dos en dos (si n es un número impar, el primer grupo tendrá tres elementos)
  • Realizamos ordenación por inserción dentro de estos grupos respectivamente.
  • Una vez completada la clasificación, reagrupamos los pasos por /2 y repetimos los pasos anteriores hasta que el tamaño del paso sea 1 y se complete la última pasada de clasificación por inserción.

En este caso, debido a que el orden en el grupo se ha ajustado una vez, los elementos pequeños se organizan lo antes posible. Incluso si se van a insertar elementos pequeños durante la última clasificación, no habrá demasiados elementos que deban retroceder. . .

Tomemos como ejemplo la siguiente matriz:

imagen-20220905223505975

Primero, la longitud de la matriz es 8. Divida 2 directamente para obtener 34. Luego, el tamaño del paso es 4. Agrupamos según el tamaño del paso de 4:

imagen-20220905223609936

Entre ellos, 4 y 8 son el primer grupo, 2 y 5 son el segundo grupo, 7 y 3 son el tercer grupo y 1 y 6 son el cuarto grupo. Realizamos clasificación por inserción dentro de estos cuatro grupos respectivamente. Después de ordenar dentro del grupo, el resultado es:

imagen-20220905223659584

Se puede ver que los elementos pequeños actuales avanzan lo más posible, aunque aún no están en orden, luego reducimos el tamaño del paso, 4/2 = 2, y dividimos según este tamaño de paso:

imagen-20220905223804907

En este momento, 4, 3, 8 y 7 son un grupo, y 2, 1, 5 y 6 son un grupo. Continuamos clasificando dentro de estos dos grupos y obtenemos:

imagen-20220905224111803

Finalmente, continuamos aumentando el tamaño del paso/2 para obtener 2/2 = 1. En este momento, el tamaño del paso se convierte en 1, lo que equivale a que toda la matriz sea un grupo. Realizamos una ordenación por inserción nuevamente. En este momento , encontraremos que los elementos pequeños están a la izquierda, y será muy fácil realizar la ordenación por inserción en este momento.

Intentemos escribir algo de código ahora:

void shellSort(int arr[], int size){
    
    
    int delta = size / 2;
    while (delta >= 1) {
    
    
        //这里依然是使用之前的插入排序,不过此时需要考虑分组了
        for (int i = delta; i < size; ++i) {
    
       //我们需要从delta开始,因为前delta个组的第一个元素默认是有序状态
            int j = i, tmp = arr[i];   //这里依然是把待插入的先抽出来
            while (j >= delta && arr[j - delta] > tmp) {
    
       
              	//注意这里比较需要按步长往回走,所以说是j - delta,此时j必须大于等于delta才可以,如果j - delta小于0说明前面没有元素了
                arr[j] = arr[j - delta];
                j -= delta;
            }
            arr[j] = tmp;
        }
        delta /= 2;    //分组插排完事之后,重新计算步长
    }
}

Aunque aquí se utilizan tres niveles de anidamiento de bucles, la complejidad del tiempo real puede ser mayor que O ( n 2 ) O(n^2)O ( n.2 )todavía es pequeño, porque puede garantizar que los elementos pequeños se muevan hacia la izquierda, por lo que el número de clasificaciones en realidad no es tanto como imaginamos. Dado que el proceso de prueba es demasiado complicado, no se enumerará aquí.

Entonces, ¿la clasificación Hill es estable? Debido a que ahora estamos agrupando por tamaño de paso, es posible que dos elementos idénticos adyacentes se muevan al frente en su propio grupo, por lo que la clasificación Hill es un algoritmo de clasificación inestable .

clasificación de montón

Veamos el último: la clasificación en montón también es un tipo de clasificación por selección, pero puede ser más rápida que la clasificación por selección directa. ¿Recuerdas la pila superior grande y la pila superior pequeña que explicamos antes? Revisemos:

Para un árbol binario completo, si todos los nodos padres del árbol son más pequeños que los nodos secundarios, lo llamamos montón raíz pequeño (montón superior pequeño), y si todos los nodos padres del árbol son más grandes que los nodos secundarios, es un montón de raíces grande.

Gracias a que el montón es un árbol binario completo, podemos utilizar fácilmente un array para representarlo:

imagen-20220818110224673

Al construir un montón, podemos ingresar una matriz desordenada en secuencia, y la secuencia final almacenada es una secuencia ordenada. Aprovechando esta propiedad, podemos usar fácilmente el montón para ordenar. Primero escribamos una pequeña pila superior:

typedef int E;
typedef struct MinHeap {
    
    
    E * arr;
    int size;
    int capacity;
} * Heap;

_Bool initHeap(Heap heap){
    
    
    heap->size = 0;
    heap->capacity = 10;
    heap->arr = malloc(sizeof (E) * heap->capacity);
    return heap->arr != NULL;
}

_Bool insert(Heap heap, E element){
    
    
    if(heap->size == heap->capacity) return 0;
    int index = ++heap->size;
    while (index > 1 && element < heap->arr[index / 2]) {
    
    
        heap->arr[index] = heap->arr[index / 2];
        index /= 2;
    }
    heap->arr[index] = element;
    return 1;
}

E delete(Heap heap){
    
    
    E max = heap->arr[1], e = heap->arr[heap->size--];
    int index = 1;
    while (index * 2 <= heap->size) {
    
    
        int child = index * 2;
        if(child < heap->size && heap->arr[child] > heap->arr[child + 1])
            child += 1;
        if(e <= heap->arr[child]) break;
        else heap->arr[index] = heap->arr[child];
        index = child;
    }
    heap->arr[index] = e;
    return max;
}

Luego solo necesitamos insertar estos elementos en el montón uno por uno y luego sacarlos uno por uno, lo que obtenemos es una secuencia ordenada:

int main(){
    
    
    int arr[] = {
    
    3, 5, 7, 2, 9, 0, 6, 1, 8, 4};

    struct MinHeap heap;    //先创建堆
    initHeap(&heap);
    for (int i = 0; i < 10; ++i)
        insert(&heap, arr[i]);   //直接把乱序的数组元素挨个插入
    for (int i = 0; i < 10; ++i)
        arr[i] = delete(&heap);    //然后再一个一个拿出来,就是按顺序的了

    for (int i = 0; i < 10; ++i)
        printf("%d ", arr[i]);
}

El resultado final es:

imagen-20220906001134488

Aunque esto es más sencillo de usar, requiere O ( n ) O (n) adicionalEl espacio O ( n ) se utiliza como montón, por lo que podemos optimizarlo aún más y reducir su ocupación de espacio. Entonces, ¿cómo optimizar? También podríamos cambiar nuestra forma de pensar y construir directamente el montón para la matriz dada.

Supongamos que la longitud de la matriz es N, el proceso detallado es:

  • Primero cambie el tamaño de la matriz dada a un montón superior grande
  • Realice N rondas de selección, seleccionando cada vez el elemento en la parte superior del montón superior y almacenándolo desde el final de la matriz (intercambiando la parte superior del montón y el último elemento del montón)
  • Una vez completado el intercambio, vuelva a ajustar el nodo raíz del montón para que continúe cumpliendo con las propiedades de un montón superior grande y luego repita las operaciones anteriores.
  • Cuando terminan las N rondas, lo que obtienes es una matriz ordenada de pequeña a grande.

Primero convertimos la matriz dada en un árbol binario completo, tomando la siguiente matriz como ejemplo:

imagen-20220906220020172

En este punto, este árbol binario aún no es un montón y nuestro objetivo principal es convertirlo en un montón superior grande. Entonces, ¿cómo convertir este árbol binario en un gran montón superior? Solo necesitamos hacer ajustes comenzando desde el último nodo no hoja (en orden de arriba a abajo). Por ejemplo, 1 es el último nodo no hoja en este momento, por lo que debemos comparar a partir de 1. Si Si el nodo secundario es más grande que él, entonces es necesario intercambiar el nodo secundario más grande. En este momento, su nodo secundario 6 es mayor que 1, por lo que es necesario intercambiar:

imagen-20220906221306519

Luego, veamos el penúltimo nodo sin hoja, que es 7. En este momento, ambos hijos son más pequeños que él, por lo que no es necesario ningún ajuste. A continuación, miremos el penúltimo nodo sin hoja 2. En este momento, el dos hijos de 2, 6 y 8, son ambos mayores que 2, entonces elegimos el mayor de los dos hijos para intercambiar:

imagen-20220906221504364

Finalmente, el único nodo no hoja que queda es el nodo raíz. En este momento, los hijos izquierdo y derecho de nuestros 4 son mayores que 4, por lo que aún es necesario realizar ajustes:

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo anti-leeching. Se recomienda guardar la imagen y cargarla directamente (img-87a7s1F4-1662545089947)(/Users/nagocoler/Library/Application Support/typora-user -imagenes/imagen-20220906221657599 .png)]

Después del ajuste, aún no ha terminado, porque después de reemplazar 4 en este momento, todavía no cumple con las propiedades de un montón superior grande. En este momento, el hijo izquierdo de 4 es mayor que 4 y debemos continuar. mirar hacia abajo:

imagen-20220906221833012

Después del intercambio, todo el árbol binario ahora satisface las propiedades de un montón superior grande y nuestro primer ajuste inicial está completo.

En este punto comenzamos el segundo paso, necesitamos intercambiar los elementos superiores del montón uno por uno, lo que equivale a sacar el más grande cada vez hasta terminar, primero intercambiamos el elemento superior del montón y el último elemento:

imagen-20220906222327297

En este momento, el elemento más grande de toda la matriz se ha organizado en la posición correspondiente, y luego ya no consideramos el último elemento. En este momento, los elementos restantes al frente continúan considerándose como un árbol binario completo, y el El nodo raíz se vuelve a acumular (solo es necesario ajustar el nodo raíz, porque otros nodos que no son hoja no han cambiado), para que continúe cumpliendo con las propiedades de un montón superior grande:

imagen-20220906222819554

Aún no ha terminado, continúe ajustando:

imagen-20220906222858752

En este momento termina la primera ronda y luego se repite la segunda ronda. Se repite la operación anterior. Primero, el elemento superior del montón todavía se arroja a la penúltima posición, lo que equivale a colocar el segundo en último elemento más grande a la posición correspondiente:

imagen-20220906222934602

En este momento, se han ordenado dos elementos. De manera similar, continuamos considerando los elementos restantes como un árbol binario completo y continuamos realizando operaciones de montón en el nodo raíz para que continúe satisfaciendo la propiedad del montón superior grande:

imagen-20220906223110734

En la tercera ronda, se usa la misma idea y la más grande se cambia hacia atrás:

imagen-20220906223326135

Después de N rondas de clasificación, cada elemento finalmente se puede organizar en la posición correspondiente. Según las ideas anteriores, intentemos escribir algo de código:

//这个函数就是对start顶点位置的子树进行堆化
void makeHeap(int* arr, int start, int end) {
    
    
    while (start * 2 + 1 <= end) {
    
        //如果有子树,就一直往下,因为调整之后有可能子树又不满足性质了
        int child = start * 2 + 1;    //因为下标是从0开始,所以左孩子下标就是i * 2 + 1,右孩子下标就是i * 2 + 2
        if(child + 1 <= end && arr[child] < arr[child + 1])   //如果存在右孩子且右孩子比左孩子大
            child++;    //那就直接看右孩子
        if(arr[child] > arr[start])   //如果上面选出来的孩子,比父结点大,那么就需要交换,大的换上去,小的换下来
            swap(&arr[child], &arr[start]);
        start = child;   //继续按照同样的方式前往孩子结点进行调整
    }
}

void heapSort(int arr[], int size) {
    
    
    for(int i= size/2 - 1; i >= 0; i--)   //我们首选需要对所有非叶子结点进行一次堆化操作,需要从最后一个到第一个,这里size/2计算的位置刚好是最后一个非叶子结点
        makeHeap(arr, i, size - 1);
    for (int i = size - 1; i > 0; i--) {
    
       //接着我们需要一个一个把堆顶元素搬到后面,有序排列
        swap(&arr[i], &arr[0]);    //搬运实际上就是直接跟倒数第i个元素交换,这样,每次都能从堆顶取一个最大的过来
        makeHeap(arr, 0, i - 1);   //每次搬运完成后,因为堆底元素被换到堆顶了,所以需要再次对根结点重新进行堆化
    }
}

Finalmente, analicemos la estabilidad de la clasificación del montón. De hecho, la clasificación del montón en sí también es una selección: cada vez, el elemento superior del montón se selecciona y se coloca en la parte posterior, pero el montón siempre se mantiene dinámicamente. De hecho, cuando se sacan elementos de la parte superior del montón, se intercambiarán con las hojas de abajo, lo que puede ocurrir:

imagen-20220906223706019

Por lo tanto, la clasificación en montón es un algoritmo de clasificación inestable .

Finalmente, resumamos las propiedades relevantes de los tres algoritmos de clasificación anteriores:

Algoritmo de clasificación en el mejor de los casos peor de los casos complejidad espacial estabilidad
Ordenación rápida O (nlogn) O(nlogn)O ( nlogn ) _ _ _ _ O (n 2) O(n^2)O ( n.2 ) O ( iniciar sesión ) O ( iniciar sesión )O ( iniciar sesión ) _ _ inestable
clasificación de colinas O (n 1.3) O(n^{1.3})O ( n.1.3 ) O (n 2) O(n^2)O ( n.2 ) O(1) O(1)o ( 1 ) inestable
clasificación de montón O (nlogn) O(nlogn)O ( nlogn ) _ _ _ _ O (nlogn) O(nlogn)O ( nlogn ) _ _ _ _ O(1) O(1)o ( 1 ) inestable

Otras opciones de clasificación

Además de los diversos algoritmos de clasificación que presentamos anteriormente, también existen otros tipos de algoritmos de clasificación. Echemos un vistazo a todos ellos.

fusionar ordenar

Merge sort utiliza la idea de división y conquista recursiva para dividir la matriz original, luego ordenar primero las matrices pequeñas divididas y finalmente fusionarlas en una matriz grande ordenada. Aún así, es fácil de entender:

imagen-20220906232451040

Tomemos como ejemplo la siguiente matriz:

imagen-20220905223505975

No nos apresuremos a ordenar al principio, dividámoslo mitad y mitad:

imagen-20220907135544173

Continuar dividiendo:

imagen-20220907135744253

Al final, se convertirán en elementos uno por uno como este:

imagen-20220907135927289

En este punto podemos comenzar a fusionar y ordenar. Tenga en cuenta que la fusión aquí no es una fusión simple. Necesitamos fusionar cada elemento en orden de pequeño a grande. El primer grupo de árboles 4 y 2. En este momento, debemos seleccione el más pequeño de estas dos matrices y muévalo al frente:

imagen-20220907140219455

Una vez completada la clasificación, continuamos fusionando hacia arriba:

imagen-20220907141217008

Finalmente fusionamos las dos matrices a su tamaño original:

imagen-20220907141442229

Finalmente, obtendrás una matriz ordenada.

De hecho, este algoritmo de clasificación también es muy eficiente, pero necesita sacrificar un espacio del tamaño de la matriz original para ordenar los datos descompuestos. El código es el siguiente:

void merge(int arr[], int tmp[], int left, int leftEnd, int right, int rightEnd){
    
    
    int i = left, size = rightEnd - left + 1;   //这里需要保存一下当前范围长度,后面使用
    while (left <= leftEnd && right <= rightEnd) {
    
       //如果两边都还有,那么就看哪边小,下一个就存哪一边的
        if(arr[left] <= arr[right])   //如果左边的小,那么就将左边的存到下一个位置(这里i是从left开始的)
            tmp[i++] = arr[left++];   //操作完后记得对i和left都进行自增
        else
            tmp[i++] = arr[right++];
    }
    while (left <= leftEnd)    //如果右边看完了,只剩左边,直接把左边的存进去
        tmp[i++] = arr[left++];
    while (right <= rightEnd)   //同上
        tmp[i++] = arr[right++];
    for (int j = 0; j < size; ++j, rightEnd--)   //全部存到暂存空间中之后,暂存空间中的内容都是有序的了,此时挨个搬回原数组中(注意只能搬运范围内的)
        arr[rightEnd] = tmp[rightEnd];
}

void mergeSort(int arr[], int tmp[], int start, int end){
    
       //要进行归并排序需要提供数组和原数组大小的辅助空间
    if(start >= end) return;   //依然是使用递归,所以说如果范围太小,就不用看了
    int mid = (start + end) / 2;   //先找到中心位置,一会分两半
    mergeSort(arr, tmp, start, mid);   //对左半和右半分别进行归并排序
    mergeSort(arr, tmp, mid + 1, end);
    merge(arr, tmp, start, mid, mid + 1, end);  
  	//上面完事之后,左边和右边都是有序状态了,此时再对整个范围进行一次归并排序即可
}

Debido a que la ordenación por combinación también se fusiona de acuerdo con la prioridad pequeña al final. Si encuentra igualdad, el primero se devolverá primero a la matriz original, por lo que el primero todavía ocupa el primer lugar, por lo que la ordenación por combinación también es una ordenación estable. algoritmo .

Clasificación por cubo y clasificación por base

Antes de comenzar a explicar la clasificación de cubos, primero echemos un vistazo a la clasificación por conteo. Requiere que la longitud de la matriz sea N y el rango de valores de los elementos de la matriz esté entre 0 - M-1 (M es menor que o igual a N)

Sitio web de demostración de algoritmos: https://visualgo.net/zh/sorting?slide=1

Por ejemplo, en la siguiente matriz, todos los elementos van del 1 al 6:

imagen-20220907142933725

Primero lo recorremos y contamos el número de apariciones de cada elemento. Una vez completadas las estadísticas, podemos saber dónde almacenar los elementos con qué valores después de ordenar:

imagen-20220907145336855

Analicémoslo, en primer lugar, solo hay un 1, por lo que solo ocupará una posición, solo hay un 2, por lo que solo ocupará una posición, y así sucesivamente:

imagen-20220907145437992

Por lo tanto, podemos completar estos valores directamente uno por uno en función de los resultados estadísticos y aún se mantienen estables, solo complete algunos en orden:

imagen-20220907145649061

¿No parece muy simple y solo es necesario recorrerlo una vez para obtener estadísticas?

Por supuesto, definitivamente hay desventajas:

  1. Cuando la diferencia entre los valores máximo y mínimo en la matriz es demasiado grande, tenemos que solicitar más espacio para contar, por lo que no es adecuado para ordenar por conteo.
  2. Cuando los valores de los elementos en la matriz no son discretos (es decir, no son números enteros), no hay forma de contarlos.

A continuación, veamos la clasificación por cubos, que es una extensión de la clasificación por conteo y la idea es relativamente simple. También requiere que la longitud de la matriz sea N y el rango de valores de los elementos de la matriz esté entre 0 - M-1. (M es menor o igual que N), por ejemplo, ahora hay 1000 estudiantes y ahora necesitamos ordenar a estos estudiantes según sus puntuaciones. Debido a que el rango de puntuación es 0-100, podemos crear 101 depósitos para el almacenamiento clasificado. .

Por ejemplo, la siguiente matriz:

imagen-20220907142933725

Esta matriz contiene los elementos del 1 al 6, por lo que podemos crear 6 depósitos para estadísticas:

imagen-20220907143715938

De esta manera, solo necesitamos atravesar una vez para clasificar todos los elementos y arrojarlos a estos depósitos. Finalmente, solo necesitamos atravesar estos depósitos en secuencia y luego sacar los elementos y almacenarlos nuevamente para obtener un orden. formación:

imagen-20220907144255326

Sin embargo, aunque la clasificación de depósitos también es muy rápida, también tiene las mismas limitaciones que la clasificación por conteo anterior. Podemos reducir la cantidad de depósitos aceptando elementos dentro de un cierto rango en cada depósito, pero esto generará una sobrecarga de tiempo adicional.

Finalmente veamos la clasificación por bases: la clasificación por bases sigue siendo un algoritmo de clasificación que se basa en estadísticas, pero no provocará una aplicación ilimitada del espacio auxiliar porque el rango es demasiado grande. La idea es separar 10 números base (del 0 al 9). Todavía solo necesitamos recorrer una vez. Clasificamos según el número en las unidades de cada elemento, porque ahora hay 10 números base, que son 10 A. balde. Después de terminar las unidades, mira las decenas y las centenas...

Sitio web de demostración de algoritmos: https://visualgo.net/zh/sorting

imagen-20220907152403435

Primero cuente por un solo dígito, luego ordene, luego cuente por decenas y luego ordene, el resultado final es el resultado final:

imagen-20220907152903020

Luego viene el dígito de las decenas:

imagen-20220907153005797

Finalmente, sácalos nuevamente en orden:
imagen-20220907153139536

Matriz ordenada obtenida con éxito.

Finalmente, resumamos las propiedades relevantes de todos los algoritmos de clasificación:

Algoritmo de clasificación en el mejor de los casos peor de los casos complejidad espacial estabilidad
Ordenamiento de burbuja O (n) O(n)O ( n ) O (n 2) O(n^2)O ( n.2 ) O(1) O(1)o ( 1 ) Estabilizar
tipo de inserción O (n) O(n)O ( n ) O (n 2) O(n^2)O ( n.2 ) O(1) O(1)o ( 1 ) Estabilizar
clasificación de selección O (n 2) O(n^2)O ( n.2 ) O (n 2) O(n^2)O ( n.2 ) O(1) O(1)o ( 1 ) inestable
Ordenación rápida O (nlogn) O(nlogn)O ( nlogn ) _ _ _ _ O (n 2) O(n^2)O ( n.2 ) O ( iniciar sesión ) O ( iniciar sesión )O ( iniciar sesión ) _ _ inestable
clasificación de colinas O (n 1.3) O(n^{1.3})O ( n.1.3 ) O (n 2) O(n^2)O ( n.2 ) O(1) O(1)o ( 1 ) inestable
clasificación de montón O (nlogn) O(nlogn)O ( nlogn ) _ _ _ _ O (nlogn) O(nlogn)O ( nlogn ) _ _ _ _ O(1) O(1)o ( 1 ) inestable
fusionar ordenar O (nlogn) O(nlogn)O ( nlogn ) _ _ _ _ O (nlogn) O(nlogn)O ( nlogn ) _ _ _ _ O (n) O(n)O ( n ) Estabilizar
contando ordenar O (norte + k) O(norte + k)O ( n.+k ) O (norte + k) O(norte + k)O ( n.+k ) Bien bien)O ( k ) Estabilizar
ordenar cubos O (norte + k) O(norte + k)O ( n.+k ) O (n 2) O(n^2)O ( n.2 ) O (k + norte) O(k + norte)O ( k+norte ) Estabilizar
Ordenación por base O ( norte × k ) O (n \ veces k)O ( n.×k ) O ( norte × k ) O (n \ veces k)O ( n.×k ) O ( k + n ) O(k+n)O ( k+norte ) Estabilizar

clasificación de monos

La clasificación de monos es más budista, ¡porque todo depende de la suerte para saber cuándo puedes terminar la clasificación!

El teorema del mono infinito fue mencionado por primera vez por Emile Borrell en un libro sobre probabilidad publicado en 1909, que introdujo el concepto de "escribir monos". El teorema del mono infinito es un ejemplo de la proposición de uniformidad cero de Kolmogorov en la teoría de la probabilidad. El significado general es que si dejas que un mono presione teclas al azar en una máquina de escribir, y si sigues presionándolas así, mientras el tiempo llegue al infinito, es casi seguro que el mono podrá escribir cualquier texto determinado, incluso El de Shakespeare. También se puede mecanografiar el conjunto completo de obras.

Supongamos que hay una matriz de longitud N:

imagen-20220907154254943

Cada vez seleccionamos aleatoriamente un elemento de la matriz y lo intercambiamos con un elemento aleatorio:

imagen-20220907154428792

Siempre que tenga la suerte, es posible que pueda hacerlo en unas pocas veces, pero si no tiene suerte, es posible que no pueda arreglar su matrimonio hasta que su nieto esté casado.

El código se muestra a continuación:

_Bool checkOrder(int arr[], int size){
    
    
    for (int i = 0; i < size - 1; ++i)
        if(arr[i] > arr[i + 1]) return 0;
    return 1;
}

int main(){
    
    
    int arr[] = {
    
    3,5, 7,2, 9, 0, 6,1, 8, 4}, size = 10;

    int counter = 0;
    while (1) {
    
    
        int a = rand() % size, b = rand() % size;
        swap(&arr[a], &arr[b]);
        if(checkOrder(arr, size)) break;
        counter++;
    }
    printf("在第 %d 次排序完成!", counter);
}

Se puede ver que con 10 elementos, la clasificación 7485618 fue exitosa:

imagen-20220907160219493

Pero no sé por qué los resultados de la clasificación son los mismos cada vez, tal vez los números aleatorios no sean lo suficientemente aleatorios.

Algoritmo de clasificación en el mejor de los casos peor de los casos complejidad espacial estabilidad
clasificación de monos O(1) O(1)o ( 1 ) O(1) O(1)o ( 1 ) inestable

Supongo que te gusta

Origin blog.csdn.net/qq_25928447/article/details/126751213
Recomendado
Clasificación