优先队列

    支持两种操作: 删除最大元素和插入元素, 这种数据结构叫做优先队列.

    当一棵二叉树的每个节点都大于等于它的两个子节点时, 它被成为堆有序.

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

堆的算法

用长度为N+1的pq[]来表示一个大小为N的堆, 为了方便,不使用pq[0], 堆元素存放在pq[1]至pq[N]中.

在堆得有序化过程中, 有两种情况:

1.由下至上的堆有序化(上浮)

    /*上浮*/
    private void swim(int k){
        while(k>1 && less(k/2, k)){
            exch(k/2,k);
            k = k/2;
        }
    }

 当 pq[k/2] 比 pq[k] 小时, less(k/2, 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;
    private int N = 0;    
    
    private MaxPQ(int maxN){
        pq = (Key[]) new Comparable[maxN+1];   //pq[0] 不用
        
    }
    
    public boolean isEmpty(){
        return N==0;
    }
    
    public int size(){
        return N;
    }
    
    public void insert(Key v){
        pq[++N] = v;
        swim(N);
    }
    
    //删除最大元素
    public Key delMax(){
        Key max= pq[1];
        exch(1,N--);  //第一个元素和最后一个元素进行交换
        pq[N+1] = null;    //防止对象游离
        sink(1);
        return max;
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub

    }

}
View Code

在insert()中, 我们把N+1并把新元素添加到最后面, 然后用swim()将堆有序化. 在delMax()中,我们从pq[1]得到需要返回的元素,然后交换pq[N]到pq[1], 将N-1并调用sink()使堆有序化, 同时还要将pq[N+1](因为之前减过1)设为null, 以便系统回收空间.  

堆排序算法

    public static void HeapSort(int[] nums){
        int N = nums.length;
        /*构造最大堆*/
        for(int i=N/2; i>=1; i--){
            sink(nums, i, N);
        }
        while(N>1){
            exch(nums,1,N--);
            sink(nums,1,N);
        }
    }
    
    public static void sink(int[] nums,int k,int N){
        while(2*k<=N){
            int j = 2*k;  /*左孩子节点*/
            if(j<N && less(nums,j,j+1)) j++;
            if(!less(nums,k,j)) break;    /*k为最大节点*/
            exch(nums,j,k);    /*k与最大节点交换*/
            k = j;    
        }
    }
    
    private static boolean less(int[] nums, int i, int j) {
        return nums[i-1] < nums[j-1];
    }

    private static void exch(int[] nums, int i, int j) {
        int swap = nums[i-1];
        nums[i-1] = nums[j-1];
        nums[j-1] = swap;
    }    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int[] nums= {8,6,4,5,7,1,3,2,9};
        HeapSort(nums);
        System.out.println("排序后");
        for(int i = 0; i<nums.length; i++){
            System.out.println(nums[i]);
        }
    }
View Code

堆排序可分为两个阶段

1.将元素数组重新组织, 构建堆.

我们只需要扫描前半部分数组即可, 因为数组的每个位置已经是一个子堆的根节点了.

从右到左用sink()函数构造子堆. , 首先跳过大小为1的子堆, 最后在位置1上调用sink()方法,扫描结束 这时已经构建了一个最大堆.

2.元素下沉排序.

sink()方法将数组nums[1]到nums[n] 进行排序, for循环构造了堆, 然后while循环将nums[N]与nums[1]交换并维护堆的性质.

如此重复,直到堆为空.  

这里需要注意的是 less()方法和exch()方法索引都需要减一, 因为这里是对数组nums[0]-nums[N-1]进行排序, 而sink()方法是对nums[1]-nums[N]排序.

原文地址:https://www.cnblogs.com/tanxing/p/5611410.html