堆(优先级队列)

1 二叉树的顺序存储: 使用数组保存二叉树结构,即将二叉树 按照层序遍历 的方式放入数组.这种方式一般只适用于完全二叉树,一般的二叉树会造成空间浪费比较严重.
2 堆(heap) 在逻辑上就是一个完全二叉树,在物理上保存在数组中.
(1) 满足任意结点的值都大于其子树的结点的值.叫做大堆(最大堆), 反之是小堆(最小堆).
(2) 堆的作用: 快速找到集合中的最值.

3 堆中元素下标关系: 已知双亲(parent)的下标, 或已知左右孩子(child)下标时:
(1) 当根节点下标从0开始计算时, 左孩子(left)下标 = 2 × parent + 1, 右孩子(right)下标 = 2 × parent + 2. 双亲下标(不区分左右子树) = (child-1) / 2;
(2) 当根节点下标从1开始计算时, 左孩子(left)下标 = 2 × parent , 右孩子(right)下标 = 2 × parent + 1. 双亲下标(不区分左右子树) = child / 2;

4 向下调整:(注意) 前提是左右子树必须已经是一个堆,才能进行调整.
(1) 通过size指定 array 中哪些元素是有效的堆元素, 即有效元素个数.
(2) index 表示从哪个位置的下标开始调整.
(3) 这里以小堆为例,根节点下标为0的调整.

 public static void shiftDown(int[] array, int size, int index) {
        int parent = index;
        int child = 2 * parent + 1;  // 根据 parent 下标找到左子树的下标
        while (child < size) {
            // 比较左右子树, 找到较小值
            if (child + 1 < size && array[child + 1] < array[child]) {
                child = child + 1;
            }
            if (array[child] < array[parent]) {
                int tmp = array[child];
                array[child] = array[parent];
                array[parent] = tmp;
            } else {
                break;
            }
            // 更新 parent 和 child, 处理下一层的数据.
            parent = child;
            child = 2 * parent + 1;
        }
    }

大堆的 向下调整过程:

public static void shiftDown(int[] array, int size, int index) {
        int parent = index;
        int child = 2 * parent + 1;
        while (child < size) {
            if (child + 1 < size && array[child + 1] > array[child]) {
                child = child + 1;
            }
            if (array[child] > array[parent]) {
                int tmp = array[child];
                array[child] = array[parent];
                array[parent] = tmp;
            } else {
                break;
            }
            parent = child;
            child = 2 * parent + 1;
        }
    }

4 建堆:把一个无序数组建成一个堆, 可以用向下调整也可以向上调整. 一般是向下调整.
建堆过程, 找到最后一个非叶子结点开始进行向下调整,需要从后往前遍历数组. 时间复杂度为(OlogN)
以向下调整构建小堆为例:

//建堆:找到最后一个非叶子节点,然后进行向下调整操作,每次之后i--
    public static void createHeap(int[] array, int size) {
        for (int i = (size - 1 - 1) / 2; i >= 0; i--) {
            shiftDown(array, size, i);
        }
    }
    public static void main(String[] args) {
        int[] array = {9,5,2,7,3,6,8};
        createHeap(array, array.length);
        System.out.println(Arrays.toString(array));
    }
    //向下调整建好的小堆: [2, 3, 6, 7, 5, 9, 8]

5 如果是向上调整的话,就找根节点紧挨的第一个双亲节点作为起始位置, 从前往后进行遍历数组.
6 构建堆的应用: 优先级队列(PriorityQueue)(注意这是一个队列,都是队列有关的性质):通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理优先级次高的对象.
操作-----入队列(以大堆为例):
(1) 首先按尾插方式放入数组.
(2) 比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质,插入结束
(3) 否则, 交换其和双亲位置的值,重新进行 2、3 步骤, 直到根结点.

private static void shiftUp(int[] array, int index) {
        int child = index;
        int parent = (child - 1) / 2;
        while (child > 0) {
            if (array[parent] < array[child]) {
                int tmp = array[parent];
                array[parent] = array[child];
                array[child] = tmp;
            } else {
                // 发现当前父节点比子节点大. 这个时候说明整个数组已经符合堆的结构了
                break;
            }
            child = parent;
            parent = (child - 1) / 2;
        }
    }

操作-----出队列(优先级最高) (以大堆为例):
(1) 下标为 0 的元素就是队首元素. 删掉的同时, 我们也希望剩下的结构仍然是一个堆.
(2) 实际上是用最后一个元素替换队首元素,然后删除最后一个元素,同时再进行向下调整.

 public int poll() {
        int oldValue = array[0];
        array[0] = array[size - 1];
        size--;
        shiftDown(array, size, 0);
        return oldValue;
    }

操作------取队首元素(优先级最高) (以大堆为例):
返回堆顶元素即可.

public int peek() {
        return array[0];
    }

7 TopK问题.

你可能感兴趣的:(堆(优先级队列))