支持两种操作: 删除最大元素和插入元素, 这种数据结构叫做优先队列.
当一棵二叉树的每个节点都大于等于它的两个子节点时, 它被成为堆有序.
二叉堆是一组能够用堆有序的完全二叉树排序的元素, 并在数组中按照层级储存(不使用数组中的第一个位置).
堆的算法
用长度为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 } }
在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]); } }
堆排序可分为两个阶段
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]排序.