普通堆(本文仅以最大堆为例)的局限性主要有以下两点:
1.如果原始待排序或者求最值的数据元素很大,交换它们的位置性能损耗。
以上代码是学习Princeton《Algorithms》参考算法思想设计而来。
1.如果原始待排序或者求最值的数据元素很大,交换它们的位置性能损耗。
2.元素在堆化后的数组中位置不确定,只能过遍历找到。
解决上述两个弊端就是用索引的方式,二叉树的节点里存储的不再是原始数据,而是原始数据在数组中的索引。在插入弹出操作中的上浮下沉比较交换中,比较大小仍然是原始数据,但交换和、上浮下沉就是对于原始数据的索引而言。显然要在最大堆的数据结构中加入一个索引数组,该索引数组的索引是二叉树层次遍历的序列序号(i),存储的是二叉堆中第i个元素在原始数据数组中的索引。数据结构如下:
public class IndexMaxPQ<Item extends Comparable<Key>> { public static final int DEFAULT_SIZE=17; public Key[] data; //存放原始待排序或者求最值的数据 public int[] index; //存储最大堆的原始数据在data中的数组索引,index[i]=x表示堆的层次遍历序列的第i个位置的值为data[x] int size; //最大堆的大小 }
关键操作上浮和下沉因为对二叉堆节点而言所有没有任何变化:
//上浮操作 private void swim(int i) { while(i/2>=1) { if(!less(i,i/2)) exchange(i,i/2); else break; i/=2; } } //下沉操作 private void sink(int i) { while(2*i<=this.size) { int j=2*i; if(j<this.size && less(j,j+1)) j++;//索引为i的值与其子节点中较大的进行比较 if(!less(j,i)) exchange(i,j); else break; i*=2; } }但是比较交换的内部代码发生了变化:
private boolean less(int a,int b) { if(this.data[this.index[a]].compareTo(this.data[this.index[b]])<=0) return true; else return false; } private void exchange(int i,int j) { int tmp=this.index[i]; this.index[i]=this.index[j]; this.index[j]=tmp; }
插入、删除、弹出、修改四个基本操作都是在上浮和下沉、比较和交换上完成。但同时要维护原始数据数组data、索引数组index、堆的规模size。代码如下:
public void delete(int i)//删除data数组索引为i的元素 { int j=0; this.data[i]=null; for(;j<=this.size;j++) if(this.index[j]==i) break; this.exchange(j, this.size--); this.swim(j); this.sink(j); } public int delMax() { int ret=this.index[1]; exchange(1,this.size); this.index[this.size]=0; sink(1); this.data[this.size--]=null; return ret; } public void change(int k,Key Key) { if(k>=1 && k<=this.size) { this.data[k]=Key; int i=1; for(;i<this.index.length;i++) if(this.index[i]==k) break; swim(i); sink(i); } } public void delete(int i)//删除data数组索引为i的元素 { int j=0; this.data[i]=null; for(;j<=this.size;j++) if(this.index[j]==i) break; this.exchange(j, this.size--); this.swim(j); this.sink(j); }
以上代码是学习Princeton《Algorithms》参考算法思想设计而来。