Estructura de datos: clasificación

1. Clasificación por inserción

1.1 Clasificación por inserción directa - Principio:

        El intervalo completo se divide en: 1. intervalo ordenado; 2. intervalo desordenado; cada vez que se selecciona el primer elemento del intervalo desordenado, se selecciona una posición adecuada para insertar en el intervalo ordenado.

        

1.2 Implementación:

public class InsertSort {

    public static void sort(int[] array){
        // 一共要取多少个元素来进行插入过程(无序区间里有多少个元素)
        for (int i = 0; i < array.length - 1; i++) {
            // 有序区间 [0, i]  至少在 i == 0 的时候得有一个元素
            // 无序区间 [i + 1, n)

            // 先取出无序区间的第一个元素,记为 k
            int k = array[i + 1];

            // 从后往前,遍历有序区间[0,i]
            // 找到属于无序第一个元素,即k的位置。
            int j = i;
            for (; j >= 0 && k < array[j]; j--) {
                array[j + 1] = array[j];        // 将不符合条件的数据往后般一格
            }

            array[j + 1] = k;
        }
    }
}

1.3 Análisis de rendimiento: los datos se pueden mantener estables. Clasificación por inserción, cuanto más cerca estén los datos iniciales de la orden, mayor será la eficiencia del tiempo.

1.4 Ordenar por inserción por la mitad (comprender)

Al seleccionar la posición donde se deben insertar los datos en un intervalo ordenado, se puede utilizar la idea de búsqueda a la mitad debido al orden del intervalo.

    public static void bsInsertSort(int[] array) {

        for (int i = 1; i < array.length; i++) {
            
            int v = array[i];
            int left = 0;
            int right = i;
            // [left, right)
            // 需要考虑稳定性
            while (left < right) {

                int m = (left + right) / 2;
                if (v >= array[m]) {
                    left = m + 1;
                } else {
                    right = m; 
                }
            }
            // 搬移
            for (int j = i; j > left; j--) {
                array[j] = array[j - 1];
            }
            array[left] = v;
        }
    }

2. Clasificación de colinas

2.1 Principio: [Idea de algoritmo] Primero, divida la secuencia de registros a clasificar en varias subsecuencias "más dispersas" y realice una clasificación por inserción directa, respectivamente. Después del ajuste aproximado anterior, los registros en toda la secuencia están básicamente en orden y, finalmente, todos los registros se insertan y clasifican directamente.
        ①Primero, la distancia entre los registros se selecciona como di (i=1), y en toda la secuencia de registros a ordenar, todos los registros con un intervalo de d1 se agrupan en un grupo, y el grupo se inserta y ordena directamente.
        ②Luego tome i=i+1, la distancia entre los registros es di ( di < d(i-1) ), en toda la secuencia de registros a ordenar, todos los registros con un intervalo de di se agrupan en un grupo y se insertan directamente en el tipo de grupo.
        Repita el paso ② varias veces hasta que la distancia entre registros sea di = 1. En este momento, solo hay una subsecuencia, y la secuencia se inserta y clasifica directamente para completar todo el proceso de clasificación.

2.2 Implementación

public class ShellSort {
    public static void sort(int[] a){
        //1.根据a的长度确定增长量h
        int h = 1;
        while (h < a.length/2){
            h = 2*h+1;
        }
        //2.希尔排序
        while ( h>=1 ){
           //排序:找到待插入的元素,
            for (int i = h; i < a.length; i++) {
                for (int j = i; j >= h ; j-=h) {
                    if (a[j-h]>a[j]){
                        //交换元素
                        swap(a,j-h,j);
                    }else {//j-h 比 j 小,不用交换。
                        break;
                    }
                }
            }
            h=h/2;
        }
    }
    private static void swap(int[] a, int i, int j){
        int emp;
        emp = a[i];
        a[i] = a[j];
        a[j] = emp;
    }
}

2.3 Estabilidad: Inestable

3. Clasificación de selección

3.1 Clasificación por selección directa - Principio

