优先队列和堆排序
1. 优先队列
优先队列支持的两种操作:删除最大(小)元素,插入元素。和队列以及栈类似。
可以将优先队列比作一个黑盒:里面存放最大(小)的若干元素,支持向里面添加元素,取出删除最大(小)元素。
jdk自带实现:PriorityQueue.
jdk还有双向顺序队列ArrayDeque和双向链式队列LinkedList。
实现方法:
1).数组无序实现:insert()方法和栈的push()方法一样,删除最大元素可以内循环找出最大元素然后和边界元素交换删除。
2).数组有序实现:插入时保持数组有序,删除时直接删除边界元素。
3).链表实现。
4).堆实现。
比较:
数据结构 | 插入元素 | 删除元素 |
有序数组 | N | 1 |
无序数组 | 1 | N |
堆 | logN | logN |
理想情况 | 1 | 1 |
2.堆
用完全二叉树来表示堆,用数组即可实现。为方便表示不使用数组第一个位置,根节点在位置1,设一节点在位置k,则它的子节点在2k和2k+1,父节点在k/2。
堆里面最重要的操作就是要保证堆有序的两个操作:上浮swim()和下沉sink()。上浮是以此节点开始,比较它和父节点的值,若不满足堆,则交换位置,直到根节点。下沉类似:和其子节点比较,不满足堆则交换,直到到达堆的底部。
插入操作:将新元素放到数组末尾,增加堆的大小并上浮新元素到合适位置。
删除最大(小)元素:从数组顶端删除最大(小)元素,并将数组最后一个元素放到顶端,减小堆大小,下沉该元素到合适位置。
用堆实现的优先队列:
1 //优先队列,用堆实现 2 //可以向队列插入数据,并删除最大的数,插入删除的的复杂度都是logN 3 public class MaxPQ <Key extends Comparable<Key>>{ 4 private Key[] pq; 5 private int N = 0; 6 7 @SuppressWarnings("unchecked") 8 public MaxPQ(int maxN) { 9 pq = (Key[]) new Comparable[maxN+1]; 10 } 11 12 public boolean isEmpty(){ 13 return N == 0; 14 } 15 public int size(){ 16 return N; 17 } 18 public void insert(Key v){ 19 pq[++N] = v; 20 swim(N); 21 } 22 private void swim(int n) { 23 while(n > 1 && less(n/2,n)){ 24 exch(n/2,n); 25 n = n/2; 26 } 27 } 28 29 private boolean less(int i, int j) { 30 return pq[i].compareTo(pq[j]) < 0; 31 } 32 33 public Key delMax(){ 34 Key max = pq[1]; 35 exch(1,N--); 36 pq[N=1] = null; //防止对象游离 37 sink(1); 38 return max; 39 } 40 41 private void sink(int i) { 42 while(2 * i <= N){ 43 int j = 2*i; 44 if(j < N && less(j,j+1)) j++; 45 if(!less(i,j)) break; 46 exch(i,j); 47 i = j; 48 } 49 } 50 51 private void exch(int i, int j) { 52 Key t = pq[i]; 53 pq[i] = pq[j]; 54 pq[j] = t; 55 } 56 }
3.索引优先队列
它允许用例引用优先队列中的元素,方法是给每个元素一个索引。
1 //索引优先队列,保存索引及其对应的项 2 //用堆实现,复杂度为logN; 3 public class IndexMinPQ<Item extends Comparable<Item>> { 4 private int[] pq; //保存索引 5 private int[] qp; //保存索引所对应堆中的下标 6 private Item[] items; //保存项 7 private int N = 0; 8 9 public IndexMinPQ(int maxN) { 10 pq = new int[maxN + 1]; //数组[0]未使用 11 qp = new int[maxN + 1]; 12 items = (Item[]) new Comparable[maxN + 1]; 13 for(int i = 0; i <= maxN; i++) qp[i] = -1; 14 } 15 public void insert(int k, Item item){ 16 N++; 17 pq[N] = k; 18 qp[k] = N; 19 items[k] = item; 20 swim(N); 21 } 22 private void swim(int n) { 23 while(n > 1 && less(n, n/2)){ 24 exch(n,n/2); 25 n = n/2; 26 } 27 } 28 private void exch(int i, int j) { 29 int t = pq[i]; 30 pq[i] = pq[j]; 31 pq[j] = t; 32 qp[pq[i]] = i; 33 qp[pq[j]] = j; 34 } 35 private boolean less(int i, int j) { 36 return items[pq[i]].compareTo(items[pq[j]]) < 0; 37 } 38 public void change(int k, Item item){ 39 items[k] = item; 40 swim(qp[k]); 41 sink(qp[k]); 42 } 43 private void sink(int i) { 44 while(2*i <= N){ 45 int j = 2*i; 46 if(j < N && less(j+1,j)) j++; 47 if(less(i,j)) break; 48 exch(i,j); 49 i = j; 50 } 51 } 52 public boolean contains(int k){ 53 return qp[k] != -1; 54 } 55 //删除索引为k的项 56 public void delete(int k){ 57 int index = qp[k]; 58 exch(index, N--); 59 swim(index); 60 sink(index); 61 qp[N+1] = -1; 62 items[pq[N+1]] = null; 63 } 64 public Item min(){ 65 return items[pq[1]]; 66 } 67 public int minIndex(){ 68 return pq[1]; 69 } 70 //删除最小项,并返回它的索引 71 public int delMin(){ 72 int minIndex = pq[1]; 73 exch(1,N--); 74 sink(1); 75 qp[N+1] = -1; 76 items[pq[N+1]] = null; 77 return minIndex; 78 } 79 public boolean isEmpty(){ 80 return N == 0; 81 } 82 public int size(){ 83 return N; 84 } 85 }
4.堆排序
堆排序可以分为两个阶段:堆的构造阶段和下沉排序阶段。每次循环都将堆顶元素和最后一个交换,最后即为有序的。构造阶段的高效方法是从N/2开始,从右至左用sink()构造子堆。跳过大小为1的子堆。
下面的实现,我使用了数组的0下标,因为如果不使用0下标,在堆排序后还要用一层循环来给0号元素排序,麻烦。虽然这样在堆实现上有点麻烦。
1 public class HeapSort { 2 public static void main(String[] args) { 3 String[] a = new String[]{"G","F","E","D","C","B","A","H","I","J","K"}; 4 heapSort(a); 5 for(int i = 0; i < a.length; i++) 6 System.out.print(a[i]); 7 } 8 public static void heapSort(Comparable a[]){ 9 int N = a.length-1; 10 for(int i = (N-1)/2; i >= 0; i--){ 11 sink(a,i,N); 12 } 13 while(N > 0){ 14 exch(a,0,N--); 15 sink(a,0,N); 16 } 17 } 18 private static void exch(Comparable[] a, int i, int j) { 19 Comparable t = a[i]; 20 a[i] = a[j]; 21 a[j] = t; 22 } 23 private static void sink(Comparable[] a, int i, int N) { 24 while(2*i+1 <= N){ 25 int j = 2*i+1; 26 if(j < N && a[j].compareTo(a[j+1]) < 0) j++; 27 if(a[i].compareTo(a[j])> 0) break; 28 exch(a,i,j); 29 i = j; 30 } 31 } 32 }
补充:
Java系统库中的优先队列:java.util.PriorityQueue.
该优先队列对基本数据类型默认使用自然顺序,即MinPq,小顶堆。可以在构造器中使用指定的比较器来使其成为MaxPQ。
相关方法:add;peek;poll;remove;clear;size;contains;toArray 等等。
不允许使用null元素。
默认构造器的初始容量为11.
内部实现:使用数组,对元素采用的是堆排序。