优先队列或堆及堆排序介绍

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qiyei2009/article/details/80155311

1 堆的基本概念

堆也叫优先队列,堆是一种特殊的完全二叉树数据结构,堆分为两种,最大堆,最小堆。
最大堆:根节点大于左右两个子节点的完全二叉树
最小堆:根节点小于左右两个子节点的完全二叉树
堆可以用数组来存储,a[i]处存根节点,a[2*i] a[2*i + 1]分别存左子树的根节点,右子树的根节点。i从1开始

所以对于一个堆,结点i,其父结点为a[i/2],左子节点a[2*i],右子节点a[2*i + 1]

2 最大堆

根节点大于左右两个子节点的完全二叉树叫最大堆

1 堆的上浮
对于最大堆来说,如果某个结点比其父结点要大,那么我们应该调整该结点的位置,从而使其满足堆的结构。调整的思路很简单,就是将与其父结点交换。这样一直循环下去,直到所有结点满足堆的结构。这个过程叫做堆的上浮

    /**
     * 堆的上浮,解决子节点比父结点大的问题
     * @param k 节点k上浮
     */
    private void swim(int k){
        //子节点比父结点大
        while (k > 1 && less(k/2,k)){
            //交换两个节点
            exch(k/2,k);
            k = k/2;
        }
    }

时间复杂度O(logN)

2 堆的下沉
对于最大堆来说,如果我们发现某个结点小于子节点(同时小于两个结点,或者小于一个结点)。这个时候我们也需要调整堆结构。调整思路就是将该结点与子节点中较大的结点做交换,递归下去。直到满足堆的结构。

    /**
     * 堆的下沉 父结点小于子节点,将父节点与较大的子节点交换
     * @param k
     */
    private void sink(int k){
        if (k > count){
            return;
        }
        int i = 2 * k;
        if (i > count ){
            return;
        }

        //只有左子节点 i = count;
        if ((i + 1 ) > count){
            if (less(k,i)){
                exch(k,i);
            }
            return;
        }
        //两个孩子都大于父节点
        if (less(k,i) || less(k,i + 1)){
            if (less(i,i+1)){
                exch(k,i+1);
                sink(i+1);
            }else {
                exch(k,i);
                sink(i);
            }
        }
    }

时间复杂度O(logN)

3 堆的插入
只要将插入的数放在数组末尾,然后上浮这个结点即可

    /**
     * 插入一个元素,并上浮
     * @param t
     */
    public void insert(T t){
        pq[++count] = t;
        swimBetter(count);
    }

时间复杂度O(logN)

4 堆的删除
这里删除指的是删除最大的元素,即删除顶点元素。删除思路很简单:获取数组第一个元素,然后将第一个元素与最后一个元素交换,并下沉第一个元素即可。

    /**
     * 删除最大的元素
     * @return
     */
    public T delMax(){
        T t = (T) pq[1];
        //删除最大的,然后将末尾元素交换,并下沉
        exch(1,count);
        pq[count] = null;
        count--;
        sinkBetter(1);
        return t;
    }

时间复杂度O(logN)

5最大堆实现
接下来我们看堆的完整代码:

**
 * @author Created by qiyei2015 on 2018/3/25.
 * @version: 1.0
 * @email: 1273482124@qq.com
 * @description:
 */
public class BaseHeap<T extends Comparable<T>> {

    /**
     * 长度需N + 1
     */
    protected Comparable[] pq;

    /**
     * 容量 pq[1....N]
     */
    protected int N;
    /**
     * 堆中元素个数
     */
    protected int count;

    /**
     * 构造方法
     */
    public BaseHeap() {
        pq = new Comparable[0];
    }

    /**
     * 创建一个初始容量为max的堆
     * @param max
     */
    public BaseHeap(int max) {
        pq = new Comparable[max + 1];
        count = 0;
        N = max;
    }

    /**
     * 数组创建
     * @param array
     */
    public BaseHeap(Comparable[] array) {
        this.pq = new Comparable[array.length + 1];
        System.arraycopy(array,0,pq,1,array.length);
        N = array.length;
        count = N;
    }

    /**
     * 时候为null
     * @return
     */
    public boolean isEmpty(){
        return count == 0;
    }

    /**
     * 返回堆中元素个数
     * @return
     */
    public int size(){
        return count;
    }

    /**
     * 比较 i < j
     * @param i
     * @param j
     * @return
     */
    protected boolean less(int i ,int j){
        return pq[i].compareTo(pq[j]) < 0;
    }