[Idea de algoritmo]
En la primera clasificación de selección simple, comenzando desde el primer registro, a través de comparaciones de n-1 palabras clave, seleccione el registro con la palabra clave más pequeña de los n registros e intercámbielo con el primer registro.
En la segunda clasificación de selección simple, a partir del segundo registro, a través de comparaciones de n-2 palabras clave, seleccione el registro con la palabra clave más pequeña de los n-1 registros e intercámbielo con el segundo registro.
En el i-ésimo ordenamiento por selección simple, a partir del i-ésimo registro, a través de ni comparaciones de palabras clave, seleccione el registro con la palabra clave más pequeña de los registros n-i+1 e intercámbielo con el i-ésimo registro.
De esta manera, después de n-1 veces de clasificación de selección simple, se colocarán n-1 registros en su lugar, y el registro más pequeño se deja directamente al final, por lo que se requieren un total de n-1 veces de clasificación de selección simple.

3.2 Implementación

public class SelectSort {

 public static void sort(int[] array){

     for (int i = 0; i < array.length-1; i++) {
         int k = i;
         for (int j = i+1; j < array.length; j++) {
             if (array[j] < array[k]){
                 k = j;
             }
         }
         swap(array,k,i);
     }
 }

    private static void swap(int[] a, int i, int j){
        int emp;
        emp = a[i];
        a[i] = a[j];
        a[j] = emp;
    }
}

3.3 Estabilidad: Inestable

3.4 Clasificación de selección bidireccional (comprensión)

Seleccione los elementos más pequeños + más grandes del intervalo desordenado cada vez, y guárdelos al principio y al final del intervalo desordenado hasta que se agoten todos los elementos de datos que se ordenarán.

public static void selectSort(int[] array) {
    
    for (int i = 0; i < array.length - 1; i++) {

        // 无序区间: [0, array.length - i)
        // 有序区间: [array.length - i, array.length)
        int max = 0;
        for (int j = 1; j < array.length - i; j++) {
        
            if (array[j] > array[max]) {
            max = j;
        }
    }

    int t = array[max];
    array[max] = array[array.length - i - 1];
    array[array.length - i - 1] = t;

    }
}

4. Clasificación de montón

4.1 Principio El principio básico también es la ordenación por selección, pero en lugar de usar el recorrido para encontrar el número más grande en el intervalo desordenado, el montón se usa para seleccionar el número más grande en el intervalo desordenado.

[Idea de algoritmo]
① Cree el montón inicial de los registros que se ordenarán de acuerdo con la definición del montón (algoritmo 9.9), y genere el elemento superior del montón.
② Ajuste la secuencia de registro restante, use el método de selección para volver a seleccionar los primeros elementos ni en un montón nuevo y luego envíe el elemento superior del montón.
③ Repita el paso ② para filtrar n-1 veces, el montón recién filtrado se hará cada vez más pequeño y las
palabras clave ordenadas detrás del nuevo montón serán cada vez más, y finalmente la secuencia de registros que se ordenarán se convierte en una secuencia ordenada. , este proceso se denomina ordenación en montón.
El proceso de dibujo es bastante engorroso, y puede dibujarlo y comprenderlo usted mismo de acuerdo con las ideas y los códigos.

4.2 Implementación

public class HeapSort {
    
    public static void sort(int[] array){
        //建初堆:升序建大堆,降序建小堆。
        for (int i = (array.length-2)/2; i >=0 ; i--) {
            shiftDown(array,array.length,i);
        }
        //维护堆:堆顶元素与最后一个元素交换后,堆顶的“堆性质”被破环,需要维护。此时维护的堆大小应该是依次减小的。
        for (int i = 0; i < array.length-1; i++) {
            swap(array,0,array.length-i-1);
            shiftDown(array,array.length-i-1,0);
        }
    }

    private static void shiftDown(int[] array, int length, int index) {

        while (index*2+1 < length){
            int left = index*2+1;
            int right = left+1;
            int max = left;

            if (right < length && array[left] < array[right]){
                max = right;
            }

            if (array[index] >= array[max]){
                return;
            }
            swap(array,index,max);
            index = max;
        }
    }

    private static void swap(int[] a, int i, int j){
        int emp;
        emp = a[i];
        a[i] = a[j];
        a[j] = emp;
    }
}

4.3 Estabilidad: Inestable

5. Clasificación de burbujas

5.1 Principio: En un intervalo desordenado, a través de la comparación de números adyacentes, el número más grande se burbujea al final del intervalo desordenado, y este proceso continúa hasta que la matriz se ordena como un todo.

5.2 Implementación:

public class BubbleSort{

    public void sort(long[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            boolean sorted = true;
            for (int j = 0; j < array.length - i - 1; j++) {
                if (array[j] > array[j + 1]) {
                    SortUtil.swap(array, j, j + 1);
                    sorted = false;
                }
            }
            if (sorted) {
                return;
            }
        }
    }
}

