Montón y cola de prioridad

montón

1. Definición del montón:

Si la clave de cualquier nodo de este árbol binario completo es menor o igual que las claves de sus hijos izquierdo y derecho, se denomina montón raíz pequeño; de lo contrario, es un montón raíz grande. Como:

De hecho, cuando se almacena el montón, no se almacena en una estructura de árbol, sino en forma de matriz. ¿Cómo entenderlo?

En pocas palabras, el montón se representa lógicamente como un árbol binario completo y se representa físicamente como una matriz. Como:

Existe una asociación entre el árbol y el arreglo: los subíndices del padre (padre), el hijo izquierdo (izquierda) y el hijo derecho (derecho) en el arreglo del árbol satisfacen la relación: (1) El subíndice padre del se conoce el padre en la matriz, luego los subíndices correspondientes del hijo izquierdo y el hijo derecho en la matriz:

                                izquierda = 2 * padre + 1;

                                derecho = 2 * padre + 2;

                 (2) Conociendo el subíndice child del niño (independientemente del niño izquierdo o del niño derecho): padre = (niño - 1) / 2;

Esta matriz también se puede ver como un recorrido de orden de nivel del árbol binario completo anterior.

2. El funcionamiento del montón

        2.1 Reconstruir el montón: Pregunta: ¿Cómo reconstruir el montón cuando cambia el registro superior del montón? Tomemos el pequeño montón como ejemplo:
        [Idea de algoritmo] Primero elimine el registro en el nodo raíz del árbol binario completo correspondiente al montón, que se llama para ser ajustado Registro.

        En este momento, el nodo secundario original con una palabra clave más grande es equivalente a un nodo vacío, y se selecciona un registro con una palabra clave más pequeña de los subárboles izquierdo y derecho del nodo vacío. Si la palabra clave del registro aún es más pequeña que la una clave de registro que se va a ajustar, mueva el registro hasta el nodo vacío.
        El proceso de movimiento anterior se repite hasta que las palabras clave de los subárboles izquierdo y derecho del nodo vacío sean más pequeñas que las palabras clave de los registros que se van a ajustar. En este punto, el
registro que se va a ajustar se puede colocar en un nodo vacío.
        El método de ajuste anterior es equivalente al proceso de "filtrado" gradual de los registros que se van a ajustar a la baja, por lo que generalmente se denomina método de "filtro" o "ajuste a la baja".

        Usamos código para explicar:

public static void shiftDown(long[] array, int size, int index) {
        // index代表要调整的位置。size为堆的大小。

        // 
        while (true) {
            // 1. 判断 index 所在位置是不是叶子
            // 逻辑上,没有左孩子一定就是叶子了(因为完全二叉树这个前提)
            int left = 2 * index + 1;
            if (left >= size) {
                // 越界 -> 没有左孩子 -> 是叶子 -> 调整结束
                return; // 循环的出口一:走到的叶子的位置
            }

            // 2. 找到两个孩子中的最值【最小值 via 小堆】
            // 先判断有没有右孩子
            int right = left + 1;       // right = 2 * index + 2
            int min = left;             // 假设最小值就是左孩子,所以 min 保存的最小值孩子所在的下标
            if (right < size && array[right] < array[left]) {
                // right < size 必须在 array[right] < array[left] 之前,不能交换顺序
                // 因为先得确定有右孩子,才有比较左右孩子的意义
                // 有右孩子为前提的情况下,然后右孩子的值 < 左孩子的值
                min = right;            // min 应该是右孩子所在的下标
            }

            // 3. 将最值和当前要调整的位置进行比较,判断是否满足堆的性质
            if (array[index] <= array[min]) {
                // 当前要调整的结点的值 <= 最小的孩子值;说明这里也满足堆的性质了,所以,调整结束
                return; // 循环的出口一:循环期间,已经满足堆的性质了
            }

            // 4. 交换两个值,物理上对应的就是数组的元素交换 min 下标的值、index 下标的值
            long t = array[index];
            array[index] = array[min];
            array[min] = t;

            // 5. 再对 min 位置重新进行同样的操作(对 min 位置进行向下调整操作)
            index = min;
        }
    }

  El proceso es el siguiente:   

         2.2 Construyendo el montón inicial: Pregunta: ¿Cómo construir el montón inicial a partir de una secuencia arbitraria?
       [Idea de algoritmo] Una secuencia arbitraria se considera como un árbol binario completo correspondiente. Dado que los nodos de hoja se pueden considerar como un montón de un solo elemento
, el algoritmo de montón de ajuste mencionado anteriormente (método de "detección") se puede usar repetidamente, y todos los subárboles se ajustan a montones hasta que
todo el árbol binario completo se ajusta a montones.
        Se puede demostrar que en el árbol binario completo anterior, el último nodo que no es hoja se encuentra en la posición Ln/2J, y n es el número de nodos del árbol binario
