经常在学习一些相关的计算机知识时会遇到:xxx是基于堆的。乍一听上去感觉很高端,给人的第一印象就是一堆杂乱的数据摆在一起,然后进行特定的操作。实际上,堆(heap)是一棵特殊的完全二叉树,而且是采用的静态存储方式。它要求每个根节点都比他的左右子树结点值大(大顶堆)或者都小(小顶堆),若是我们每次取出根节点的值,然后删除掉根节点,对整个堆进行调整,再重复上面操作,这样就能得到一个有序的序列。
建堆的思想是,先逐个插入结点建立一棵完全二叉树(因为采用的静态存储,逐个赋值即可得到),然后对非叶结点逐个进行向下调整,调整完毕后即可得到堆,下面是调整和建堆操作:
void downAdjust(int low,int high)//将结点编号为low的结点向下调整,直到合适的位置
{
int i=low,j=i*2;//i是待调整结点,j是其左子节点
while(j<=high)//保证i不是叶子结点
{
if(j+1<=high&&heap[j+1]>heap[j])//若有右子节点,且其值大于左子节点,让j存储右子节点下标
j=j+1;
if(heap[j]>heap[i])//j表示子节点的最大值,如果比待调整结点大,交换其位置
{
swap(heap[i],heap[j]);
i=j;//i向下移动,保持j为i的左子节点
j=i*2;
}
else//若子节点比待比较结点小,则不用交换,直接退出
break;
}
}
void creat_heap()//完全二叉树有一半(上整)结点是叶子结点,是被动调整的,非叶结点范围是[1,n/2]。逐个调整即可建堆
{
for(int i=n/2;i>=1;i--)//这里采用的是倒序枚举,每一次后面部分总是满足堆要求
{
downAdjust(i,n);
}
}
对于堆,一般涉及的比较多的是堆排序算法,即上面提到的得到有序序列的方法。在每次删掉根节点后,调整的策略是把最后一个结点值赋予根节点,直接删除最后一个结点,然后将根节点向下调整,不断重复操作,即可得到堆排序如下:
void heapSort()
{
creat_heap();//建堆
for(int i=n;i>1;--i)//i始终对应最后一个结点编号
{
printf("%d\t",heap[1]);
swap(heap[i],heap[1]);//交换根节点和最后一个结点的值
downAdjust(1,i-1);//对根结点在剩余的n-1个结点中进行向下调整
}
}
但到这里也只能算是个静态排序算法,因为没考虑插入操作,也就是说我们的堆一开始就是固定的,得到的排序结果也是固定的,下面讨论插入操作。对于二叉树而言,一般插入操作都是找到空指针插入然后做简单调整即可,由于对是一棵完全二叉树,在插入结点后还要保持这一特点,所以一般结点直接插入到最后,然后再对这个结点进行向上调整操作。
void upAdjust(int low,int high)//插入元素一般插在最底层最右边,需要向上调整
{
int i=high;//i是要调整结点,j是其父结点
int j=i/2;
while(j>=low)
{
if(heap[j]<heap[i])//这里和下调不同,只需要一次比较
{
swap(heap[j],heap[i]);
i=j;//i向上移动
j=i/2;
}
else//已到最终位置
break;
}
}
void insertNode(int x)//插入结点值为x的结点
{
heap[++n]=x;//将结点插入到最后
upAdjust(1,n);
}