堆:可以被视为一个完全二叉树,树的每一层都是填满的,除最后一层外。表示堆的数组A具有两个属性:length[A]是数组中元素的个数,heap_size[A]是存放在A中的堆的元素的个数。就是说,A[heap_size[A]]之后的元素都不属于相应的堆,heap_size[A]<=length[A]。树的根为A[1],给定节点下标,可以很快算出它的父节点,左孩子和右孩子节点。
PARENT(i)=i/2
LEFT(i)=2*i
RIGHT(i)=2*i+1
堆分为两种:大顶堆和小顶堆,在大顶堆中,A[PARENT(i)]>=A[i],父节点的值要大于孩子节点的值,容易知道在大顶堆中最大值存储在根节点中。而小顶堆则恰好相反。
在本次排序中,我们使用的是大顶堆。在进行排序之前,需要了解堆的一些操作。
1.保持堆的性质
所谓堆的性质,指的就是父节点的值总是大于孩子节点的值,而在刚刚建成的一颗完全二叉树中,并不一定满足该性质,所以需要做某些调整,使得二叉树成为一个大顶堆。
如下图所示,A[2]由于比它的孩子节点要大,所以不符合大顶堆的性质,于是就拿A[2]和它的左孩子A[4]和右孩子A[5]进行比较,而左孩子的值要大于右孩子,所以拿A[2]和A[4]进行交换,然后就以左孩子A[4]为讨论的节点,一直递归下去,直到到达底层或是大于它的两个孩子节点。
在程序代码中,用MAX_HEAPILF(int* A,int i)实现,A为建堆的数组,i为讨论的节点。
2.建堆
没什么可说的,就是自底向上依次调用MAX_HEAPILF函数就可以了,具体过程可参照下图:
3.堆排序
堆排序的过程是建立在前两个步骤的基础之上,由于大顶堆的根节点总是最大的,因此给定一个数组现将其化为大顶堆(用到上面那个方法建堆),然后输出根节点的数值,此时,将最后一个节点与根节点进行交换。注意,这个时候有两个变化发生,第一个堆的数目Heap_Size会减小一个,因为输出的根节点已经不需要了,又因为根节点与最后一个节点兑换了位置,此时的二叉树已经不满足堆的性质了,使用第一个方法,调用MAX_HEAPILF(A,1)使其重新成为一个堆(这里数组下标从1开始)。重复上述过程,直到将堆中所有元素全部输出。
一下是图示过程:
最后是程序的代码:
#include <stdio.h>
#include <stdlib.h>
#define MAX 20
//堆的大小,注意,和数组本身的大小事有区别的,在堆排序的函数中要尤其小心
int Heap_Size=20;
//保持堆的性质,其实就是拿A[i]和左右两个孩子比较大小,然后递归下去就行了
void MAX_HEAPIFY(int* A,int i)
{
int Largest;
int L=2*i;
int R=2*i+1;
if(L<=Heap_Size&&A[L]>A[i])
Largest=L;
else
Largest=i;
if(R<=Heap_Size&&A[R]>A[Largest])
Largest=R;
if(Largest!=i)
{
int temp=A[i];
A[i]=A[Largest];
A[Largest]=temp;
MAX_HEAPIFY(A,Largest);
}
}
//建堆的过程
void Build_Max_Heap(int* A)
{
int i=MAX/2;
for(;i>=1;i--)
{
MAX_HEAPIFY(A,i);
}
}
//堆排序算法的过程
void Heap_Sort(int* A)
{
Build_Max_Heap(A);
int i=MAX;
for(;i>2;i--)
{
printf("%d ",A[1]);
int temp=A[1];
A[1]=A[i];
A[i]=temp;
Heap_Size--;
MAX_HEAPIFY(A,1);
}
}
//主函数进行测试,注意,数组下标从1开始,第一个元素存放数组的大小
int main(void)
{
int A[MAX+1]={MAX,34,23,67,34,2,5,6,1,78,34,2,4,5,44,12,6,97,23,54,10};
Heap_Sort(A);
return 0;
}