. Por lo tanto, la "detección" debe comenzar desde el nodo Ln/2Jth y retroceder capa por capa hasta el nodo raíz.

public static void buildHeap (int[] array) {
        //我们假定传入的数组是经过处理的,即数组内的元素个数就是堆的元素个数。
        //通过二叉树可以观察到只需要从最后一个节点的双亲结点开始从底向上进行向下调整
        for (int i = (array.length-2)/2; i >=0 ; i--) {
            shiftDown(array,array.length,i);
        }
    }

    private static void shiftDown(int[] array, int size, int index) {
    //index 为当前需要调整的位置
        while (index * 2 + 1 < size){
            
            int left = index * 2 + 1;
            int right = left + 1;
            //找出最小孩子的下标
            int min = left;
            if (right < size && array[min] > array[right]){
                min = right;
            }
            //如果当前结点满足堆的性质则结束。
            if (array[index] < array[min]){
                return;
            }
            //交换当前结点与最小孩子的值
            swap(array,min,index);
            //继续向下调整
            index = min;
        }

    }

    private static void swap(int[] array, int min, int index) {
        int t = array[index];
        array[index] = array[min];
        array[min]= t;
    }

En segundo lugar, la cola de prioridad (cola de prioridad)

        1. Definición: los elementos en una cola de prioridad se pueden insertar en cualquier orden, pero se recuperan en un orden ordenado. Es decir, cada vez que se llama al método remove, siempre se obtiene el elemento más pequeño en la cola de prioridad actual. Sin embargo, la cola de prioridad no ordena todos los elementos. Si estos elementos se procesan de forma iterativa, no hay necesidad de ordenarlos.La cola de prioridad utiliza una estructura de datos ordenada y eficiente llamada montón. Un montón es un árbol binario autoorganizado cuyas operaciones de agregar (anuncio) y (eliminar) permiten que el elemento más pequeño se mueva a la raíz sin tener que dedicar tiempo a ordenar los elementos.
        Al igual que TreeSet, la cola de prioridad puede contener un objeto de clase que implementa la interfaz Ccomparable o
un objeto Comparator proporcionado en el constructor.
        Un uso típico de las colas de prioridad es la programación de tareas. Cada tarea tiene una prioridad y las tareas se agregan a la cola en orden aleatorio
. Cada vez que se inicia una nueva tarea, la tarea con la prioridad más alta se elimina de la cola (dado que 1 se establece convencionalmente
como la prioridad "más alta", la operación de eliminación elimina el elemento más pequeño).

Implementar la cola de prioridad:


// 直接使用 long 类型作为我们的元素类型,不考虑泛型了
public class MyPriorityQueue {
    // 很重要的属性:堆 = 数组 + 有效元素个数
    private long[] array;
    private int size;

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    // 由于我们的元素类型是 long 类型,不需要考虑 Comparable 和 Comparator 的问题
    // 所以我们只需要一个构造方法即可
    public MyPriorityQueue() {
        array = new long[16];
        size = 0;
    }

    public void offer(long e) {
        // 放入我们的优先级队列中,放入之后,保证堆的性质仍然是满足的
        ensureCapacity();

        array[size] = e;
        size++;

        // [size - 1] 就是刚刚插入的元素的位置
        shiftUp(array, size - 1);
    }

    // 前提:size > 0
    public long peek() {
        // 返回堆顶元素
        if (size < 0) {
            throw new RuntimeException("队列是空的");
        }
        return array[0];
    }

    public long poll() {
        // 返回并删除堆顶元素
        if (size < 0) {
            throw new RuntimeException("队列是空的");
        }

        long e = array[0];

        // 用最后一个位置替代堆顶元素,删除最后一个位置
        array[0] = array[size - 1];
        array[size - 1] = 0;        // 0 代表这个位置被删除了,不是必须要写的
        size--;

        // 针对堆顶位置,做向下调整
        shiftDown(array, size, 0);

        return e;
    }

    // 检查我们的优先级队列对象是否正确
    // 1. 0 <= size && size <= array.length
    // 2. 满足小堆的特性(任取结点(除开叶子结点),其值 <= 它的两个孩子的值(如果存在的话)
    public void check() {
        if (size < 0 || size > array.length) {
            throw new RuntimeException("size 约束出错");
        }

        // 如果每个结点都没问题,说明小堆成立
        for (int i = 0; i < size; i++) {
            int left = 2 * i + 1;
            int right = 2 * i + 2;

            if (left >= size) {
                // 说明是叶子,跳过
                continue;
            }

            // 左孩子破坏了规则
            if (array[i] > array[left]) {
                throw new RuntimeException(String.format("[%d] 位置的值大于其左孩子的值了", i));
            }

            // 右孩子破坏了规则
            if (right < size && array[i] > array[right]) {
                throw new RuntimeException(String.format("[%d] 位置的值大于其右孩子的值了", i));
            }
        }
    }

