Notas de estudio de algoritmos: clasificación

Introducción a los algoritmos de clasificación:

La clasificación es una operación importante en el proceso de programación de computadoras Su función es reorganizar una secuencia arbitraria de elementos de datos en una secuencia ordenada por palabras clave.

​ Es importante porque la operación de búsqueda es muy importante, y la tabla de secuencias ordenadas puede usar una búsqueda binaria más eficiente (la complejidad de tiempo es O(logn)), mientras que la tabla de secuencias desordenadas solo puede realizar búsquedas secuenciales (complejidad de tiempo El grado es O(N)), y el proceso de creación de un árbol de clasificación binario, un árbol binario ordenado y un montón es en sí mismo un proceso de clasificación.

La estabilidad del algoritmo de clasificación:

​ Se pueden almacenar dos o más datos con palabras clave iguales en la secuencia de datos a clasificar, suponiendo ki==kj e i!=j, si i<j antes de clasificar, i<j después de clasificar, este tipo de El algoritmo de clasificación es una especie estable y viceversa es una especie inestable.

1. Clasificación clásica:

Puede completar la operación de clasificación, pero sin ninguna optimización, la cantidad de comparaciones de datos no es menor, la cantidad de intercambios de datos no es menor, no hay ninguna ventaja y no puede llamarse algoritmo.

// 时间复杂度:O(N^2),空间复杂度:O(1),稳定性:稳定
void classic_sort(int* arr,size_t len)
{
    
    
    for(int i=0; i<len-1; i++)
    {
    
    
        for(int j=i+1; j<len; j++)
        {
    
    
            if(arr[i] > arr[j])
                swap(arr[i],arr[j]);
        }   
    }   
}

2. Clasificación de burbujas:

El proceso de clasificación es como el aumento de las burbujas en el agua, cuanto más grandes son las burbujas, más rápido se elevan, por lo que se denomina clasificación de burbujas, a partir de los primeros datos, deje que se comparen los datos adyacentes, si Ki> Ki + 1, intercámbielos, se completa una pieza de datos para cada clasificación, y este proceso se repite, y los datos que se clasificarán directamente son 1, luego finaliza la clasificación.

En comparación con la clasificación clásica, la clasificación de burbujas es sensible al orden de los datos. Si no hay intercambio en una clasificación, significa que los datos anteriores son más pequeños que los datos posteriores, y puede detenerse inmediatamente y finalizar la clasificación antes.

​ Nota: Si los datos que se ordenarán están básicamente ordenados, usar la ordenación por burbuja es la forma más rápida, ya que es sensible al orden de los datos y puede terminar antes de tiempo.

// 最优时间复杂度:O(N),最差时间复杂度:O(N^2),平均时间复杂度:O(N^2),空间复杂度:O(1),稳定性:稳定
void bubble_sort(int* arr,size_t len)
{
    
    
    bool flag = true;
    for(int i=len-1; flag&&i>0; i--)
    {
    
    
        flag = false;
        for(int j=0; j<i; j++)
        {
    
    
            if(arr[j] > arr[j+1])
            {
    
    
                swap(arr[j],arr[j+1]);
                flag = true;
            }   
        }   
    }   
}

3. Seleccione ordenar:

​Comience con los i-ésimos datos a ordenar, asuma que es el valor mínimo y use min para registrar su subíndice, y luego use arr[min] para comparar con los datos detrás de él, si hay datos que aún son pequeños , actualícelo El valor de min, después de ordenar, si min==i, significa que los primeros datos son el valor mínimo, no es necesario intercambiar, si min!=i, transfiera los primeros datos y arr[min], entonces i++, repetir Este paso es hasta que los datos que se ordenarán sean 1 (i=len-1), y finalice la clasificación.

En comparación con la clasificación clásica, su número de comparaciones de datos no se reduce, pero su número de intercambios de datos se reduce considerablemente (O (N-1)), lo que ahorra mucho tiempo de intercambio de datos. Aunque la complejidad del tiempo no cambia, la eficiencia de clasificación Mucho mejor que la clasificación clásica.

​ Nota: La característica más destacada de la clasificación por selección es la menor cantidad de intercambios de datos. Si la cantidad de bytes de datos que se clasificarán es grande, como: estructuras, objetos de clase, usar la clasificación por selección es lo más rápido.