    /**
     * 交换两个数
     * @param i
     * @param j
     */
    protected void exch(int i,int j){
        Comparable temp = pq[i];
        pq[i] = pq[j];
        pq[j] = temp;
    }

}
/**
 * @author Created by qiyei2015 on 2018/3/25.
 * @version: 1.0
 * @email: 1273482124@qq.com
 * @description: 最大堆
 */
public class MaxPQ<T extends Comparable<T>> extends BaseHeap{

    /**
     * 无参构造方法
     */
    public MaxPQ() {
        super();
    }

    /**
     * 创建一个初始容量为max的堆
     * @param max
     */
    public MaxPQ(int max) {
        super(max);
    }

    /**
     * 用数组创建堆,时间复杂度 O(N)
     * @param a
     */
    public MaxPQ(Comparable[] a){
        super(a);
        //最后一个父结点是 count / 2 [count/2...1]这个结点区间的结点都下沉,就是堆了
        for (int i = count/2; i >= 1 ; i--){
            sinkBetter(i);
        }
    }

    /**
     * 插入一个元素,并上浮
     * @param t
     */
    public void insert(T t){
        pq[++count] = t;
        swimBetter(count);
    }

    /**
     * 删除最大的元素
     * @return
     */
    public T delMax(){
        T t = (T) pq[1];
        //删除最大的,然后将末尾元素交换,并下沉
        exch(1,count);
        pq[count] = null;
        count--;
        sinkBetter(1);
        return t;
    }

    /**
     * 堆的上浮,解决子节点比父结点大的问题
     * @param k 节点k上浮
     */
    private void swim(int k){
        //子节点比父结点大
        while (k > 1 && less(k/2,k)){
            //交换两个节点
            exch(k/2,k);
            k = k/2;
        }
    }

    /**
     * 堆的上浮,解决子节点比父结点大的问题,少交换,优化堆的上浮过程
     * @param k 节点k上浮
     */
    private void swimBetter(int k){
        Comparable temp =  pq[k];
        //子节点比父结点大
        while (k > 1 && less(k/2,k)){
            //父结点移到子节点 子节点暂存 不用每次都去新建一个临时变量来交换
            pq[k] = pq[k/2];
            pq[k/2] = temp;
            k = k/2;
        }
    }


    /**
     * 堆的下沉 父结点小于子节点,将父节点与较大的子节点交换
     * @param k
     */
    private void sink(int k){
        if (k > count){
            return;
        }
        int i = 2 * k;
        if (i > count ){
            return;
        }

        //只有左子节点 i = count;
        if ((i + 1 ) > count){
            if (less(k,i)){
                exch(k,i);
            }
            return;
        }
        //两个孩子都大于父节点
        if (less(k,i) || less(k,i + 1)){
            if (less(i,i+1)){
                exch(k,i+1);
                sink(i+1);
            }else {
                exch(k,i);
                sink(i);
            }
        }

//        //判断有左孩子,有孩子就行
//        while (2 * k <= N){
//            int j = 2 * k; //此轮循环中 k 与j交换
//            if ((j +1) <= N && less(j,j+1)){
//                j++; //更新为右孩子
//            }
//            //父结点大于子节点
//            if (!less(k,j)){
//                break;
//            }
//            exch(k,j);
//            k = j; //更新k的位置
//        }

    }


    /**
     * 堆的下沉 父结点小于子节点,将父节点与较大的子节点交换
     * @param k
     */
    private void sinkBetter(int k) {
        Comparable temp = pq[k];
        //判断有左孩子,有孩子就行
        while (2 * k <= count) {
            int j = 2 * k; //此轮循环中 k 与j交换
            if ((j + 1) <= count && less(j, j + 1)) {
                j++; //更新为右孩子
            }
            //父结点大于子节点
            if (!less(k, j)) {
                break;
            }

            //将子节点移到父结点,父结点移到子节点 不用每次都去新建一个临时变量来交换
            pq[k] = pq[j];
            pq[j] = temp;
            k = j; //更新k的位置
        }
    }
}

3 最小堆

与最大堆类似,我们只需要在堆的上浮和下沉改变一下条件,即可创建最小堆

1 最小堆上浮
结点比父节点小,该结点与父节点交换

    /**
     * 堆的上浮,如果子节点比父节点小,就交换
     * @param k 节点k上浮
     */
    private void swim(int k){
        //子节点比父结点大
        while (k > 1 && less(k,k/2)){
            //交换两个节点
            exch(k/2,k);
            k = k/2;
        }
    }

时间复杂度O(logN)

