堆是一棵完全二叉树,根据树中每个结点和其左右子结点的关系可分为大顶堆(结点不小于左右子结点),小顶堆(结点不大于左右子结点)
堆初始化是将值存储在一个一维数组heap中,从下标1到n,可以根据这个顺序想象成一棵完全二叉树。heap[1]对应的根结点
举例说明(构造大顶堆的过程)
例如heap[5]={2,6,4,3,1},可以想象成这样的一棵完全二叉树,箭头可以直接忽略。。
由于大顶堆需要上面的元素不小于下面的,所以只需要针对有子结点的结点进行判断,这又是一棵完全二叉树,结点的个数n,对应的最后的有子结点的下标是n/2。
这里n=5,n/2=2,也就是6这个结点,进行向下调整对应的 void downAdjust(int low,int high) 函数
这里向下调整有两个方向,向左和向右。
找到两个子结点中最大的那个与该结点比较,如果子结点大于该结点,就把该结点和对应子结点交换。否则就忽视。
这里6的子结点是1和3,最大的3,但是3<6,不做变动。
对2号结点(6)处理完毕,未发生变化。
接下来对1号结点(2)进行处理,依然是向下调整。
找到6和4中最大的,是2号结点(6),交换2号结点(6)和1号结点(2)的值。
此时2号结点的值(2),再将2号结点(2)进行向下调整。2号结点对应的子结点有4号结点(3)和5号结点(1)。两个中间最大的为4号结点(3),3>2 交换4号结点(3)和2号结点(2)的位置,如下
此时大顶堆就形成了。可以看出所有子结点都不大于其父结点。heap[5]={6,3,4,2,1}
但是大顶堆还并不是一个有序的序列。
堆排序需要对大顶堆再次进行调整。
将除1号结点外的每个结点与1号结点i进行交换,然后按照向下调整,调整范围为1到i-1,
构造大顶堆的时候调整范围是从i到n
步骤:
- 将5号结点(1)与1号结点(6)进行交换,此时向下调整范围为下标1到4
1.1 找到1号结点(1)子结点中最大的,3号结点(4),4>1交换两个结点
1.2 3号结点(1)下面没有子结点,该次向下调整结束
2. 将4号结点(2)与1号结点(4)进行交换,此时调整范围为下标1到3
2.1 找到1号结点(2)子结点中最大的,2号结点(3),3>2,交换两个结点.
2.2 找到2号结点(2)子结点在1到3中范围最大的,没有,所以结束。
3. 将3号结点(1)与1号结点(3)进行交换,此时调整范围为下标1到2
3.1 找到1号结点(1)子结点中在1到2范围最大的,2号结点(2),交换两个结点
之前漏了加黑的这个条件是在前面的,然后找到最大的子结点为3号结点(3),再判断超出了范围结束了调整,这是错误的,然后发现最后交换2和1,排序错误
- 将2号结点(1)与1号结点(2)进行交换,此时调整范围为下标1到2
堆排序结束。
实现代码
//向下调整
void downAdjust(int low,int high){
int i=low,j=i*2;
while(j <= high){
if(j+1<=high && heap[j+1]>heap[j]){
j=j+1;
}
if(heap[j]>heap[i]){
swap(heap[j],heap[i]);
i=j;
j=i*2;
}else{
break;
}
}
}
//构建大顶堆
void createHeap(){
for(int i=n/2;i>=1;i--){
downAdjust(i,n);
}
}
//向上调整
void upAdjust(int low,int high){
int i=high,j=i/2;
while(j>=low){
if(heap[j]<heap[i]){
swap(heap[j],heap[i]);
i=j;
j=i/2;
}else{
break;
}
}
}
//向大顶堆中插入数据x
void insert(int x){
heap[++n]=x;
upAdjust(1,n);
}
//堆排序
void heapSort(){
createHeap();
for(int i=n;i>1;i--){
swap(heap[i],heap[1]);
downAdjust(1,i-1);
}
}