// 时间复杂度:O(N^2),空间复杂度:O(1),稳定性:不稳定
void select_sort(int* arr,size_t len)
{
    
    
    for(int i=0; i<len-1; i++)
    {
    
    
        int min = i;
        for(int j=i+1; j<len; j++)
        {
    
    
            if(arr[min] > arr[j])
                min = j;
        }
        if(min != i)
            swap(arr[min],arr[i]);
    }
}

4. Clasificación por inserción:

Agregue nuevos datos a la secuencia ordenada para mantener la secuencia en orden. Los pasos específicos son: Suponga que los nuevos datos se almacenan en i=len y compare val con los datos anteriores uno por uno. val<arr[i-1 ], si los datos anteriores son mayores que tmp, copie los datos anteriores hacia atrás y luego repita la comparación disminuyendo i desde 1 hasta arr[i-1]<=tmp o 0==i, entonces la posición i es tmp La ubicación donde debe almacenarse, se ordenan los datos recién insertados.

Puede usar el método anterior para ordenar la secuencia desordenada, tratar la secuencia como dos puntajes totales, la parte que se ordenó (el número es 1) y la parte que se insertará (len-1) y los datos que se insertado en la parte uno por uno Se inserta la parte ordenada anterior y se completa la clasificación de la secuencia desordenada.

Nota: como su nombre indica, el algoritmo de clasificación por inserción es adecuado para agregar nuevos datos a una secuencia ordenada. La ventaja es que no hay intercambio de datos durante el proceso de clasificación, lo que ahorra mucho tiempo.

// 时间复杂度:O(N^2),空间复杂度:O(1),稳定性:稳定
void _insert_sort(int* arr,size_t len,int val)
{
    
    
    int i = len; 
    while(i>0 && arr[i-1]>val)
    {
    
       
        arr[i] = arr[i-1];
        i--;
    }
    arr[i] = val;
}
void insert_sort(int* arr,size_t len)
{
    
    
    for(int i=1; i<len; i++)
    {
    
    
        _insert_sort(arr,i,arr[i]);
    }
}

void insert_sort(int* arr,size_t len)
{
    
    
    for(int i=1; i<len; i++)
    {
    
    
        int tmp = arr[i], j = i;
        while(j-1>=0 && arr[j-1]>tmp)
        {
    
    
            arr[j] = arr[j-1];
            j--;
        }
        arr[j] = tmp;
    }
}

5. Clasificación de colinas:

​ El autor que diseñó el algoritmo se llama Hill, por lo que se llama ordenación Hill. Introduce el concepto de incremento (la distancia que se mueve cada vez que se insertan datos) sobre la base de la ordenación por inserción. La ordenación por inserción solo mueve una posición a la vez. tiempo de forma predeterminada. Cuando la cantidad de datos es relativamente grande, la velocidad de movimiento es relativamente lenta. La ordenación en pendiente primero usa la mitad de la cantidad como el incremento de movimiento para realizar la ordenación por inserción, ordena los datos de forma aproximada y luego reduce el incremento a fino. ajusta los datos y luego completa la ordenación por inserción.

​ Nota: la clasificación por colinas es adecuada para agregar nuevos datos a una secuencia ordenada cuando el número es relativamente grande.

// 时间复杂度:O(NlogN),空间复杂度:O(1),稳定性:不稳定
void shell_sort(int* arr,size_t len)
{
    
    
    for(int k=len/2; k>0; k/=2)
    {
    
    
        for(int i=k; i<len; i+=k)
        {
    
    
            int tmp = arr[i], j = i;
            while(j-k>=0 && arr[j-k]>tmp)
            {
    
    
                arr[j] = arr[j-k];
                j-=k;
            }
            arr[j] = tmp;
        }
    }
}

6. Clasificación rápida:

Primero busque un punto de referencia en la secuencia que se va a ordenar y luego compárelo con los datos restantes. Los datos más pequeños que el punto de referencia se colocan a la izquierda del punto de referencia y los datos más grandes que el punto de referencia se colocan a la derecha. , de modo que el punto de referencia prevalezca aproximadamente en orden, y luego ordene los datos a la izquierda del punto de referencia y los datos a la derecha del punto de referencia de la misma manera hasta que toda la secuencia esté completamente ordenada.

Nota: La razón por la que la clasificación rápida se denomina clasificación rápida es que tiene el mejor rendimiento y la velocidad más rápida en todas las situaciones. Si no conoce los datos que se van a clasificar, se recomienda elegir primero la clasificación rápida.

