Comprender el algoritmo de ordenación del montón

    En este tutorial, aprenderá cómo funciona el algoritmo de ordenación del montón. Además, encontrará ejemplos que utilizan lenguaje C.
    La clasificación de pilas es un algoritmo de clasificación popular y eficaz en la programación de computadoras. Aprender a escribir un algoritmo de clasificación de montones requiere la comprensión de dos tipos de estructuras de datos: matrices y árboles.
    Almacenamos el conjunto inicial de números que se ordenarán en una matriz, como [10, 3, 76, 34, 23, 32]. Después de ordenar, obtenemos una matriz ordenada [3,10,23,32,34, 76 ]. El principio de funcionamiento de la ordenación del montón es visualizar los elementos de una matriz como un árbol binario completo de un tipo especial llamado montón.

Relación entre el índice de la matriz y los elementos del árbol

    Un árbol binario completo tiene una propiedad interesante, podemos usarlo para encontrar el nodo hijo y el nodo padre de cualquier nodo.
    Si el índice de cualquier elemento de la matriz es i, el elemento del índice 2i + 1 se convertirá en el hijo izquierdo y el elemento del índice 2i + 2 se convertirá en el hijo derecho. Además, el elemento padre de cualquier elemento en el índice i viene dado por el límite inferior de (i-1) / 2.
Inserte la descripción de la imagen aquí
    Vamos a probarlo

Left child of 1 (index 0)
= element in (2*0+1) index 
= element in 1 index 
= 12


Right child of 1
= element in (2*0+2) index
= element in 2 index 
= 9

Similarly,
Left child of 12 (index 1)
= element in (2*1+1) index
= element in 3 index
= 5

Right child of 12
= element in (2*1+2) index
= element in 4 index
= 6

    También necesitamos confirmar que las reglas se aplican para encontrar el nodo principal de cualquier nodo.

Parent of 9 (position 2) 
= (2-1)/2 
= ½ 
= 0.5
~ 0 index 
= 1

Parent of 12 (position 1) 
= (1-1)/2 
= 0 index 
= 1

    Comprender esta asignación de índices de matriz a posiciones de árbol es fundamental para comprender cómo funciona la estructura de datos del montón y cómo usarla para implementar la clasificación del montón.

¿Qué es la estructura de datos del montón?

    Heap es una estructura de datos especial basada en árboles. Si se cumplen las siguientes condiciones, se dice que el árbol binario sigue la estructura de datos del montón:

  • Es un árbol binario completo
  • Todos los nodos del árbol siguen la propiedad de ser mayores que los nodos secundarios, es decir, el elemento más grande está en la raíz, todos los nodos secundarios son menores que el nodo raíz, etc. Tal montón se llama el montón más grande. Por el contrario, si todos los nodos son más pequeños que sus nodos secundarios, se denomina montón mínimo.

    El siguiente gráfico de muestra muestra el montón máximo y el montón mínimo.
Inserte la descripción de la imagen aquí

Cómo "apilar" un árbol

    A partir de un árbol binario completo, podemos modificarlo al máximo montón ejecutando una función llamada heapify en todos los elementos no hoja del montón.
    Debido a que heapify usa la recursividad, es difícil de entender. Entonces, consideremos primero cómo apilar un árbol con tres elementos.

heapify(array)
    Root = array[0]
    Largest = largest( array[0] , array [2*0 + 1]. array[2*0+2])
    if(Root != Largest)
          Swap(Root, Largest)

Inserte la descripción de la imagen aquí
    El ejemplo anterior muestra dos situaciones: en un caso, la raíz es el elemento más grande y no necesitamos realizar ninguna operación; en el otro caso, hay un elemento secundario más grande en la raíz y debemos cambiar a mantener el atributo de montón máximo.
    Si ha utilizado un algoritmo recursivo antes, es posible que haya determinado que esta debe ser la situación básica.
    Consideremos ahora otro escenario donde hay múltiples niveles.
