堆其实就是一个基于数组的二叉树,由于本身是数组,因此相比于普通二叉树,它可以通过简单的数值计算就能够访问节点。堆有最大堆和最小堆之分,最大堆中每个父节点值大于或等于子节点的值,最小堆则相反。
对于n元素数值而言,它的每个节点arr[i]的子节点点索引分别问2*i+1,2×i+2,根节点为0
以最大堆为例,向一个堆中添加节点时,需要将父节点路径上的节点向下移动一层,知道这个节点位于正确的位置
public static<T> void pushHeap(T[]arr,int last,T item,Comparator<? super T> comp)
{
int currentPos=last;
int parentPos=(currentPos-1)/2;
while(currentPos!=0)
{
if(comp.compare(item, arr[parentPos])<0)//易错点
{
arr[currentPos]=arr[parentPos];
currentPos=parentPos;
parentPos=(currentPos-1)/2;
}
else
break;
}
arr[currentPos]=item;
}
堆的删除节点的操作一般情况下被限制只能对根节点进行,因此当删除根节点后堆原有排列顺序就换遭到破环,因此在删除节点后必须对堆进行调整,整个删除分为两部分,
首先将根节点与末节点进行置换,事根节点处于安全状态,然后将置换后的根节点沿着子节点路径向下转移,知道叶节点处。
private static<T> void adjustHeap(T[]arr,int first,int last,Comparator<? super T> comp)
{
int currentPos=first;
int childPos=2*currentPos+1;
T target=arr[first];
while(childPos<last)//易错点
{
if(childPos+1<last&&comp.compare(arr[childPos],arr[childPos+1])>0)
{
childPos=childPos+1;
}
if(comp.compare(arr[currentPos],arr[childPos])>0)
{
arr[currentPos]=arr[childPos];
currentPos=childPos;
childPos=2*currentPos+1;
}
else
break;
}
arr[currentPos]=target;//易错点
}
public static<T> T popHeap(T[]arr,int last,Comparator<? super T> comp)
{
T temp=arr[0];
arr[0]=arr[last-1];
arr[last-1]=temp;
adjustHeap(arr,0,last-1,comp);
return temp;
}
而堆排序则是利用堆每次移除根节点(最大堆曾为最大值)的特点进行排序,因此排序分为两个过程,首先将待排序列进行堆化,然后利用popHeap方法进行排序。
public static<T> void makeHeap(T[] arr,Comparator<? super T> comp)
{
int heapPos,lastPos;
lastPos=arr.length;
heapPos=(lastPos-2)/2;
while(heapPos>=0)
{
adjustHeap(arr,heapPos,lastPos,comp);
heapPos--;
}
}
public static <T> void heapSort(T[] arr,Comparator<? super T> comp)
{
Heaps.makeHeap(arr, comp);
int i,n=arr.length;
for(i=n;i>1;i--)
{
Heaps.popHeap(arr, i, comp);
}
}
排序的时间复杂度:堆化O(n),pop执行n-1次,因此pop代价为O(log2n),所以总的时间为O(log2n)
与其他诸如快速排序和归并排序不同,堆排序最坏情况复杂度仍然为O(log2n)
为了更方便的实现倒序与正序排列,可以利用Greater类和Less类,这两个类均实现了Comparator 接口