2 最小堆下沉
父结点大于子节点,该结点与子结点中较小的节点交换

    /**
     * 堆的下沉 父结点大于子节点,将父节点与较小的子节点交换,并下沉子节点
     * @param k
     */
    private void sink(int k){
        if (k > count){
            return;
        }
        int i = 2 * k;
        if (i > count ){
            return;
        }

        //只有左子节点 i = count;
        if ((i+1) > count){
            if (less(i,k)){
                exch(k,i);
            }
            return;
        }

        //父节点与较小的子节点交换
        if (less(i,k) || less(i+1,k)){
            //将父节点与较小的节点交换
            if (less(i,i+1)){
                exch(k,i);
                sink(i);
            }else {
                exch(k,i+1);
                sink(i+1);
            }
        }
    }

时间复杂度O(logN)

3 最小堆实现
完整最小堆代码:

package com.qiyei.heap;

/**
 * @author Created by qiyei2015 on 2018/4/16.
 * @version: 1.0
 * @email: 1273482124@qq.com
 * @description: 最小堆
 */
public class MinPQ<T extends Comparable<T>> extends BaseHeap {
    /**
     * 无参构造方法
     */
    public MinPQ() {
        super();
    }

    /**
     * 创建一个初始容量为max的堆
     * @param n
     */
    public MinPQ(int n) {
        super(n);
    }

    /**
     * 用数组创建堆,时间复杂度 O(N)
     * @param a
     */
    public MinPQ(Comparable[] a){
        super(a);
        for (int i = count/2; i >= 1 ; i--){
            sinkBetter(i);
        }
    }

    /**
     * 插入一个元素
     * @param t
     */
    public void insert(T t){
        pq[++count] = t;
        swimBetter(count);
    }

    /**
     * 删除最小的元素
     * @return
     */
    public T delMin(){
        T t = (T) pq[1];
        exch(1,count);
        pq[count] = null;
        count--;
        sinkBetter(1);
        return t;
    }

    /**
     * 堆的上浮,如果子节点比父节点小,就交换
     * @param k 节点k上浮
     */
    private void swim(int k){
        //子节点比父结点大
        while (k > 1 && less(k,k/2)){
            //交换两个节点
            exch(k/2,k);
            k = k/2;
        }
    }

    /**
     * 堆的上浮,如果子节点比父节点小,就交换。优化堆的上浮过程
     * @param k 节点k上浮
     */
    private void swimBetter(int k){
        Comparable temp =  pq[k];
        //子节点比父结点大
        while (k > 1 && less(k,k/2)){
            //父结点移到子节点 子节点暂存 不用每次都去新建一个临时变量来交换
            pq[k] = pq[k/2];
            pq[k/2] = temp;
            k = k/2;
        }
    }


    /**
     * 堆的下沉 父结点大于子节点,将父节点与较小的子节点交换,并下沉子节点
     * @param k
     */
    private void sink(int k){
        if (k > count){
            return;
        }
        int i = 2 * k;
        if (i > count ){
            return;
        }

        //只有左子节点 i = count;
        if ((i+1) > count){
            if (less(i,k)){
                exch(k,i);
            }
            return;
        }

        //父节点与较小的子节点交换
        if (less(i,k) || less(i+1,k)){
            //将父节点与较小的节点交换
            if (less(i,i+1)){
                exch(k,i);
                sink(i);
            }else {
                exch(k,i+1);
                sink(i+1);
            }
        }
    }


    /**
     * 堆的下沉 父结点大于子节点,将父节点与较小的子节点交换
     * @param k
     */
    private void sinkBetter(int k) {
        Comparable temp = pq[k];
        //判断有左孩子,有孩子就行
        while (2 * k <= count) {
            int j = 2 * k; //此轮循环中 k 与j交换
            if ((j + 1) <= count && less(j+1, j)) {
                j++; //更新为右孩子
            }
            //父结点大于子节点
            if (!less(j, k)) {
                break;
            }

            //将子节点移到父结点,父结点移到子节点 不用每次都去新建一个临时变量来交换
            pq[k] = pq[j];
            pq[j] = temp;
            k = j; //更新k的位置
        }
    }
}

4 堆排序

有了最大最小堆,堆排序变得非常简单。首先根据数组构造堆,然后依次取堆顶元素即可,对于最大堆,需要把倒序赋值数组。
堆排序的时间复杂度是O(nlogn)

    /**
     * 堆排序
     * @param array
     */
    @Override
    public void sort(Comparable[] array) {
        //时间复杂度O(n)
        MaxPQ maxPQ = new MaxPQ(array);
        for (int i = array.length - 1 ; i >= 0 ; i--){
            //时间复杂度O(logn)
            array[i] = maxPQ.delMax();
        }
    }

最后:
源代码github https://github.com/qiyei2015/Algorithms heap部分

猜你喜欢

转载自blog.csdn.net/qiyei2009/article/details/80155311