《算法》学习笔记——基于堆的优先队列

每篇一句:

The more we love our friends, the less we flatter them.


优先队列:

  • 概念介绍:

    优先队列是一种抽象数据类型,它表示了一组值和对这组值的操作,在优先队列中,元素被赋予 优先级 。优先队列最重要的操作是 删除优先级最高元素(delMax()插入元素(insert()'(本文以下内容默认元素数值越大,优先级越高)

  • 实现方式:

    1. 数组实现(无序):

      实现优先队列的最简单方法就是基于 下压栈(数组实现)的代码。insert() 方法的代码和栈的push()方法完全一样。要实现删除最大元素delMax(),我们可以添加一段类似于 选择排序 的内循环的代码,将最大元素和边界元素交换然后删除它。和栈类似,我们也可以加入调整数组大小的代码来保证数据结构中至少含有四分之一的元素而又永远不会溢出。

    2. 数组实现(有序):

      还是基于 下压栈(数组实现)的代码。insert() 方法中添加代码:将所有较大的元素向右边移动一格以使数组保持有序(和 插入排序 一样) 。这样,最大的元素总会在数组的一边,优先队列的删除最大元素操作delMax()就和栈的pop()操作一样了。

    3. 链表表示法:

      基于 下压栈(链表实现) 的代码。可以选择修改pop()来找到并返回最大元素,或是修改push()来保证所有元素为逆序并用pop()来返回链表的首元素(也就是最大的元素)。

    上述三种初级实现方式中,插入元素和删除最大元素这两个操作之一在最坏情况下需要线性时间来完成。接下来介绍的基于堆的实现能够保证这两个操作都能更快的执行。


二叉堆:

   数据结构二叉堆能够很好的实现优先队列的基本操作。

  • 基本概念:

    • 堆有序:当一颗二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序。

    • 堆的有序化:堆的操作会首先进行一些简单的改动,打破堆的状态,然后再遍历堆并按照要求将堆的状态恢复。这个过程称为对的有序化。

    • 二叉堆:二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级储存(不使用数组的第一个元素)。

  • 二叉堆的特点:

    • 位置为 k 的结点的父结点的位置为 k/2,而他的两个子结点的位置分别为 2k2k+1

    • 一颗大小为N的完全二叉树的高度为lgN向下取整

  • 堆有序化的两种方式:

    • 上浮:如果堆的有序状态因为某个结点变得比它的父结点更大而被打破,那么我们就需要通过交换它和它的父结点来修复堆。将这个结点不断向上移动直到遇到一个比它更大的父结点。
    private void swim(int k){
        while(k > 1 && less(k/2, k)){
            exch(k/2, k);  //交换位置
            k = k / 2;
        }
    }
    • 下沉:如果堆的有序状态因为某个结点变得比它的两个子结点或是其中之一更小了而被打破,那么我们可以通过将它和它的两个子结点中的较大者交换来恢复堆。将这个结点向下移动直到它的子结点都比它小或是到达了堆的底部。
    private void sink(int k){
        while(2*k <= N){
            int j = 2*k;
            if (j < N && less(j, j+1))  j++;
            if (!less(k, j))  break;
            exch(k, j);
            k = j;
        }
    }

基于堆的优先队列的实现:

  • 插入元素:

    将新元素加到数组末尾,增加堆的大小并让这个新元素上浮到合适位置。

    插入

  • 删除最大元素:

    从数组顶端删去最大的元素,并将数组的最后一个元素放到顶端,减少堆的大小并让这个元素下沉到合适的位置。

    删除

  • 完整实现:

public class MaxPQ<Key extends Comparable<Key>> {
    private Key[] pq = (Key[]) new Comparable[1];
    private int N = 0;

    // 调整数组大小的代码来保证数据结构中至少含有四分之一的元素而又永远不会溢出。
    private void resize(int max){
        // 移动到一个大小为max的新数组
        Key[] temp = (Key[])  new Object[max];
        for (int i = 0; i < N; i++){
            temp[i] = pq[i];
        }
        pq = temp;
    }


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

    // 插入
    public void insert(Key v){
        N++;
        if (N == pq.length) resize(2*pq.length);
        pq[N] = v;
        swim(N);
    }

    // 删除最大元素
    public Key delMax(){
        Key max = pq[1];
        exch(1, N--);
        pq[N+1] = null;
        sink(1);
        if (N > 0 && N == pq.length / 4) resize(pq.length / 2);
        return max;
    }

    // 下沉
    private void sink(int k){
        while(2*k <= N){
            int j = 2*k;
            if (j < N && less(j, j+1))  j++;
            if (!less(k, j))  break;
            exch(k, j);
            k = j;
        }
    }

    // 上浮
    private void swim(int k){
        while(k > 1 && less(k/2, k)){
            exch(k/2, k);  //交换位置
            k = k / 2;
        }
    }
    private boolean less(int i, int j){
        return pq[i].compareTo(pq[j]) < 0;
    }
    private void exch(int i, int j){
        Key t = pq[i];
        pq[i] = pq[j];
        pq[j] = t;
    }
}
  • 简单分析:

    对一个含有N个元素的基于堆的优先队列:

    • 插入元素操作只需不超过(lgN+1)次比较

    • 删除最大元素操作需要不超过2lgN次比较


参考:

  • 《算法》第四版——2.4 优先队列

如果文中有什么不足或错误之处,欢迎指出!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值