5.3 Estabilidad: Estable

6. Clasificación rápida (importante)

6.1 Principio - Descripción general

        1. Seleccione un número del rango que se ordenará como valor pivote;

        2. Partición: recorra todo el intervalo que se va a clasificar, coloque los menores que el valor de referencia (que pueden incluir iguales) a la izquierda del valor de referencia y los mayores que el valor de referencia (que pueden incluir iguales) a la izquierda. parte inferior del valor de referencia

derecho;

        3. Usando la idea de divide y vencerás, las celdas izquierda y derecha se procesan de la misma manera, hasta que la longitud de la celda == 1, lo que significa que se ha ordenado, o la longitud de la celda == 0, lo que significa no hay datos.

6.2 Principio - partición: La esencia de la ordenación rápida es la operación de partición. Hay muchas maneras de lograr esta operación. El punto general es dividir los datos de acuerdo con el pivote.

6.3 Estabilidad: Inestable

6.4 Principio - Selección de valores de referencia

        1. Seleccione el borde (izquierdo o derecho)

        2. Selección aleatoria

        3. Tome el medio de algunos números (por ejemplo, tome el medio de tres números): array[left], array[mid], array[right] El tamaño del medio es el valor de referencia

6.5 Implementación del código:



public class QuickSort {
    public static void sort(int[] array) {
        
        quickSortRange(array,0,array.length-1);
    }

    // 为了代码书写方便,我们选择使用左闭右闭的区间表示形式
    // from,to 下标的元素都算在区间的元素中
    // 左闭右闭的情况下,区间内的元素个数 = to - from + 1;
    private static void quickSortRange(int[] array, int from, int to) {

        if (to - from +1 <= 1) {
            // 区间中元素个数 <= 1 个
            return;
        }

        // 挑选中区间最右边的元素 array[to],
        //int pi = partitionMethodA(array, from, to);
        //经过该步处理后数组array中的数据呈现: [from,pi)的元素是小于 pivot ;(pi,array.length-1]元素是大于 pivot ;
        //pivot == array[pi];
        // 按照分治算法的思路,使用相同的方式,处理相同性质的问题,只是问题的规模在变小
        int[] index = partitionD(array,from,to);
        int left = index[0];
        int right = index[1];
        quickSortRange(array, from, left);    // 针对小于等于 pivot 的区间做处理
        quickSortRange(array, right, to);   // 针对大于等于 pivot 的区间做处理
    }

    /**
     * 以区间最右边的元素 array[to] 最为 pivot,遍历整个区间,从 from 到 to,移动必要的元素
     * 进行分区
     * @param array
     * @param from
     * @param to
     * @return 最终 pivot 所在的下标
     */

    /*
        <= pivot: [from,left];
        > pivot : [right,to];
        未比较 :   (left,right);
     */
    private static int partitionA(int[] array, int from, int to) {
        int left = from;
        int right = to;
        int pivot = array[to];
        while (left < right){
            while (left < right && array[left] <= pivot){
                left++;
            }
            while(left < right && array[right] >= pivot){
                right--;
            }
            swap(array,left,right);
        }
        swap(array,left,to);
        return left;

    }

    /*
        <= pivot: [from,left];
        > pivot : [right,to];
        未比较 :   (left,right);

     */
    public static int partitionB(int[] array, int from, int to){
        int pivot = array[to];
        int left = from;
        int right = to;

        while(left < right){
            while(left < right && array[left] < pivot){
                left++;
            }
            array[right] = array[left];
            while(left < right && array[right] > pivot){
                right--;
            }
            array[left] = array[right];
        }
        array[left] = pivot;
        return left;
    }

    /**
     * 对 array 的 [from, to] 区间进行分区
     * 分区完成之后,区间被分割为 [<= pivot] pivot [>= pivot]
     * 分区过程中,始终保持
     * [from, s)    小于 pivot
     * [s, i)       大于等于 pivot
     * [i, to)      未比较过的元素
     * [to, to]     pivot
     * @param array
     * @param from
     * @param to
     * @return pivot 最终所在下标
     */
    public static int partitionC(int[] array,int from,int to){

        int s = from;
        int pivot = array[to];
        for (int i = from; i < to; i++) {   // 遍历 [from, to)
            // 这里加 == 号也保证不了稳定性,有交换操作
            if (array[i] < pivot) {
                // TODO: 可以进行简单的优化:如果 i == s,就不交换
                swap(array,i,s);
                s++;
            }
        }

        array[to] = array[s];
        array[s] = pivot;

        return s;
    }

