优先级队列实现原理

一 点睛

在算法设计中,经常需要从序列中查找最值(最小值或最大值),例如:最短路径、哈特曼编码等都需要查找最小值。若顺序查找最值需要 O(n) 时间,而使用优先级队列查找需要 O(1) 时间,则入队和出队需要 O(logn) 时间。

在树形结构中有两种比较特殊的二叉树:满二叉树和完全二叉树。

二 满二叉树

指一棵深度为 k 且有二的 k 次方减 1 个节点的二叉树,满二叉树的每一层都充满节点,达到最大节点数。

三 完全二叉树

除了最后一层,每一层都是满的,达到最大节点数,最后一层节点从左向右出现的。深度为 k 的完全二叉树,其每个节点都与深度为 k 的满二叉树中的节点一一对应,完全二叉树和上图的满二叉树节点一一对应。完全二叉树除了最后一层,前面每一层都是满的,最后一层必须从左向右排列。也就是说,若2 没有左子节点,则 2 不可以有右子节点,若 2 没有右子节点,则 3 不可以有左子节点。

四 性质

若对完全二叉树从上至下,从左至右编号,则编号为 i 的节点,其左子节点编号必为 2i,其右子节点编号必为 2i+1,其双亲编号必为 i/2。

例如,有一棵完全二叉树 2 号节点的双亲节点为 1,左子节点为4,右子节点为 5; 3 号节点的双亲节点为 1,左子节点为 6,右子节点为 7。

五 最大堆和最小堆

若每个节点的值都大于或等于左右子节点的值,则称之为最大堆。若每个节点的值都小于或等于左右子节点的值,则称为最小堆。可以将堆看作一棵完全二叉树的顺序存储结构。一个数据元素序列及其对应的完全二叉树如下图所示,该完全二叉树满足最大堆的定义。

普通队列是先进先出的,优先队列与普通队列不同,每次出队都按照优先级顺序出队。优先队列是通过堆实现的,优先队列中的元素存储满足堆的定义,上图中的每一个节点的值都是大于或等于左右子节点的值,满足最大堆的定义,是最大值优先的最大堆。

优先队列有出队和入队两种基本操作。

1 出队

出队时,堆顶出队,最后一个元素代替堆顶的位置,重新调整为堆。

2 图解

一个最大堆如下图所示。出堆时,堆顶 30 出队,最后一个元素 12 代替堆顶。

出队后,除了堆顶,其他节点都满足最大堆的定义,只需堆顶执行下沉操作,即可调整为堆。下沉指堆顶与左右子节点比较,若比子节点大,则已调整为堆,若比子节点小,则与较大的子节点交换,交换到新的位置后继续向下比较,从根节点一直比较到叶子。

堆顶下沉的过程如下。

a 堆顶 12 和两个子节点 28、20 比较,比子节点小,与较大的子节点 28 交换。

b 12 再和两个子节点 16、18 比较,比子节点小,与较大的子节点 18 交换。

c 比较到叶子时停止,已调整为堆。

调整堆的过程,就是从堆顶从根下沉到叶子的过程。

3 入队

入队时,将新元素放入最后一个元素之后,重新调整为堆。

4 图解

例如 29入队,首先将 29 放入最后一个元素 12 的后面。

入队后,除了新入队的元素,其他节点都满足最大堆的定义,只需新元素执行上浮操作,即可调整为堆。上浮指新元素,与其父节点比较。若小于或等于父节点,则已调整为堆,若比父节点大,则与父节点交换,交换到新的位置后继续向上比较,从叶子一直比较到根。

新元素上浮的过程如下。

a 新元素 29 与其父节点 18 比较,比父节点大,与父节点交换。

b 29 再和父节点 28 比较,比父节点大,与父节点交换

c 29 再和父节点 30 比较,比父节点小,已调整为堆。

六 代码

package com.platform.modules.alg.alglib.p21;

public class P21 {
    public String output = "";

    private int maxN = 1000;
    int r[] = new int[maxN];

    public static void swap(int[] arr, int num1, int num2) {
        int temp = arr[num1];
        arr[num1] = arr[num2];
        arr[num2] = temp;
    }

    void Sink(int k, int n) // 下沉操作
    {
        while (2 * k <= n) // 如果有左孩子
        {
            int j = 2 * k; // j 指向左孩子
            if (j < n && r[j] < r[j + 1]) // 如果有右孩子且左孩子比右孩子小
                j++;    // j 指向右孩子
            if (r[k] >= r[j]) // 比较大的孩子大
                break;    // 已满足堆
            else
                swap(r, k, j); // 与较大的孩子交换
            k = j; // k 指向交换后的新位置,继续向下比较,一直下沉到叶子
        }
    }

    void Swim(int k) // 上浮操作
    {
        while (k > 1 && r[k] > r[k / 2]) // 如果大于双亲
        {
            swap(r, k, k / 2); // 与双亲交换
            k = k / 2; // k 指向交换后的新位置,继续向上比较,一直上浮到根
        }
    }

    void CreatHeap(int n) // 构建初始堆
    {
        for (int i = n / 2; i > 0; i--) // 从最后一个分支结点 n/2 开始下沉调整为堆,直到第一个结点
            Sink(i, n);
    }

    void push(int n, int x) // 入队
    {
        r[++n] = x; // n 加 1 后,将新元素放入尾部
        Swim(n); // 最后一个元素上浮操作
    }

    void pop(int n) // 出队
    {
        output += r[1] + "\n"; // 输出堆顶
        r[1] = r[n--]; // 最后一个元素代替堆顶,n减1
        Sink(1, n); // 堆顶下沉操作
    }

    public String cal(String input) {
        String[] line = input.split("\n");
        String[] words = line[0].split(" ");
        for (int i = 0; i < words.length; i++)
            r[i + 1] = Integer.parseInt(words[i]);
        int n = words.length;
        CreatHeap(n); // 创建初始堆
        int N = Integer.parseInt(line[1]);
        int x;

        for (int i = 0; i < N; i++) {
            String[] command = line[i + 2].split(" ");
            int commandNum = Integer.parseInt(command[0]);
            switch (commandNum) {
                case 1:
                    x = Integer.parseInt(command[1]);
                    push(n, x); // 入队
                    n = n + 1;
                    break;
                case 2:
                    pop(n); // 出队
                    n = n - 1;
                    break;
            }
        }

        return output;
    }
}

七 测试

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值