一、堆排序
1、堆
(二叉)堆是一个数组,它可以被近似看成是一颗完全二叉树。树上的每一个结点都对应数组的一个元素。除了最底层,该树是完全充满的,该树在数组中是从左向右填充的。表示堆的数组A通常包括两个属性:数组长度A.length和堆的元素个数A.heapSize。给定一个结点的下标i,可以知道它的父结点、左孩子和右孩子的下标分别为i/2、2i和2i+1(下标从1开始)。
最大堆(大根堆):对于除根结点的任意结点,它的值小于等于它的父结点。
最小堆(小根堆):对于除根结点的任意结点,它的值大于等于它的父结点。
2、维护堆的性质
使用下面的方法来维护最大堆的性质,它的输入是一个数组A和一个下标i,假定根结点为left(i)和right(i)的二叉树都是最大堆(即结点i的左右子树都是最大堆)但这时A[i]可能小于它的左右结点,这样就违背了最大堆的性质。下面的方法通过让A[i]的值在最大堆中“逐级下降”,从而使得以下标i为根结点的子树重新遵循最大堆的性质。
private void maxHeap(List<Integer> t,int i,int heapSize){
int lagest = i; //代表i结点和它的左右孩子中最大值的下标
int l = i*2; //左孩子下标
int r = i*2+1; //右孩子小标
//heapSize为堆的大小
if( i <= heapSize && t.get(i) < t.get(l) )
lagest = l;
else
lagest = i;
if( lagest <= heapSize && t.get(lagest) < t.get(r))
lagest = r;
if( lagest != i){ //交换最大值和根结点的值
int vis = t.get(largest);
t.set(largest, t.get(i));
t.set(i, vis);
maxHeap(t,largest,heapSize); //由于交换可能使得i结点的孩子不满足最大堆的性质
}
}
3、建堆
对于一个大小n的数组(下标从1开始),它的最后一个非叶结点是n/2(向下取整,下同),即n/2+1、n/2+2…一直到n都是叶结点。建堆就是从最后一个非结点开始自底向上维护堆的性质。
public void buildMaxHeap(List<Integer> t){
int len = t.size()-1; //由于下标从1开始,对于有一个有t个元素的list,除去list[0]外还有t-1个
for(int i = len/2; i >= 1 ; i--){
maxHeap(t,i,len);
}
}
4、堆排序
知道了建堆和维护堆的方法就可以开始堆排序了。堆排序就是交换堆的根结点和最后一个叶子结点的位置,即交换数组第一个元素和最后堆元素的位置,并把堆的大小减一,即把数组的最后一个元素不算在堆的范围内,heapSize由此减一。交换之后除了根结点外其他结点都满足最大堆的性质,所以要调用一次maxHeap维护最大堆的性质,然后又重新交换元素。
public void headSort(List<Integer> t>{
buildMaxHeap(t); //首先建立最大堆
int len = t.size()-1; //数组元素长度
int heapSize = len; //初始堆大小
for(int i = t.size()-1; i > 1 ; i--){
int vis = t.get(i);
t.set(i,t.get(1));
t.set(1,vis);
heapSize--;
maxHeap(t,1,heapSize);
}
}
二、优先队列
这里用最大堆实现最大优先队列,假设数组中的元素已经符合最大堆的性质。一般一个最大优先队列支持一下操作:
1、返回最大元素(优先级最高的元素)
这个直接返回根结点,即数组的第一个元素。
public int maxIMum(List<Integer> t){
return t.get(1);
}
2、去掉返回最大的元素
类似于堆排序,交换根结点和最后一个叶结点,然后将堆得大小减一并维护堆的性质。
public int extractMax(List<Integer> t){
if( heapSize < 1) //堆中无元素
return -1;
int max = t.get(1);
t.set(1,t.get(heapSize)); //将堆中最后一个叶结点放到根的位置
heapSize--; //去掉最后一个叶结点,相当于去掉了最大的元素
heapMax(t,1,heapSize) //维护堆的性质
return max;
}
3、将某个元素x的值增加到k,要求k>=x。
增加某个结点值,不会影响他的后代结点是最大堆的性质,但是可能影响他的父结点。所以如果x的值增加到k后大于它的父结点的值,那么交换它的它父结点,由于它的父结点本来就大于原来的x,所以交换后也不会影响后代是最大堆的性质,就这样一直向上交换直到不大于它的父结点或到达根结点。
public void increaseKey(List<Integer> t, int i,int key){
if(t.get(i) > key)
return;
t.set(i,key);
while( i > 1 && t.get(i/2) < t.get(i) ){
int vis = t.get(i/2);
t.set(i/2,t.get(i));
t.set(i,vis);
i = i/2;
}
}
4、插入一个结点到堆中
使用的方法是首先假设这个结点是一个很小额值,所以直接将其插入到堆的最后面,然后增大这个结点的值。
public void insert(List<Integer> t,int x){
if( t.size() > heapSize )
t.set(heapSize+1,x);
else
t.add(x);
heapSize++;
increatesKey(heapSize,x);
}
以上代码皆未验证。