    public static int[] partitionD(int[] array,int from,int to){

        int s = from;
        int i = from;
        int g = to;

        int pivot = array[to];
        while (g-i+1 > 0){
            if (array[i] == pivot){
                i++;
            }else if (array[i] < pivot){
                swap(array,s,i);
                s++;i++;
            }else {
                swap(array,g,i);
                g--;
            }
        }
        return new int[] {s-1,i};
    }

    public static int partitionE(int[]array,int left,int right){
        int d = left + 1;
        int pivot = array[left];

        for (int i = left+1; i <=right ; i++) {
            if(array[i] < pivot) {
                swap(array,i,d);
                d++;
            }
        }
        swap(array,d,left);
        return d;
    }


    private static void swap(int[] a, int i, int j){
        int emp;
        emp = a[i];
        a[i] = a[j];
        a[j] = emp;
    }
}

6.7 Resumen de optimización

        1. Es muy importante elegir el valor de referencia, generalmente use algunos números para tomar el método medio

        2. Durante el proceso de partición, también se selecciona el número igual al valor de referencia

        3. Cuando el rango a ordenar es menor que un umbral, use la ordenación por inserción directa

6.8 Resumen

        1. Seleccione un valor de referencia en el rango que se ordenará

                1. Seleccione izquierda o derecha

                2. Seleccionado al azar

                3. Chino y francés por algunos números.

        2. Haz una partición para que el número pequeño quede a la izquierda y el número grande a la derecha

                1. hoare

                2. Cava un hoyo

                3. Traverse hacia adelante y hacia atrás

                4. Seleccione también los que tienen el mismo valor de referencia (entender)

        3. Divida y conquiste las celdas izquierda y derecha hasta que el número de celdas sea inferior a un umbral, utilice la ordenación por inserción

7. Combinar clasificación (importante)

7.1 Principio - Descripción general: La ordenación por fusión (MERGE-SORT) es un algoritmo de clasificación eficiente basado en la operación de fusió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 ordene cada subsecuencia y luego ordene los segmentos de la subsecuencia. Si dos listas ordenadas se fusionan en una lista ordenada, se denomina fusión bidireccional.

7.2 Principio: fusión de dos matrices ordenadas

7.3 Implementación:

public class MergeSort {
    private static int[] assist;
    
    public static void sort(int[] array) {
        assist = new int[array.length];
        int lo = 0, hi = array.length - 1;
        sort(array, lo, hi);
    }

    private static void sort(int[] array, int lo, int hi) {
        if (hi <= lo) return;
        int mid = lo + (hi - lo) / 2;
        sort(array, lo, mid);
        sort(array, mid + 1, hi);//以上两步均为分组,
        merge(array, lo, mid, hi);//将array中从 lo 到 hi 的元素合并为有序数组。
    }

    private static void merge(int[] array, int lo, int mid, int hi) {
        int i = lo, p1 = lo, p2 = mid + 1;//三个指针
        while (p1 <= mid && p2 <= hi) {
            if (array[p1] < array[p2]) {
                assist[i++] = array[p1++];
            } else {
                assist[i++] = array[p2++];
            }
        }
        while (p1 <= mid) {
            assist[i++] = array[p1++];
        }
        while (p2 <= hi) {
            assist[i++] = array[p2++];
        }
        System.arraycopy(assist, lo, array, lo, hi - lo + 1);
    }

    private static void exchange(int[] a, int i, int j) {
        int emp;
        emp = a[i];
        a[i] = a[j];
        a[j] = emp;
    }
}

7.4 Estabilidad: Estable

7.5 Resumen de optimización

        Reutilice las dos matrices durante el proceso de clasificación, lo que reduce el proceso de copia de elementos.

8. Clasificación externa

         En los diversos métodos de clasificación discutidos anteriormente, los registros a clasificar y la información relacionada se almacenan en la memoria, y todo el proceso de clasificación se completa en la memoria y no implica el intercambio de almacenamiento de datos interno y externo, que
es denominados colectivamente clasificación interna. Si el número de registros a ordenar es tan grande que es imposible transferir la memoria a la vez, todo el proceso de clasificación debe completarse utilizando la memoria externa para transferir personas en lotes. Este tipo de clasificación se denomina clasificación externa
. Esta sección presentará principalmente la idea básica de los métodos de clasificación externa basados ​​en dispositivos de acceso directo (almacenamiento en disco) y dispositivos de acceso secuencial (almacenamiento en cinta).

