堆的基本操作&优先级队列

在这篇博客中,我们实现二叉堆结构,二叉堆结构具有两个特点:结构性和堆序性

结构性:必须是一颗完全二叉树,树的插入从左到右

堆序性:父节点必须小于等于(小堆)或者大于等于(大堆)左右节点

在二叉堆的具体实现中,通过一个数组来储存所有的元素


这里进行除法运算时采取“向下取整”;

如果一个元素的数组下标为 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;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值