// 时间复杂度:O(NlogN),空间复杂度:O(1),稳定性:不稳定
void _quick_sort(int* arr,int left,int right)
{
    
    
    // 备份左右边界
    int l = left, r = right;
    // 把最右边的数据作为标杆,前记录标杆下标
    int pv = arr[left], pi = left;

    while(l<r)
    {
    
    
        // 从右向左寻找比标杆小的数据
        while(l<r && arr[r]>=pv) r--;
        // 找到比标杆小的数据
        if(l<r)
        {
    
    
            // 把它移动标杆的左边
            arr[pi] = arr[r];
            // 记录标杆的新位置
            pi = r;
        }
        // 从左向右寻找比标杆大的数据
        while(l<r && arr[l]<=pv) l++;
        // 打到比标杆大的数据
        if(l<r)
        {
    
    
            // 把它移动到标杆的右边
            arr[pi] = arr[l];
            // 记录标杆的新位置
            pi = l;
        }
    }
    // 把标杆的值存放到最新位置
    arr[pi] = pv;

    // 快速排序标杆左边的数据
    if(pi-left>1)
        _quick_sort(arr,left,pi-1);
    // 快速排序标杆右边的数据
    if(right-pi>1)
        _quick_sort(arr,pi+1,right);
}
void quick_sort(int* arr,size_t len)
{
    
    
    _quick_sort(arr,0,len-1);
}

7. Clasificación de montones:

​ La denominada clasificación de montón consiste en tratar los datos que se van a clasificar como un gran montón de raíz y, a continuación, mostrar gradualmente el valor máximo en la parte superior del montón y almacenarlo al final de la secuencia, es decir, la clasificación es completado con la ayuda de la estructura de datos del montón raíz grande.

​ Nota: En teoría, la velocidad de la clasificación del montón no es más lenta que la clasificación rápida, pero para la clasificación de secuencia desordenada, primero debe crear un montón, y la complejidad del tiempo es O (N), y luego salir del montón. por uno para completar la complejidad del tiempo de clasificación es O (NlogN), por lo que la clasificación rápida es más rápida que la clasificación en montón para secuencias desordenadas, por lo que la clasificación en montón generalmente no se usa en aplicaciones prácticas y solo vive en libros de texto.

// 时间复杂度:O(NlogN),空间复杂度:O(1),稳定性:不稳定
void _heap_sort(int* arr,int root,size_t len)
{
    
    
    while(root*2+1<len)
    {
    
    
        // 假定左子树是左右子树中的最大值
        int max = root*2+1;
        // 判断右子树是否大于左子树,如果大于则更新最大值下标
        if(max+1<len && arr[max+1]>arr[max])
            max++;
        // 如果最大子树依然小于根,则结束
        if(arr[max] < arr[root])
            return;
        // 把最大子树与根交换
        swap(arr[max],arr[root]);
        root = max;
    }
}
void heap_sort(int* arr,size_t len)
{
    
    
    // 创建大根堆
    for(int i=len/2-1; i>=0; i--)
        _heap_sort(arr,i,len);

    for(int i=len-1; i>0; i--)
    {
    
    
        // 把堆的根与末尾的数据交换,数量减-1
        swap(arr[0],arr[i]);
        // 调整堆
        _heap_sort(arr,0,i);
    }
    printf("%s:",__func__);
}

8. Clasificación por combinación:

​ Agrupa los datos a ordenar en unidades de k=2, cada grupo se divide en partes izquierda y derecha, y luego se fusiona en otro espacio en orden ascendente, y luego k*=2 repite el proceso hasta que k/2> =len entonces la clasificación está completa.

La ordenación combinada requiere un espacio adicional para almacenar los resultados combinados. Su complejidad de tiempo es la misma que la rápida y la pila, pero no hay intercambio de datos durante el proceso de clasificación, sino una copia directa de datos, lo que ahorra mucho intercambio de datos. Tiempo, pero también consume memoria adicional, por lo que es un algoritmo de clasificación típico que intercambia espacio por tiempo.

​ Nota: Si el usuario tiene requisitos elevados de velocidad de clasificación, pero no le importa el consumo de memoria, es adecuado utilizar la clasificación por fusión.