8.1 El método básico de clasificación externa

        El método de clasificación externo más utilizado es la clasificación por combinación. El método consta de dos etapas: en la primera etapa, los registros que se van a clasificar se leen en la memoria por lotes, el archivo se ingresa en el contenido segmento por segmento y cada segmento del archivo se clasifica mediante un
método El segmento de archivo ordenado se llama Secuencia (o segmento de fusión), cuando se generan, se escriben en la memoria externa en forma de subarchivos, por lo que muchas
secuencias iniciales se forman en la memoria externa, la segunda etapa es la fusión multidireccional de subarchivos Cierto método de fusión (como el método de fusión bidireccional) realiza fusiones múltiples, de modo que la longitud de la secuencia aumenta gradualmente de pequeña a grande hasta que se convierte en una secuencia, es decir, se ordena todo el expediente. La memoria externa, como la cinta y el disco, se puede utilizar para la clasificación externa. La longitud del archivo en serie formado inicialmente depende del
tamaño del área de clasificación proporcionada por la memoria y la estrategia de clasificación inicial. El número de rutas de combinación depende del número de dispositivos externos que se pueden proporcionar.

9. Ordenar resumen:

9.1 generalmente se divide en los siguientes tipos:

9.2 Compare varios métodos de clasificación a partir de tres aspectos: la complejidad de tiempo promedio del algoritmo, la complejidad de tiempo peor y el espacio de almacenamiento auxiliar requerido por el algoritmo.

método de clasificación Complejidad de tiempo promedio Peor sí oro complejidad Espacio de almacenamiento secundario
método de clasificación simple En 2) En 2)         O(1)
ordenación rápida       O(n registro 2n) En 2) O (log2n)
ordenar en montón O(n registro 2n) O(n registro 2n) O(1)
ordenar por fusión O(n registro 2n) O(n registro 2n) Sobre)

9.3 Análisis completo y comparación de varios métodos de clasificación, se pueden sacar las siguientes conclusiones:
        ① El método de clasificación simple generalmente solo se usa cuando n es pequeño (por ejemplo, n<30). Cuando los registros en la secuencia están "básicamente ordenados", la clasificación por inserción directa es el mejor método de clasificación. Si los datos en el registro son grandes, se debe usar el método de clasificación de selección simple con menos movimientos.
        ②La complejidad de tiempo promedio de la clasificación rápida, la clasificación en montón y la clasificación combinada son todos O(nlog_n), pero los resultados experimentales muestran que, en términos de rendimiento de tiempo promedio, la clasificación rápida es el mejor de todos los métodos de clasificación. Desafortunadamente, el rendimiento de tiempo de Quicksort en el peor de los casos es O(㎡). La peor complejidad de tiempo de la ordenación en montón y la ordenación en combinación sigue siendo O(nlogan).
        ③ El método de clasificación simple se puede utilizar en combinación con el método de clasificación con un mejor rendimiento. Por ejemplo, en la ordenación rápida, cuando la longitud de los subintervalos divididos es inferior a un cierto valor, se puede llamar al método de ordenación por inserción directa en su lugar; o la secuencia que se va a ordenar se divide en varias subsecuencias y se realiza la ordenación por inserción directa. respectivamente, y luego se utiliza el método merge sort para fusionar subsecuencias ordenadas en una secuencia ordenada completa.
        ④La complejidad temporal del tipo radix se puede escribir como O(dn). Por lo tanto, es más adecuado para secuencias con valores grandes de n y un número pequeño de dígitos de la clave, d. Cuando d es mucho menor que n, su complejidad temporal es cercana a O(n)
        ⑤Desde el punto de vista de la estabilidad de la clasificación, entre todos los métodos de clasificación simple, la clasificación de selección simple es inestable y otros métodos de clasificación simple son estables. Sin embargo, entre los métodos de clasificación con mejor rendimiento de tiempo, la clasificación Hill, la clasificación rápida y la clasificación en montón son inestables, solo la clasificación por fusión y la clasificación por base son estables.
        En resumen, cada método de clasificación tiene sus propias características y ningún método es absolutamente óptimo. Se debe seleccionar el método de clasificación apropiado de acuerdo con la situación específica, o se puede usar una combinación de métodos.

Supongo que te gusta

Origin blog.csdn.net/weixin_52575498/article/details/124063085
Recomendado
Clasificación