优先级队列

Posted aiguozou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了优先级队列相关的知识,希望对你有一定的参考价值。

优先级队列的特征在于删除最大值和插入操作。

初级实现

  • 数组实现(无序):惰性方法,仅在必要的时候找出最大元素;
  • 数组实现(有序):积极方法:在插入时就保持列表有序,使后续操作更高效;
  • 链表表示法
    技术图片
数据结构 插入元素 删除最大元素
有序数组 N 1
无序数组 1 N
logN logN
理想情况 1 1

在上述优先队列的初级实现中,删除最大元素和插入元素这两个操作之一在最坏情况下需要线性时间来完成。

堆的定义

1. 完全二叉树(从上到下,从左到右)
2. 一棵二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序
二叉堆表示法

如果使用指针来表示堆有序的二叉树,那么每个元素都需要三个指针来找到它的上下结点。
但使用完全二叉树,只需要数组而不需要指针就可以表示,十分方便。
具体方法是将二叉树的结点按照层级顺序放入数组中。(下图_根节点从1开始)
技术图片

位置 k 父结点位置 两子结点位置
根节点从1开始 k/2 2k,2k+1
根节点从0开始 (k-1)/2 2k+2,2k+1

高性能的原因:利用在数组中无需指针即可沿树上下移动的便利。

堆的算法

我们在长度为 n + 1 的私有数组 pq []中表示大小为 N 的堆,其中pq [0]未使用且堆在pq [1]到pq [n]中。我们只通过函数less() 和exch()来访问元素。

堆有序化

打破堆的状态,然后再遍历堆并按照要求将堆的状态恢复。

由下至上的堆序列化(上浮)
    private void swim(int n) 
        //     第一层      父节点小于子节点
        while (n > 1 && less(n / 2, n)) 
            exch(n / 2, 2);
            //n=父节点
            n = n / 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;//跟父节点比对,父节点大就break;
      exch(k,j);//交换后   k = j 继续下沉
      k = j;
   

技术图片

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

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

api&实现


技术图片

public class MaxPQ<T extends Comparable<T>> 
    private boolean less(int i, int j) 
        return pq[i].compareTo(pq[j]) < 0;
    

    private void exch(int i, int j) 
        T t = pq[i];
        pq[i] = pq[j];
        pq[j] = t;
    

    private T[] pq;
    private int N = 0; //存放到[1-N]中 0没使用

    public MaxPQ(int maxN) 
        pq = (T[]) new Comparable[maxN + 1];
    

    public boolean isEmpty() 
        return N == 0;
    

    public int size() 
        return N;
    

    public T delMax() 
        T max = pq[1];
        exch(1, N--);
        pq[N + 1] = null;//防止对象游离态
        //下沉
        sink(1);
        return max;
    

    private void sink(int i) 
        while (2 * i <= N) 
            int j = 2 * i;
            if (j < N && less(j, j + 1)) 
                j++;
            
            if (!less(i, j)) 
                break;
            
            exch(i, j);
            i = j;
        
    

    public void insert(T n) 
        pq[++N] = n;
        //上浮
        swim(N);
    

    private void swim(int n) 
        while (n > 1 && less(n / 2, n)) 
            exch(n / 2, 2);
            n = n / 2;
        
    

特点

在某些数据处理的场合,总数据量太大(可以认为输入是无限的),
无法排序(甚至无法全部装进内存)。如果将每个新的输入和已知的 M 个最大(或最小)元素比较,除非 M 较小,
否则这种比较的代价会非常高昂。如果有了优先队列,就只用一个能存储 M 个元素的队列即可。
利用在数组中无需指针即可沿树上下移动的便利。

以上是关于优先级队列的主要内容,如果未能解决你的问题,请参考以下文章

优先队列和单调队列一样吗?

堆(Heap)和有优先队列(Priority Queue)

Redis实现优先级队列

优先级队列

优先队列

堆和优先级队列2:java实现堆和优先级队列