索引优先队列 超大文件如何排序?
//索引优先队列 ,类似 带有键值对 key:index value:obj //二叉堆实现 public class IndexMinPQ<T extends Comparable<T>> { int[] pqueue; //索引优先队列 N->k , 从1开始 ,1为堆顶(最小) int[] k2n; //k->N ,从1开始,0是哨兵 T items[]; //k->Item ,从1开始,0是哨兵 final int maxN; //最大索引数 int N; //当前数据个数 public IndexMinPQ(int maxN) { this.maxN = maxN; pqueue = new int[maxN + 1]; k2n = new int[maxN + 1]; items = (T[]) new Comparable[maxN + 1]; for (int i = 0; i < maxN + 1; i++) { k2n[i] = -1; } } public int getMaxN() { return maxN; } public boolean contain(int k) { return k2n[k] != -1; } //k 自定义索引, k = [1 ~ maxN] ,下标从1开始 //item 数据对象 public void insert(int k, T item) { if (k < 1 || k > maxN) throw new RuntimeException("k=[1,maxN] K:" + k); if (contain(k)) { throw new RuntimeException("already contain K:" + k + " item:" + items[pqueue[k]]); // return; } N++; k2n[k] = N; //k->N pqueue[N] = k; //N->k items[k] = item; swimUp(N); } public void change(int k, T item) { items[k] = item; swimUp(k2n[k]); sinkDown(k2n[k]); } public T top() { if (N > 0) return items[pqueue[1]]; return null; } public T delTop() { if (N < 1) return null; T top = items[pqueue[1]]; //保留删除的最顶 exch(1, N); //最低的换到最顶,做sink items[pqueue[N]] = null; //删掉换到最低的最顶 k2n[pqueue[N]] = -1; pqueue[N] = 0; N--; sinkDown(1); return top; } public T del(int k) { if (k2n[k] == -1) return null; T tDel = items[k]; pqueue[k2n[k]] = pqueue[N]; //最低的换到当前 N--; swimUp(k2n[k]); //将换过来的做 swim,sink ,堆有序化 sinkDown(k2n[k]); k2n[k] = -1; //删除目标k对应的n items[k] = null; return tDel; } public int topIndex() { if (N > 0) return pqueue[1]; return -1; } public boolean isEmpty() { return N < 1; } public int size() { return N; } private void swimUp(int n) { int child = n; int parent = child / 2; while (parent > 0) { T pt = items[pqueue[parent]]; T ct = items[pqueue[child]]; if (pt.compareTo(ct) < 0) break; exch(parent, child); child = parent; parent = child / 2; } } private void sinkDown(int n) { int parent = n; int lchild = parent * 2; int rchild = lchild + 1; int targetChild; while (lchild <= N || rchild <= N) { T pt = items[pqueue[parent]]; T tc = null; targetChild = lchild; //默认用left child,当 lchild == rchild value 的时候,或不存在rchild的时候 if (rchild < N) { //targetChild ,找到2个child中更小的那个 int rcmpl = items[pqueue[rchild]].compareTo(items[pqueue[lchild]]); if (rcmpl < 0) //若 rchild 更小 targetChild = rchild; } tc = items[pqueue[targetChild]]; if (pt.compareTo(tc) < 0) break; exch(parent, targetChild); parent = targetChild; lchild = parent * 2; rchild = lchild + 1; } } private void exch(int n1, int n2) { int tmp = pqueue[n1]; //[n]->k pqueue[n1] = pqueue[n2]; pqueue[n2] = tmp; tmp = k2n[pqueue[n1]]; //[k]->n k2n[pqueue[n1]] = k2n[pqueue[n2]]; k2n[pqueue[n2]] = tmp; } @Override public String toString() { return "IndexMinPQ{" + "pqueue[N]=k =" + Arrays.toString(pqueue) + ", \nk2n[k]=N =" + Arrays.toString(k2n) + ", \nitems[k]=O =" + Arrays.toString(items) + ", \nmaxN=" + maxN + ", \nN=" + N + '}'; } public static void main(String[] args) { IndexMinPQ<Integer> ipq = new IndexMinPQ<>(10); ipq.insert(2, 22); ipq.insert(6, 66); ipq.insert(1, 11); ipq.insert(4, 44); System.out.println(ipq); ipq.change(1, 111); System.out.println(ipq); System.out.println("ipq.contain(2) :" + ipq.contain(2)); System.out.println("ipq.top() :" + ipq.top()); //2 System.out.println("ipq.topIndex() :" + ipq.topIndex()); //2 System.out.println("ipq.delTop() :" + ipq.delTop()); System.out.println(ipq); ipq.insert(10, 1); // ipq.insert(0, 1); System.out.println(ipq); while (!ipq.isEmpty()) { System.out.println("k: " + ipq.topIndex() + " v:" + ipq.delTop()); } System.out.println("===="); Random r = new Random(123); while (ipq.size() < ipq.getMaxN()) { ipq.insert(ipq.size() + 1, r.nextInt(100)); } while (!ipq.isEmpty()) { System.out.println("k: " + ipq.topIndex() + " v:" + ipq.delTop()); } } }
带索引号的优先队列
key,索引号 用于找对应value
value, 任意可比较对象,优先队列根据对象的大小排序
优先队列中存储 key,根据key 值,找到在 item[] 中的对象,根据对象比较结果,进行排序(堆有序化,上浮,下沉)
也可以用change(k,item) 对堆中已存在的某个 key值对应的对象经行更新,更新后再次对该对象进行堆有序化处理
应用:
比如有1000个已经排序好的(从小达到)大文件,每个1G,文件内单条数据1M,而机器上的内存只有1G内存(单个文件比内存还大,无法完整载入内存),如何将这1000个文件排序并输出到1个文件中?
用这个 索引优先队列就可以完成,申请一个 1000个元素的索引优先队列,并同时打开这1000个文件,给文件按照从1-1000 编号,每个文件一次只读取1条数据到 索引优先队列,并且key值对应文件编号。
当1000个文件的1000条数据读入后,从索引优先队列中取出最小值,然后单独打开一个输出文件,将最小值输出,并再次从刚才读取最小值的文件编号的文件中再次读取下一跳数据,再插入到优先队列中。
如此反复,直到单个文件的所有数据读取并处理完成后,不再对这个文件进行处理,直到所有的这1000个文件都处理完成后,就可以完成有序合并。