Inserte la descripción de la imagen aquí
    El elemento superior no es el montón más grande, pero todos los subárboles son el montón más grande.
    Para mantener la propiedad de montón máxima de todo el árbol, debemos continuar presionando 2 hasta que alcance la posición correcta.
Inserte la descripción de la imagen aquí
    Por lo tanto, para mantener el atributo de montón máximo en un árbol donde ambos subárboles son el montón más grande, necesitamos ejecutar heapify repetidamente en el elemento raíz hasta que sea más grande que su elemento hijo o se convierta en un nodo hoja.
    Podemos combinar estas dos condiciones en una función de heapify:

void heapify(int arr[], int n, int i) {
    
    
  // Find largest among root, left child and right child
  int largest = i;
  int left = 2 * i + 1;
  int right = 2 * i + 2;

  if (left < n && arr[left] > arr[largest])
    largest = left;

  if (right < n && arr[right] > arr[largest])
    largest = right;

    // Swap and continue heapifying if root is not largest
    if (largest != i) {
    
    
      swap(&arr[i], &arr[largest]);
      heapify(arr, n, largest);
  }
}

    Esta función es adecuada para casos básicos y árboles de cualquier tamaño. Por lo tanto, siempre que el subárbol sea el montón más grande, podemos mover el elemento raíz a la posición correcta para mantener el estado de montón más grande de cualquier árbol.

Construye el montón máximo

    Para construir un montón máximo a partir de cualquier árbol, podemos acumular cada subárbol de abajo hacia arriba y obtener un montón máximo después de aplicar la función a todos los elementos, incluido el elemento raíz.
    En el caso de un árbol completo, el primer índice de un nodo no hoja viene dado por n / 2-1. Todos los demás nodos posteriores son nodos hoja, por lo que no es necesario acumular.
    Por lo tanto, podemos construir un montón máximo:

    // Build heap (rearrange array)
    for (int i = n / 2 - 1; i >= 0; i--)
      heapify(arr, n, i);

Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
    Como se muestra en la figura anterior, primero acumulamos el árbol más pequeño y luego nos movemos gradualmente hacia arriba hasta llegar al elemento raíz.
    Si ha aprendido todo hasta ahora, felicidades, está en camino de dominar la clasificación de pilas.

¿Cómo funciona la clasificación en pila?
  1. Dado que el árbol satisface la propiedad de montón máxima, el elemento más grande se almacena en el nodo raíz.
  2. Intercambiar: elimine el elemento raíz y colóquelo al final de la matriz (la enésima posición), y coloque el último elemento del árbol (montón) en una posición vacía.
  3. Eliminar: reduce el tamaño del montón en 1.
  4. Heapify: Heapify el elemento raíz nuevamente para que haya el elemento más alto en la raíz.
  5. Repita este proceso hasta que todos los elementos de la lista estén ordenados.
    Inserte la descripción de la imagen aquí

    El siguiente código muestra la operación.

    // Heap sort
    for (int i = n - 1; i >= 0; i--) {
    
    
      swap(&arr[0], &arr[i]);

      // Heapify root element to get highest element at root again
      heapify(arr, i, 0);
    }
C ejemplo
// Heap Sort in C
  
  #include <stdio.h>
  
  // Function to swap the the position of two elements
  void swap(int *a, int *b) {
    
    
    int temp = *a;
    *a = *b;
    *b = temp;
  }
  
  void heapify(int arr[], int n, int i) {
    
    
    // Find largest among root, left child and right child
    int largest = i;
    int left = 2 * i + 1;
    int right = 2 * i + 2;
  
    if (left < n && arr[left] > arr[largest])
      largest = left;
  
    if (right < n && arr[right] > arr[largest])
      largest = right;
  
    // Swap and continue heapifying if root is not largest
    if (largest != i) {
    
    
      swap(&arr[i], &arr[largest]);
      heapify(arr, n, largest);
    }
  }
  
  // Main function to do heap sort
  void heapSort(int arr[], int n) {
    
    
    // Build max heap
    for (int i = n / 2 - 1; i >= 0; i--)
      heapify(arr, n, i);
  
    // Heap sort
    for (int i = n - 1; i >= 0; i--) {
    
    
      swap(&arr[0], &arr[i]);
  
      // Heapify root element to get highest element at root again
      heapify(arr, i, 0);
    }
  }
  
  // Print an array
  void printArray(int arr[], int n) {
    
    
    for (int i = 0; i < n; ++i)
      printf("%d ", arr[i]);
    printf("\n");
  }
  
  // Driver code
  int main() {
    
    
    int arr[] = {
    
    1, 12, 9, 5, 6, 10};
    int n = sizeof(arr) / sizeof(arr[0]);
  
    heapSort(arr, n);
  
    printf("Sorted array is \n");
    printArray(arr, n);
  }
