2.4.4.7/Ex2_2_33/34 索引优先队列
很多时候,数据输入流有多个,且数量巨大(10亿个),使用索引可以确定不同输入流,使用优先队列可以确保无论输入多长都可以读入并排序,二者结合即索引优先队列
import edu.princeton.cs.algs4.In;import edu.princeton.cs.algs4.StdOut;/*本例使用merge(),基于索引优先队列,将作为命令行输入的多行字符串(字符串本身升序排好),归并为为一行有序的输出.*/public class IndexMinPQ<Key extends Comparable<Key>> {private Key[] keys;//存放原始数据,顺序不变private int[] pq;//存放索引的二叉堆,从1开始private int[] qp;//存放索引的逆序,qp[pq[i]] = pq[pq[i]] = i;private int N;//PQ中元素数量/*----------constructor and Helpers-----------*/public IndexMinPQ(int maxN){keys = (Key[]) new Comparable[maxN+1];pq = new int[maxN+1];qp = new int[maxN+1];//当索引i不在队列中,令qp[i] = -1for (int i =0; i<= maxN; i++) qp[i] = -1;}public boolean isEmpty(){ return N == 0;}public int size(){ return N;}//通过返回对索引的查找结果确定是否包含元素public boolean contains(int k){ return qp[k] != -1; }/*----------major function-----------*/public void insert(int pri, Key key){N++;pq[N] = pri;//保存索引qp[pri] = N;//保存pq的逆序keys[pri] = key;swim(N);}public Key min(){ return keys[pq[1]]; }public int delMin(){int indexOfMin = pq[1];exch(1, N--);sink(1);keys[pq[N+1]] = null;qp[pq[N+1]] = -1;return indexOfMin;//返回indexOfMin,确定输入流}private void change(int pri, Comparable key){keys[pri] = key;int index = qp[pri];swim(index);sink(index);}public void delete(int pri){int index = qp[pri];exch(index, N--);swim(index);sink(index);keys[pri] = null;qp[pri] = -1;}//此处使用large(),构造小顶堆private boolean larger(int i, int j){return keys[pq[i]].compareTo(keys[pq[j]]) > 0;}private void exch(int i, int j){int swap = pq[i];pq[i] = pq[j];pq[j] = swap;qp[pq[i]] = i;qp[pq[j]] = j;}public void swim(int k){while (k > 1 && larger(k/2, k)){exch(k/2, k);k = k/2;}}public void sink(int k) {while (k * 2 <= N) {int j = k * 2;if (larger(j, j + 1) && j < N) j++;//取子结点较大者;j<N,取等号j++溢出if (!larger(k, j)) break;//比较父子大小exch(k, j);k = j;}}public static void merge(In[] streams){int N = streams.length;IndexMinPQ<String> pq = new IndexMinPQ<>(N);for (int i = 0; i < N; i++)if (! streams[i].isEmpty())pq.insert(i,streams[i].readString());while (!pq.isEmpty()){StdOut.println(pq.min());int i = pq.delMin();if (! streams[i].isEmpty())pq.insert(i,streams[i].readString());}}/*-----------test function-----------*/public static void main(String[] args){int N = args.length;In[] streams = new In[N];for (int i = 0; i < N; i++)streams[i] = new In(args[i]);merge(streams);}}
算法理解:
关于索引优先队列:
在一般的优先队列中,我们只能通过delMax()访问最大元素,无法直接访问队列中其他元素进行更新或删除,要是建立一个映射关系,让队列中第k个元素操纵着原始数据,队列中针对这种关系排序即可,不改变原始数据结构.实现这种映射控制需要索引(priority)
上述算法中,keys[]即为原始数据结构,只有change()或delete()会改变其中元素,排序对其无影响;pq[]为索引队列,里面依据keys[]中元素堆有序保存了对应的索引,pq[1]即保存了最小元素的索引,比如pq[1]=3,说明keys[3]的元素最小
当要更新元素时,如将keys[5]元素改变,再去维护pq[i]的值,但除非遍历pq[],无法获悉哪个元素pq[i]保存了5,因此必须再添加一个数组,保存与对象相关的队列数组元素下标,qp[pq[i]]=pq[qp[i]]=i;,假如qp[5] = 3,则pq[3]中保存了索引5,这时恢复pq有序,对应恢复qp有序.
关于归并输入流:
假设命令行参数传入m1.txt,m2.txt,m3.txt三个文件,每个文件中有一列升序的字符串,则main函数stream[]是含有N=3的数组,则merge()一开始读取stream[]三个流元素的首个字符串,三个字符串构成索引优先队列,然后delMin(),输出并删除三者最小,再补充删去元素索引对应的下个字符串,知道所有流下的所有字符串都被输出并删除,达成归并不同流并排序的目的.
图示:

算法分析:
大小N的索引优先队列,插入insert(),改变优先级change(),删除delete(),删除最小元素delMin()操作所需比较次数和logN成正比
证明:由堆中所有路径最长为~lgN,可得
N个元素的堆索引优先队列不同操作最坏情况下成本
| 操作 | 比较次数增长级 |
|---|---|
| insert() | logN |
| change() | logN |
| contains() | 1 |
| delete() | logN |
| min() | 1 |
| minIndex() | 1 |
| delMin() | logN |
Review:
- 索引pri是指定key[]中的某个位置,实际元素即key[pri],pq[i]==pri代表此索引堆有序后在堆中第i处,为方便找出key[pri]的索引在pq[]哪个位置,引入qp[],使得qp[pri]=qp[pq[i]]=i;
- sink()中
if (larger(j, j+1) && j < N) j++构造小顶堆找子结点较小者,构造大顶堆找子结点较大者 - 三个边界取等问题
- 构造函数
for (int i=0; i <= maxN; i++),因为contains通过qp[pri]是否为-1判断,qp[maxN]即保存key[maxN]的索引倒序 - swim()
while (k > 1 && larger(k/2, k)),若k>=1,k=1时操作空元素pq[0] - sink()
while (k*2 <= N),当k恰为2次幂时,层数加一,需要判断下沉.
- insert()在末尾操作只需swim(),change()和delete()涉及中间操作要先swim()在sink()
- merge()实现不唯一
private static void merge(In[] streams){int N = streams.length;IndexMinPQ pq = new IndexMinPQ(N);//读入初始三个值for (int i = 0; i < N; i++)if (! streams[i].isEmpty())pq.insert(streams[i].readString(), i);StdOut.println(pq.showMin());int i = pq.delMin();while (!streams[i].isEmpty()){pq.insert(streams[i].readString(), i);StdOut.println(pq.showMin());i = pq.delMin();}}
