普通堆(本文仅以最大堆为例)的局限性主要有以下两点:
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》参考算法思想设计而来。