    private void shiftDown(long[] array, int size, int index) {
        while (2 * index + 1 < size) {
            // 说明 index 一定有左孩子的
            int min = 2 * index + 1;
            int right = min + 1;
            if (right < size && array[right] < array[min]) {
                min = right;
            }

            if (array[index] <= array[min]) {
                return;
            }

            swap(array, index, min);

            index = min;
        }
    }

    private void swap(long[] array, int i, int j) {
        long t= array[i];
        array[i] = array[j];
        array[j] = t;
    }

    private void ensureCapacity() {
        if (size < array.length) {
            return;
        }

        array = Arrays.copyOf(array, array.length * 2);
    }

    // 向上调整期间,不需要 size
    private void shiftUp(long[] array, int index) {
        while (index != 0) {
            int parent = (index - 1) / 2;
            if (array[parent] <= array[index]) {
                return;
            }

            swap(array, index, parent);
            index = parent;
        }
    }
}

2. La cola de prioridad implementada en Java:

Una cola de         prioridad ilimitada basada en un montón de prioridad . Los elementos de la cola de prioridad se ordenan en su orden naturalComparator , o de acuerdo con lo previsto al construir la cola , según el método de construcción utilizado. Las colas de prioridad no permiten nullelementos . Las colas de prioridad que se basan en el orden natural tampoco permiten la inserción de objetos incomparables (si lo hace, puede resultar en

ClassCastException)。

La cabeza de         esta cola es el elemento más pequeño determinado por el orden especificado . Si varios elementos son el valor mínimo, el encabezado es uno de los elementos; el método de selección es arbitrario. Poner en cola las operaciones poll, removey accederpeek al elemento al principio de la cola.element

        Las colas de prioridad son ilimitadas, pero tienen una capacidad interna que controla el tamaño de la matriz utilizada para almacenar elementos de la cola. Por lo general, es al menos igual al tamaño de la cola. A medida que se agregan elementos a la cola de prioridad, su capacidad aumenta automáticamente. No es necesario especificar los detalles de la estrategia de aumento de capacidad.

        Esta clase y sus iteradores Collectionimplementan todos los métodos opcionalesIterator de las interfaces y . No se garantiza que el iterador provisto en el método atraviese los elementos de la cola de prioridad en un orden particular. Considere usar si necesita atravesar en orden .iterator()Arrays.sort(pq.toArray())

Resumen del método
 boolean add(E e)
          Inserta el elemento especificado en esta cola de prioridad.
 void clear()
          Elimina todos los elementos de esta cola de prioridad.
 Comparator<? super E> comparator()
          Devuelve el comparador utilizado para ordenar los elementos de esta cola, o si esta cola se ordena según el orden naturalnull de sus elementos .
 boolean contains(Object o)
          Devuelve si esta cola contiene el elemento especificado true.
 Iterator<E> iterator()
          Devuelve un iterador sobre los elementos de esta cola.
 boolean offer(E e)
          Inserta el elemento especificado en esta cola de prioridad.
 E peek()
          Obtiene pero no elimina el encabezado de esta cola; devuelve un valor nulo si esta cola está vacía.
 E poll()
          Obtiene y elimina el encabezado de esta cola, o nulo si esta cola está vacía.
 boolean remove(Object o)
          Elimina una sola instancia del elemento especificado de esta cola, si existe.
 int size()
          Devuelve el número de elementos de esta colección.
 Object[] toArray()
          Devuelve una matriz que contiene todos los elementos de esta cola.
<T> T[]
toArray(T[] a)
          Devuelve una matriz que contiene todos los elementos de esta cola; el tipo de tiempo de ejecución de la matriz devuelta es el de la matriz especificada.

        

3. Resumen:

        1. El montón es un árbol binario por definición, pero es una matriz en la implementación real.

                La relación de subíndice entre el nodo principal y el nodo secundario del árbol binario en la matriz:

                izquierda = padre * 2 +1;

                derecho = padre * 2 + 2;

                padre = (hijo-1)/2;

        2. Montón: Encuentre el mayor valor en algunos conjuntos de datos que cambian con frecuencia.

        3. La operación central del montón: ajuste hacia abajo, construyendo el montón inicial.

        4, puede realizar la cola de prioridad.

        5. Problema de k superior.

Supongo que te gusta

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