06.自定义优先级的优先队列(二叉堆)

二叉堆

一、实现的堆有如下特点

  1. 理论分析上为二叉堆
  2. 理论分析上为完全二叉树
  3. 自定义堆的优先级,创建堆对象时需要实现比较器
  4. 底层用线性数组存取元素。
  5. 支持heapify操作,将一个数组进行原地排序

二、如果索引从0开始开始编号,父子索引的关系如下

parent(i) = (i - 1) / 2
left child(i) = i * 2 + 1
right child(i) = i * 2 + 2

三、具体实现

import java.util.ArrayList;
import java.util.Comparator;

/**
 * 实现一个自定义优先级的堆。
 * 要求用户传输一个Lambda表达式指定是最大堆还是最小堆,或者其他定义!
 * 很灵活是不是@_@
 */

public class MyHeap {
    private ArrayList array;
    private Comparator compare;

    // 构造函数
    public MyHeap(Comparator compare){
        array = new ArrayList<>();
        this.compare = compare;
    }

    // 返回堆的大小
    public int size() {
        return array.size();
    }

    // 判断堆是否为空
    public boolean isEmpty() {
        return this.size() == 0;
    }

    // swap交换函数
    private void swap(int i, int j) {
        E temp = array.get(i);
        array.set(i, array.get(j));
        array.set(j, temp);
    }

    // 计算父节点索引:parent(i) = (i - 1) / 2
    private int parent(int i) {
        return (i - 1) / 2;
    }

    // 计算左孩子节点:left child(i) = i * 2 + 1
    private int leftChild(int i) {
        return i * 2 + 1;
    }

    // 计算右孩子节点:right child(i) = i * 2 + 2
    private int rightChild(int i) {
        return i * 2 + 2;
    }

    // Shift Up操作,将末尾元素与父元素比较,按照规则进行交换!
    private void shiftUp(int i) {
        int parentIndex = parent(i);
        while (i > 0 && compare.compare(array.get(i), array.get(parentIndex)) >= 0) {
            swap(i, parentIndex);
            i = parentIndex;
            parentIndex = parent(parentIndex);
        }
    }

    // 向堆中添加一个元素
    public void add(E e) {
        array.add(e);
        shiftUp(array.size() - 1);
    }

    // 找到子节点优先级最高的节点的索引
    // 为了实现原地排序法,需要边界参数 boundary
    // 如果没有子节点则返回 -1
    private int prioritySonIndex(int i, int boundary) {
        int leftIndex = leftChild(i);
        int rightIndex = rightChild(i);

        if(leftIndex >= boundary)
            return -1;

        if(rightIndex >= boundary)
            return leftIndex;

        if(compare.compare(array.get(leftIndex), array.get(rightIndex)) > 0)
            return leftIndex;
        else
            return rightIndex;
    }

    // Shift Down操作,从根节点开始与子节点比较,按照规则进行交换!
    // 为了实现原地排序法,需要边界参数 boundary
    private void shiftDown(int i, int boundary) {
        int parent = i;
        int son = prioritySonIndex(i, boundary);
        while (son != -1) {
            if(compare.compare(array.get(son), array.get(parent)) > 0) {
                swap(parent, son);
                parent = son;
                son = prioritySonIndex(parent, boundary);
            } else
                return;
        }
    }

    // 取出堆中的第一个元素,将最后一个元素
    // 赋值到第一个元素的位置,然后进行shift down操作。
    public E pop() {
        try {
            E temp = array.get(0);
            array.set(0, array.get(array.size() - 1));
            array.remove(array.size() - 1);
            shiftDown(0, array.size());
            return temp;
        } catch (Exception e) {
            throw new IndexOutOfBoundsException("数组索引越界错误!");
        }
    }

    /** @Heapify排序,将一个数组进行排序
     * 从最后一个含有子节点的节点开始一直到根节点,进行shift down操作
     * 由于排序完毕之后是一个堆,需要进行原地排序法将数组整理一下
     * 思路:【排序后是倒叙,所以在创建 堆 时,得设计好优先级!】
     *   1. Heapify完成之后,将第一个元素与最后一个元素交换,进行shift down操作【最后一个数不参与】
     *   2. 将第一个元素与倒数第二个交换,shift down操作【倒数第二个数之后的不参与】
     *   ...
     *   n. 直到第一个元素与交换的索引相等了,就终止
     */

    public void heapify(ArrayList arr){
        this.array = arr;
        int last = parent(arr.size() - 1);
        for (int i = last; i >= 0; i--) {
            shiftDown(i, array.size());
        }

        // 实现原地排序法
        for (int i = array.size() - 1; i > 0; i --) {
            swap(0, i);
            shiftDown(0, i);
        }
    }

    @Override
    public String toString() {
        return array.toString();
    }
}

和堆相关的其他问题【以后探讨】

  1. 在N个元素中选出前M个元素O(NlogM)【已实现】

  2. 多路归并排序


    多路并规排序.png
  3. d 叉堆 d-ary heap:每个节点有n个节点

  4. 最大堆,最大索引堆,最小堆,最小索引堆【已满足】

  5. 容量伸缩堆【已实现】

  6. 同时维护最大索引和最小索引

  7. 二项堆、斐波那契堆

你可能感兴趣的:(06.自定义优先级的优先队列(二叉堆))