La declaración de bucle implementa la ordenación por fusión:
// 时间复杂度:O(NlogN),空间复杂度:O(N),稳定性:稳定
void merge_sort(int* arr,size_t len)
{
    
    
    // src指向的是待合并的数据,dest用于存储合并后的数据
    int *src = arr, *dest = malloc(sizeof(arr[0])*len);
    for(int k=1; k<len; k*=2)
    {
    
    
        // 以k为单位进行合并
        for(int i=0,j=i; i<len; i+=k*2)
        {
    
    
            // 计算出左边的起始位置和结束位置
            int l = i, le = i+k < len ? i+k : len;
            // 计算出右边的起始位置和结束位置
            int r = le, re = i+k*2 < len ? i+k*2 : len;
            // 左右两边的数据进行合并,谁小谁存储在dest里面,直到有一方结束
            while(l<le && r<re)
            {
    
    
                dest[j++] = src[l]<src[r] ? src[l++] : src[r++];
            }
            // 把左边剩余的数据拷贝到dest里面
            while(l<le)
                dest[j++] = src[l++];
            // 把右边剩余的数据拷贝到dest里面
            while(r<re)
                dest[j++] = src[r++];
        }
        // 合并完成后,dest中存储的就是下一次待合并的数据,所以两个指针交换
        swap(src,dest);
    }
    // 执行完swap(src,dest)后,src指向的是排序后的数据,如果src!=arr则说明排序后的数据存储在堆内在中,需要拷贝给arr
    if(src != arr)
    {
    
    
        memcpy(arr,src,sizeof(arr[0])*len);
        dest = src;
    }
    // 释放堆内存
    free(dest);
}
La función implementa recursivamente la ordenación por fusión:
void __merge_sort(int* src,int* dest,int left,int p,int right)
{
    
       
    int l = left, le = p, r = p, re = right, j=left;
    while(l<le && r<re)
    {
    
    
        dest[j++] = src[l]<=src[r]?src[l++]:src[r++];
    }
    while(l<le)
        dest[j++] = src[l++];
    while(r<re)
        dest[j++] = src[r++];
    memcpy(src+left,dest+left,sizeof(src[0])*(right-left));
}

void _merge_sort(int* src,int* dest,int left,int right)
{
    
    
    int p = (left + right) / 2;
    if(p-left>1)
        _merge_sort(src,dest,left,p);
    if(right-p>1)
        _merge_sort(src,dest,p,right);
    __merge_sort(src,dest,left,p,right);
}

void merge_sort(int* arr,size_t len)
{
    
    
    int* dest = malloc(sizeof(arr[0])*len);
    _merge_sort(arr,dest,0,len);
    free(dest);
}

9. Contar y clasificar:

Primero defina una matriz cnts que cuente la cantidad de ocurrencias de los datos e inicialice todos los miembros a 0, luego use el valor de los datos como el subíndice de la matriz y luego cuente la cantidad de ocurrencias de cada dato.

Recorriendo la matriz cnts con i=[0,max], cuando cnst[i]>0 indica que i ha aparecido, luego almacene i en la matriz para ser ordenada cnts[i], y luego se completa la clasificación.

​ Nota: La clasificación computacional tiene limitaciones relativamente grandes. Solo puede clasificar datos enteros, pero no puede clasificar datos de punto flotante ni de cadenas. Cuanto mayor sea la repetibilidad de los datos que se van a clasificar, menor será la diferencia y mayor será la velocidad. por el contrario, aunque puedes remar, pero la ganancia no vale la pena la pérdida.

Conozca el rango de datos a ordenar:
// 时间复杂度:O(N+K),空间复杂度:O(K),稳定性:稳定
void count_sort(int* arr,size_t len)
{
    
    
    int cnts[100] = {
    
    }; 
    for(int i=0; i<len; i++)
    {
    
       
        cnts[arr[i]]++;
    }

    int k = 0;
    for(int i=0; i<100; i++)
    {
    
    
        for(int j=0; j<cnts[i]; j++)
        {
    
       
            arr[k++] = i;
        }
    }
}
Se desconoce el rango de datos a ordenar:
void count_sort(int* arr,size_t len)
{
    
    
    int max = arr[0], min = arr[0];
    for(int i=1; i<len; i++)
    {
    
    
        if(arr[i] > max)
            max = arr[i];
        if(arr[i] < min)
            min = arr[i];
    }

    int* cnts = calloc(max-min+1,sizeof(arr[0]));
    for(int i=0; i<len; i++)
    {
    
    
        cnts[arr[i]-min]++;
    }

    for(int i=min,k=0; i<=max; i++)
    {
    
    
        for(int j=0; j<cnts[i-min]; j++)
        {
    
    
            arr[k] = i;
        }
    }
    free(cnts);
}

