在这篇博客中,我们实现二叉堆结构,二叉堆结构具有两个特点:结构性和堆序性
结构性:必须是一颗完全二叉树,树的插入从左到右
堆序性:父节点必须小于等于(小堆)或者大于等于(大堆)左右节点
在二叉堆的具体实现中,通过一个数组来储存所有的元素
这里进行除法运算时采取“向下取整”;
如果一个元素的数组下标为 i,那么这个元素的父节点就是(i-1)/2; 这个元素的左孩子节点就是(i*2)+1;
首先给出这些操作的头文件
#ifndef __HEAP_H__
#define __HEAP_H__
#include<stdio.h>
#include<assert.h>
#include<malloc.h>
typedef int DataType;
typedef int (*Compare)(DataType,DataType) ;
typedef struct Heap
{
DataType * array;
int size;
int capacity;
Compare com;
}Heap;
typedef struct PriorityQueueInit
{
Heap heap;
}Priority;
//向下调整
void AdjustDownHeap(Heap * ph,int parent);
//创建堆
void CreateHeap(Heap *ph ,DataType* array,int size);
//初始化堆
void InitHeap(Heap* ph,int size,Compare com);
//小堆比较
int Less (DataType left,DataType right);
//交换
void swap(DataType *parent,DataType*child);
//插入
void InsertHeap(Heap *ph,DataType data);
//检查数组容量是否足够
void ChickSize(Heap* ph);
//向上调整
void AdjustUpHeap(Heap*ph);
//删除
void DeleteHeap(Heap* ph);//可能会删除堆顶元素,传二级指针
//堆排序
void HeapSort(Heap *ph);
//优先级队列
void PriorityQueueInit(Priority * pq,Compare com);
void PriorityQueuePush(Priority * pq , DataType data);
void PriorityQueuePop(Priority *pq );
int PriorityQueueSize(Priority * pq);
int PriorityQueueEmpty(Priority * pq);
//获取堆顶元素
DataType TopHeap(Heap * ph);
//海量数据Top k 问题
void TopK (Heap * ph,DataType * array,int k,int size);
#endif//__HEAP_H__
要使用堆,就要先初始化堆,主要包括给数组赋值,为函数指针赋值,这里需要特殊强调一下函数指针的定义
typedef int (*Compare)(DataType,DataType) ;
这句定义的含义是,将一个参数为(DataType,DataType),返回值为int的函数指针类型重命名为Compare
以后就可以用Compare来接受函数名了
//大堆比较
int Great(DataType left,DataType right)
{
return left<right?1:0;
}
//小堆比较
int Less (DataType left,DataType right)
{
return left>right?1:0;
}
上面定义了两个函数,在下面初始化函数中我们可以直接将Less和Great当做实参传给形参 Compare com
这样,在后序的使用中就不用再次传了
//初始化堆
void InitHeap(Heap*ph,int size,Compare com)
{
assert(ph);
ph->array=(DataType*)malloc(sizeof(DataType)*size);
if(NULL==ph->array)
{
assert(0);
return ;
}
ph->size=0;
ph->capacity=size;
ph->com=com;
}
初始化完成后,我们还需要设计一个调整函数,将没有堆结构的数组,调整成堆结构,这个函数叫做向下调整函数
向下调整函数
给一个堆元素,经过比较和 交换,使这个堆元素以下的所有部分都具有堆结构
//向下调整
void AdjustDownHeap(Heap * ph,int parent)
{
int child;
assert(ph);
child=parent*2+1;
if((ph->size-1>child+1)&&(ph->com(ph->array[child],ph->array[child+1])))
//条件一是为了处理有左子树,没有右子树的情况
{
child++;
}
if(ph->com(ph->array[parent],ph->array[child]))
{
swap(&ph->array[parent],&ph->array[child]);
parent=child;
child=parent*2+1;
if(child<ph->size)
{
AdjustDownHeap(ph,parent);//调整完一个,后面也会出现堆错乱,要再次调整
}
}
}
有了调整函数,就可以进行堆的创建了
创建堆
//创建堆
void CreateHeap(Heap *ph ,DataType* array,int size)//size是数组的元素个数
{
int parent=0;
int i=0;
assert(ph);
for(;i<size;i++)//给堆的数组赋值,此时还不具有堆结构
{
ph->array[i]=array[i];
ph->size++;
}
parent=(size-2)>>1;
for(;parent>=0;parent--)//循坏得调整堆元素,使其具有堆结构
{
AdjustDownHeap(ph,parent);
}
}
创建好之后,就可以对其进行插入
在插入之前,首先要进行数组容量的检查
//检查数组容量是否足够
//检查数组容量是否足够
void ChickSize(Heap* ph)
{
assert(ph);
if(ph->size==ph->capacity)
{
int i=0;
DataType* ret;
DataType* Del;
ph->capacity*=2;
ret=(DataType*)realloc(ph->array,sizeof(DataType)*ph->capacity);
if(ret==NULL)
{
return;
}
if(ret!=ph->array)//realloc函数可能产生新的空间,释放时需要判断
{
for(;i<ph->size;i++)
{
ret[i]=ph->array[i];
}
Del=ph->array;
free(Del);
ph->array=ret;
}
}
}
检查完成之后,还要设计给具有堆结构的数组插入一个元素的调整函数,我们叫它,向上调整函数,因为会将新的元素插入数组的末尾,然后向上去调整,这里也可以使用向下调整函数,但是已具有堆结构,就会很浪费,可以直接向上比较交换就可以了
//向上调整,小数向下取整
//向上调整,小数向下取整
void AdjustUpHeap(Heap*ph)
{
int parent;
int child;
assert(ph);
child=ph->size-1;
parent =(child-1)>>1;
while(child)
{
if(ph->com(ph->array[parent],ph->array[child]))
{
swap(&ph->array[parent],&ph->array[child]);
}
child=parent;//向上改变孩子的位置,进行调整
parent=(child-1)>>1;
}
}
然后就可以 进行插入了
//插入
void InsertHeap(Heap *ph,DataType data)
{
assert(ph);
ChickSize(ph);
ph->array[ph->size]=data;
ph->size++;
AdjustUpHeap(ph);
}
删除(只有头删)交换堆顶和堆尾元素,使堆的元素个数减1,也就是丢掉堆尾元素,重新调整
//删除(只有头删)
void DeleteHeap(Heap* ph)
{
int Last;
int Top;
assert(ph);
if((ph)->size==0)
{
return ;
}
if((ph)->size==1)
{
(ph)->size=0;
}
Last=(ph)->size-1;
Top=0;
swap(&(ph)->array[Last],&(ph)->array[Top]);
(ph)->size--;
AdjustDownHeap(ph,Top);
}
堆的应用
堆排序就是用删除的方式遍历整个堆,最后底层数组就是有序的数组
//堆排序(堆本身只有堆的特性,并不是拍好序列的,所以需要排序时循环的拿出和调整),具体是每次交换堆顶和堆尾元素,丢掉堆尾元素,重新调整,这样到最后被丢掉的就已经排好序了
void HeapSort(Heap *ph)
{
int Last;
int Top;
Last=ph->size-1;
Top=0;
assert(ph);
if(Last==0&&Last==1)
{
return ;
}
while(Last)
{
swap(&ph->array[Last],&ph->array[Top]);
ph->size--;
if(Last!=1)//如果不判断,交换后又调整,等于没交换
{
AdjustDownHeap(ph,Top);
}
Last--;
}
}
在二叉堆的具体实现中,通过一个数组来储存所有的元素
测试函数
void TestHeap ()
{
Heap str;
Heap* tmp;
int sz;
DataType array[]={9,2,4,7,8,3};
sz=sizeof(array)/sizeof(array[0]);
tmp=&str;
InitHeap(&str,sz,Less);
CreateHeap(&str ,array,sz);
InsertHeap(&str,1);
DeleteHeap(&tmp);
HeapSort(&str);
}
//海量数据Top k 问题
void TopK (Heap * ph,DataType * array,int k,int size)//k 是 要求的前几位,
{
DataType Top;
assert(ph);
CreateHeap(ph,array,k);//先创建一个k个元素的堆,小堆
Top=TopHeap(ph);//在循环的将后面数据与堆顶元素比较
while(k<size)
{
if(Top<array[k])
{
swap(&array[k],&ph->array[0]);//比堆顶元素大,交换
AdjustDownHeap(ph,0);//调整,这样下来最后再进行排序,就可以得到Top k
}
k++;
}
}
Top K测试函数
void TestTopK()
{
int sz;
Heap str;
DataType array []={6,78,93,0,3,1,23,78,43,89,35,57,13};
InitHeap(&str,10,Less);//必须采用小堆才能排出来降序 Less
sz=sizeof(array)/sizeof(array[0]);
TopK (&str,array,10,sz);
HeapSort(&str);//最后进行堆排序
}
优先级队列:顾名思义,它已经不是普通意义上的具有先进先出特性的队列了,它在出队列的时候会进行选择优先级最高的元素。
优先级:如何使一个队列具有优先级呢?
在这篇博客中,我们实现优先级队列使用二叉堆结构,二叉堆结构具有两个特点:结构性和堆序性
结构性:必须是一颗完全二叉树,树的插入从左到右
堆序性:父节点必须小于或者大于左右节点
这样就使得堆顶的元素一定是整个堆中最大或者最小的元素,对堆顶元素的出队列也便有了一定的顺序。这就是优先级。
在二叉堆的具体实现中,通过一个数组来储存所有的元素,
现在我们给出对于优先级队列的基本操作,
这是优先级队列的结构体
typedef struct PriorityQueueInit
{
Heap heap;//一个堆
}Priority;
基本操作函数的定义
void PriorityQueueInit(Priority * pq,Compare com);//初始化
void PriorityQueuePush(Priority * pq , DataType data);//入队列
void PriorityQueuePop(Priority *pq );//出队列
int PriorityQueueSize(Priority * pq);//队列元素个数
int PriorityQueueEmpty(Priority * pq);//判空
//初始化优先级队列
void PriorityQueueInit(Priority * pq,Compare com)
{
assert(pq);
pq->heap.capacity=3;
pq->heap.array=(DataType*)malloc(sizeof(DataType)*pq->heap.capacity);
pq->heap.size=0;
pq->heap.com=com;
}
//入优先级队列
void PriorityQueuePush(Priority * pq , DataType data)
{
assert(pq);
InsertHeap(&(pq->heap),data);
}
//出优先级队列
void PriorityQueuePop(Priority *pq )
{
assert(pq);
DeleteHeap(&(pq->heap));
}
//优先级队列的大小
int PriorityQueueSize(Priority * pq)
{
assert(pq);
return pq->heap.size;
}
//判空
int PriorityQueueEmpty(Priority * pq)
{
assert(pq);
return pq->heap.size==0?0:1;
}