Complejidad de clasificación de montón

    La clasificación de montón tiene una complejidad de tiempo O (nlog n) para todos los casos (mejor caso, caso promedio y peor caso).
    Entendamos por qué. La altura de un árbol binario completo que contiene n elementos es log n.
    Como hemos visto antes, para apilar completamente el elemento del subárbol que ya es el montón más grande, necesitamos comparar constantemente el elemento con sus elementos secundarios izquierdo y derecho y empujarlo hacia abajo hasta que alcance el punto correspondiente, momento en el que es Ambos elementos secundarios son más pequeños que él.
    En el peor de los casos, necesitamos mover el elemento de la raíz al nodo hoja para comparar e intercambiar múltiplos de log (n).
    En la etapa de construcción del montón máximo, realizamos esta operación en n / 2 elementos, por lo que la complejidad del peor caso del paso de construir el montón es n / 2 * log n ~ nlog n.
    En el paso de clasificación, intercambiamos el elemento raíz con el último elemento y apilamos el elemento raíz. Para cada elemento, toma como máximo log n tiempo, porque es posible que tengamos que intercambiar el elemento de la raíz a la hoja. Entonces, repetimos n veces, el tiempo empleado en el paso de clasificación del montón es nlog n.
    Además, dado que los pasos para construir el montón máximo (build_max_heap) y la clasificación del montón (heap_sort) se ejecutan uno tras otro, la complejidad del algoritmo no aumentará exponencialmente, sino que permanecerá en el nivel de nlog n.
    La complejidad espacial de la clasificación de montones es O (1). En comparación con la ordenación rápida, su peor caso es mejor, que es O (nlog n). El peor caso de ordenación rápida es O ( n 2 n ^ 2norte2 ). Pero en otros casos, la clasificación rápida es más rápida. Introsort es una alternativa a la clasificación de montón. Combina clasificación rápida y clasificación de montón para conservar las ventajas de ambos: la velocidad de clasificación de montón en el peor de los casos y la velocidad media de clasificación rápida.

Aplicación de clasificación de pila

    Los sistemas relacionados con la seguridad y los sistemas embebidos (como el kernel de Linux) utilizan la clasificación del montón, porque el tiempo de ejecución de la clasificación del montón tiene un límite superior de O (nlogn) y el límite superior del almacenamiento auxiliar es O (1) constante.
    Aunque la ordenación del montón tiene una complejidad de tiempo O (nlogn) incluso en el peor de los casos, no tiene más aplicaciones (en comparación con otros algoritmos de ordenación, como la ordenación rápida y la ordenación combinada). Sin embargo, si queremos extraer los datos más pequeños (o más grandes) de la lista de elementos sin considerar el orden de los elementos restantes, podemos usar la estructura de datos básica, heap. Por ejemplo, colas de prioridad.

Documentos de referencia

[1] Parewa Labs Pvt. Ltd. Algoritmo de clasificación en pila [EB / OL] .https: //www.programiz.com/dsa/heap-sort,2021-01-01.

Supongo que te gusta

Origin blog.csdn.net/zsx0728/article/details/115019784
Recomendado
Clasificación