10. Clasificación de radix

Primero ordene los datos de acuerdo con el tamaño del dígito de la unidad de los datos, y luego ordene el dígito de las decenas del resultado de la clasificación, luego el dígito de las centenas, el dígito de los miles... hasta que se complete la clasificación.

​ La ventaja de usar este método de clasificación es que no hay necesidad de comparar e intercambiar los datos que se van a clasificar, por lo que su velocidad de clasificación es mucho más rápida que la clasificación normal, pero tiene grandes limitaciones. Solo puede clasificar datos enteros y datos adicionales. espacio de memoria

​ Nota: Cuando el número de dígitos de los datos es pequeño y la diferencia no es grande, los datos enteros son adecuados para la clasificación por radix.

// 时间复杂:O(n+k) 空间复杂度:O(n+k) 稳定性:稳定
void radix_sort(int* arr,size_t len)
{
    
    
    int (*radix)[len+1] = calloc(10,sizeof(arr[0])*(len+1));

    int exp = 1;
    while(exp < 1000000000)
    {
    
    
        for(int i=0; i<len; i++)
        {
    
    
            int row = arr[i]%(exp*10)/exp;
            radix[row][++radix[row][0]] = arr[i];
        }
        exp *= 10;

        int index = 0, cnt = 0;
        for(int i=0; i<10; i++)
        {
    
    
            for(int j=1; j<=radix[i][0]; j++)
            {
    
    
                arr[index++] = radix[i][j];
                if(radix[i][j] <exp)
                    cnt++;
            }
            radix[i][0] = 0;
        }
        if(cnt >= (len-1))
            break;
    }
    free(radix);
}

11. Clasificación de cubeta

La clasificación de cubos consiste en dividir los datos que se van a clasificar en diferentes rangos según el valor y almacenarlos en diferentes "cubos", luego usar otros algoritmos de clasificación para ordenar los datos en los cubos y, finalmente, fusionar los datos en los cubos para lograr el propósito de clasificar.

​ La razón de esto es que cuando los datos que se van a clasificar son relativamente grandes, afectarán el rendimiento del algoritmo de clasificación. La clasificación de cubos es para clasificar los datos y reducir el tamaño de los datos, mejorando así el rendimiento del algoritmo de clasificación. .

​ Nota: El código de este algoritmo es simple, pero es muy difícil de implementar. Requiere una comprensión suficiente de los datos, y el número en cada cubo debe ser lo suficientemente uniforme para lograr el efecto deseado.

// 时间复杂:O(n+k) 空间复杂度:O(n+k) 稳定性:未知
void bucket_sort(int* arr,size_t len)
{
    
    
    int bucket[3][len],i1=0,i2=0,i3=0;
    for(int i=0; i<len; i++)
    {
    
    
        if(arr[i] < 100)
            bucket[0][i1++] = arr[i];
        else if(arr[i] < 1000)
            bucket[1][i2++] = arr[i];
        else
            bucket[2][i3++] = arr[i];

    }
    quick_sort(bucket[0],i1);
    quick_sort(bucket[1],i2);
    quick_sort(bucket[2],i3);

    memcpy(arr,bucket[0],sizeof(arr[0])*i1);
    memcpy(arr+i1,bucket[1],sizeof(arr[0])*i2);
    memcpy(arr+i1+i2,bucket[2],sizeof(arr[0])*i3);
}

=0; yo<len; i++)
{ if(arr[i] < 100) cubeta[0][i1++] = arr[i]; else if(arr[i] < 1000) cubo[1][i2++] = arr[i]; else cubeta[2][i3++] = arr[i];





}
quick_sort(bucket[0],i1);
quick_sort(bucket[1],i2);
quick_sort(bucket[2],i3);

memcpy(arr,bucket[0],sizeof(arr[0])*i1);
memcpy(arr+i1,bucket[1],sizeof(arr[0])*i2);
memcpy(arr+i1+i2,bucket[2],sizeof(arr[0])*i3);

}


Supongo que te gusta

Origin blog.csdn.net/m0_62480610/article/details/126656428
Recomendado
Clasificación