利用堆这种逻辑结构进行排序,难点主要在于创建堆与调整堆,利用堆进行排序无非就是大顶堆(左右孩子节点小于双亲节点)或者小顶堆(左右孩子节点都小于双亲节点)若节点下标从0开始,则每个节点的左孩子为2*i+1;右孩子节点为2*i+2;
1、定义堆结构
#define HEAPSIZE 20 //定义堆大小
#define INCSIZE 2 //定义的每次扩容的大小
typedef int ElemType;
typedef struct
{
ElemType *data;
int heapsize; //堆的大小
int cursize; //堆目前的元素个数
}heap;
2、堆的基本操作(初始化堆,销毁堆,判空,判满,堆的元素个数)
void init_heap(heap *hp) //初始化堆
{
assert(hp!=NULL);
hp->heapsize=HEAPSIZE;
hp->cursize=0;
hp->data=(ElemType *)malloc(sizeof(ElemType)*hp->heapsize);//heap->data指向堆空间
if(hp->data==NULL)exit(1);
}
void destroy_heap(heap *hp) //销毁堆
{
assert(hp!=NULL);
free(hp);
hp->data=NULL;
hp->heapsize=0;
hp->cursize=0;
}
void clear(heap *hp) //清空堆
{
assert(hp!=NULL);
hp->cursize=0;
}
bool empty_heap(heap *hp) //判空
{
assert(hp!=NULL);
return hp->cursize==0;
}
bool full_heap(heap *hp) //判满
{
assert(hp!=NULL);
return hp->cursize==hp->heapsize;
}
int size_heap(heap *hp) //求堆的元素个数
{
assert(hp!=NULL);
return hp->cursize;
}
3、调整堆
堆排序分为两步,一步是初始调整堆(即在第一次调整堆时,是从第一个非叶节点开始调整,直到调整到0节点,其中调整每个非叶节点时,是将其所包含的最大子树进行调整);另一步是在大顶堆(小顶堆)的基础上进行0节点与最后一个节点的元素交换,整个堆的元素减一,在由0节点开始向下调整最大子树,直到重新满足大顶堆(小顶堆)的要求;
void filter_down(ElemType *ar,int pos,int end,int flag) //向下调整最堆函数
{
assert(ar!=NULL);
int i=pos;int j=i*2+1;
ElemType tmp=ar[i];
while(j<=end)
{
if(flag==0) //flag的作用是决定到底是选择大顶堆还是小顶堆进行调整,flag==0则是小顶堆
{
if( j+1<=end && ar[j]>ar[j+1])j+=1;
if(ar[j]>=tmp)break;
}
else //flag==1则是大顶堆
{
if(j+1<end && ar[j]<ar[j+1])j+=1;
if(ar[j]<=tmp)break;
}
ar[i]=ar[j];
i=j;
j=i*2+1;
}
ar[i]=tmp;
}
void make_heap(heap *hp,int flag)
{
assert(hp!=NULL);
int end=hp->cursize-1;
int pos=(end-1)/2; //从第一个非叶节点开始调整
while(pos>=0)
{
filter_down(hp->data,pos,end,flag);
pos--;
}
}
void insert_heap(heap *hp,ElemType *ar,int n,int flag) //为了便于堆元素从数组ar中导入
{
assert(hp!=NULL && ar!=NULL && n>0);
for(int i=0;i<n;++i)
{
hp->data[i]=ar[i];
}
hp->cursize=n;
make_heap(hp,flag);
}
void filter_up(heap *hp,int flag)//向上调整堆元素,对堆进行插入,需要插入到堆尾,则需要从插入位置向上调整堆
{
int j=hp->cursize-1;
int i=(j-1)/2;
ElemType tmp=hp->data[j];
while(j>0)
{
if(flag==0) //同样flag为标志进行大顶堆(小顶堆)的调整
{
if(hp->data[i]<=tmp)break;
}
else
{
if(hp->data[i]>=tmp)break;
}
hp->data[j]=hp->data[i];
j=i;
i=(j-1)/2;
}
hp->data[j]=tmp;
}
void inc_heap(heap *hp) //对堆进行扩容
{
int n = INCSIZE+hp->cursize;
hp->data=(ElemType*)realloc(hp->data ,sizeof(ElemType)*n); //注意hp->data为堆上空间
if(hp->data==NULL)exit(1);
hp->heapsize=n;
}
void push_heap(heap *hp,ElemType x,int flag)
{
assert(hp!=NULL);
if(full_heap(hp)) //若果堆满了 则进行堆的扩容
{
inc_heap(hp);
}
hp->data[hp->cursize++]=x;
filter_up(hp,flag); //入堆元素是从堆尾入堆的 则需要从堆尾调整
}
4、对堆进行排序
void print_heap(heap *hp)
{
for(int i=0;i<hp->cursize;++i)
{
printf("%d ",hp->data[i]);
}
printf("\n");
}
void make_sort(heap *hp,int flag)
{
make_heap(hp,flag);//第一步创建堆(从第一个非叶节点开始)
int n=hp->cursize;
for(int i=0;i<n-1;n--)
{
swap(&hp->data[0],&hp->data[n-1]); //依次将调整好的元素与堆进行分离(放到后面)
filter_down(hp->data,0,n-2,flag);
}
print_heap(hp);
}
5、 有选择的进行大顶堆或者小顶堆时还可以用到宏定义
#define MINHEAP
void filter_down(ElemType *ar,int start,int end)
{
int i = start; // root;
int j = i*2+1; // leftchild;
ElemType tmp = ar[i];
while(j <= end)
{
#ifdef MINHEAP
if(j < end && ar[j] >= ar[j+1]) j+=1;
if(tmp <= ar[j]) break;
#else
if(j < end && ar[j] <= ar[j+1]) j+=1;
if(tmp >= ar[j]) break;
#endif
ar[i] = ar[j];
i = j;
j = i*2+1;
}
ar[i] = tmp;
}
其中堆排序还有一个应用最广的是找大数据的前几大或者前几小;