基础知识
- 堆
堆是用数组实现的二叉树,它的逻辑结构是二叉树,存储结构是数组,所以它没有左右孩子指针。
堆(优先队列)的结构体定义为:
#define Type int
typedef struct HeapStruct{
int capacity;
int size;
Type *eles;
}PriorityQueue;

capacity是堆的最大容量,创建时进行初始化;size是已使用的容量;eles是指针,用于对数据进行操作.
优先队列
优先队列是利用堆的性质实现的,分两种情况,大顶堆和小顶堆。以大顶堆为例,其性质是:- 节点的左右孩子的值,都小于该节点的值
- 左右孩子也都是大顶堆.
根据优先队列的性质可以看到,大顶堆的根节点即为序列的最大值,相应的小顶堆的根节点就是序列的最小值。那么我们将一组数输入到大顶堆中,然后依次输出根节点,便实现了堆排序,这样的堆就是常用的优先队列(PriorityQueue)。
这个过程中,我们主要做的是,每插入一个元素,都要将堆调整为优先队列,同样,每删除一个根节点,也要将堆重新调整。
构建优先队列
优先队列的操作包括:
- 创建/初始化
- 判空/判满
- 插入
- 查找最值
- 删除
- 摧毁
创建
创建一个PriorityQueue结构体,并分配相应的内存,为指针eles分配capacity+1的内存。因为用数组来存储二叉树,父节点和子节点存储位置的关系是son = father/2,在插入操作中,从叶节点创建一个空穴,不断和父节点比较并上移,所以从子节点到父节点经常要取半i/2,为了操作方便,我们把数组的0号位置空出来不使用,将根节点放在i=1的位置上,因此在初始化时,分配capacity+1的内存。
PriorityQueue *init(int max_size){ //这里初始化init定义为指针函数,其返回值是指针,可以直接赋值给*pq
PriorityQueue *pq = NULL; //pq为结构体指针
//检查输入大小的合法性
if(max_size <= 0){
printf("max_size is illeagel!\n");
return NULL;
}
pq = malloc(sizeof(PriorityQueue));
if(pq == NULL){
printf("malloc failed!\n");
return NULL;
}
//下标为0的位置保留,不使用
pq->eles = malloc((max_size+1)*sizeof(Type));//为数组eles分配max_size + 1的内存
if(pq->eles == NULL){
printf("eles malloc failed!\n");
free(pq);
return NULL;
}
//初始化队列成员
memset(pq->eles, 0, (max_size+1)*sizeof(Type));
pq->capacity = max_size;
pq->size = 0;
return pq;
}
在使用时只需要
PriorityQueue *pq = init(max_size);
判空
//判断是否为空
int is_empty(PriorityQueue *pq){
if(pq == NULL) //如果指针为空
return 0;
if(pq->size == 0)
return 1;
else
return 0;
}
判满
//判断是否为满
int is_full(PriorityQueue *pq){
if(pq == NULL)
return 0;
if(pq->size == pq->capacity)
return 1;
else
return 0;
}
插入
我们在 i = size +1 的位置创建一个空穴,要插入的值value与父节点进行比较,如果大于父节点,刚将父节点与空穴交换,此时空穴的位置到了 i/2 ,然后重复上述步骤,直到 i <= 1 或value小于父节点为止,然后将value 放入到空穴里,即完成了插入操作。
以序列3, 4,7,5,8,2 为例,令capacity = 9。
- 插入3
- 插入4
空穴与其父节点 j/2 进行比较,即 4 > 3,所以父节点与3交换,j = j/2,因为 j = 1,所以操作终止,将4放入空穴 j = 1;size = size + 1; - 插入7
以此类推。最终的结果为
序列在数组中的存储并不是有序的,直接遍历不能得到有序序列,因为它在结构上是树形的,类似于二叉树的顺序存储结构。
插入操作的完整代码如下:
int push(Type value, PriorityQueue *pq){
int i=0;
//首先判断队列是否为满
if(is_full(pq)){
printf("Queue is Full!\n");
return 0;
}
printf("push %d\n", value);
//将最后位置+1,作为待插入的空穴,不断将空穴与父节点比较,如果比父节点大,则空穴上移
for(i = pq->size+1; value > pq->eles[i/2] && i>1; i/=2){
pq->eles[i] = pq->eles[i/2];
}
pq->eles[i] = value; //将value放到最后的空穴中
pq->size ++;
return 1;
}
查找最值
最值即为根节点 eles[1],将其值赋值给value,并返回1,否则返回0;
//查找最值
int find_max(PriorityQueue *pq, Type *value){
if(is_empty(pq)){
printf("Queue is empty\n");
return 0;
}
*value = pq->eles[1];
return 1;
}
删除
优先队列的删除从根节点,即eles[1]开始,每完成一次删除,将队列重新调整为优先队列。
首先将根节点赋值给max,并输出(用来实现排序);
然后将最后一个元素取出来,赋值给last,令size - 1;如果size = 0, 则结束;
否则将根节点作为空穴,并下移。即找到空穴左右孩子中较大的值,和空穴作交换,直到空穴没有左右孩子,最后将last 放入到最后的空穴位置。
以删除8为例:将 8 赋值给max; 将最后一个值 2 取出来赋值给 last, size = size - 1;
移动7之后,空穴到了 i = 2的位置,其左右孩子分别在 4, 5 位置,仍将较大的孩子移动到空穴,同时空穴下移:
最后将 2 放入空穴:
完整代码如下:
//删除
int pop(PriorityQueue *pq, Type *max){
int i=1;
int maxchild = 0;
//判断是否为空
if(is_empty(pq)){
printf("Queue is empty!\n");
return 0;
}
//先把最后一个元素取出来
Type last;
last = pq->eles[pq->size];
//取得最大值
*max = pq->eles[1];
printf("Pop %d\n", pq->eles[1]);
pq->size --;
if(pq->size == 0){ //如果只有一个元素
pq->eles[i] = 0; //删除之后变为0
return 1;
}
//将第一个元素作为空穴,并不断下移
for(i=1; i * 2 < pq->size; i = maxchild){
maxchild = i * 2;
//选择两个孩子中的最大值
if(pq->eles[maxchild] < pq->eles[maxchild+1] && maxchild != pq->size)
maxchild += 1;
//如果last比空穴处的元素小,则空穴还要往下
if(last < pq->eles[maxchild])
pq->eles[i] = pq->eles[maxchild];
else
break;
}
pq->eles[i] = last;
return 1;
}
摧毁
int destroy(PriorityQueue *pq) {
if(pq == NULL){
return 0;
}
free(pq->eles);
pq->eles = NULL;
free(pq);
pq = NULL;
printf("destroy success\n");
return 1;
}
- 测试
int main(void){
//创建容量为6的优先队列
PriorityQueue *pq = init(6); //init在定义时,定义为指针,因此其返回值是个指针,直接复制给pq
int arr[]={3,4,7,5,8,2,9,10} ;
int i = 0;
//试图插入多于6个的元素,最后两个元素将无法插入
for(i = 0; i<8; i++){
push(arr[i], pq);
}
//遍历队列内容
printf("the value of pq are: ") ;
for(i=0; i<pq->size; i++){
printf("%d ", pq->eles[i+1]);
}
printf("\n");
printf("pq size is %d\n", pq->size);
Type max;
int size = pq->size;
//每次从堆顶取元素
for(i=0; i<size; i++){
if(find_max(pq, &max)){
printf("the max is %d\n", max);
pop(pq, &max);
}
}
printf("new size is %d\n", pq->size) ;
//销毁队列
destroy(pq);
return 0;
}
- 输出
完整可运行代码:
//大顶堆
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define Type int
/*定义优先队列的数据结构*/
typedef struct HeapStruct{
int capacity; //最大容量
int size;
Type *eles;
}PriorityQueue;
//初始化优先队列 ,创建空堆,初始化元素为0
PriorityQueue *init(int max_size){ //这里初始化init定义为指针函数,其返回值是指针,可以直接赋值给*pq
PriorityQueue *pq = NULL;
//检查输入大小的合法性
if(max_size <= 0){
printf("max_size is illeagel!\n");
return NULL;
}
pq = malloc(sizeof(PriorityQueue));
if(pq == NULL){
printf("malloc failed!\n");
return NULL;
}
//下标为0的位置保留,不使用
pq->eles = malloc((max_size+1)*sizeof(Type));
if(pq->eles == NULL){
printf("eles malloc failed!\n");
free(pq);
return NULL;
}
//初始化队列成员
memset(pq->eles, 0, (max_size+1)*sizeof(Type));
pq->capacity = max_size;
pq->size = 0;
return pq;
}
//判断是否为空
int is_empty(PriorityQueue *pq){
if(pq == NULL)
return 0;
if(pq->size == 0)
return 1;
else
return 0;
}
//判断是否为满
int is_full(PriorityQueue *pq){
if(pq == NULL)
return 0;
if(pq->size == pq->capacity)
return 1;
else
return 0;
}
//插入
int push(Type value, PriorityQueue *pq){
int i=0;
//首先判断队列是否为满
if(is_full(pq)){
printf("Queue is Full!\n");
return 0;
}
printf("push %d\n", value);
//将最后位置+1,作为待插入的空穴,不断将空穴与父节点比较,如果比父节点大,则空穴上移
for(i = pq->size+1; value > pq->eles[i/2] && i>1; i/=2){
pq->eles[i] = pq->eles[i/2];
}
pq->eles[i] = value; //将value放到最后的空穴中
pq->size ++;
return 1;
}
//查找最值
int find_max(PriorityQueue *pq, Type *value){
if(is_empty(pq)){
printf("Queue is empty\n");
return 0;
}
*value = pq->eles[1];
return 1;
}
//删除
int pop(PriorityQueue *pq, Type *max){
int i=1;
int maxchild = 0;
//判断是否为空
if(is_empty(pq)){
printf("Queue is empty!\n");
return 0;
}
//先把最后一个元素取出来
Type last;
last = pq->eles[pq->size];
//取得最大值
*max = pq->eles[1];
printf("Pop %d\n", pq->eles[1]);
pq->size --;
if(pq->size == 0){ //如果只有一个元素
pq->eles[i] = 0; //删除之后变为0
return 1;
}
//将第一个元素作为空穴,并不断下移
for(i=1; i * 2 < pq->size; i = maxchild){
maxchild = i * 2;
//选择两个孩子中的最大值
if(pq->eles[maxchild] < pq->eles[maxchild+1] && maxchild != pq->size)
maxchild += 1;
//如果last比空穴处的元素小,则空穴还要往下
if(last < pq->eles[maxchild])
pq->eles[i] = pq->eles[maxchild];
else
break;
}
pq->eles[i] = last;
return 1;
}
//摧毁
int destroy(PriorityQueue *pq) {
if(pq == NULL){
return 0;
}
free(pq->eles);
pq->eles = NULL;
free(pq);
pq = NULL;
printf("destroy success\n");
return 1;
}
//测试
int main(void){
//创建容量为6的优先队列
PriorityQueue *pq = init(6); //init在定义时,定义为指针,因此其返回值是个指针,直接复制给pq
int arr[]={3,4,7,5,8,2,9,10} ;
int i = 0;
//试图插入多于6个的元素,最后两个元素将无法插入
for(i = 0; i<8; i++){
push(arr[i], pq);
}
//遍历队列内容
printf("the value of pq are: ") ;
for(i=0; i<pq->size; i++){
printf("%d ", pq->eles[i+1]);
}
printf("\n");
printf("pq size is %d\n", pq->size);
Type max;
int size = pq->size;
//每次从堆顶取元素
for(i=0; i<size; i++){
if(find_max(pq, &max)){
printf("the max is %d\n", max);
pop(pq, &max);
}
}
printf("new size is %d\n", pq->size) ;
//销毁队列
destroy(pq);
return 0;
}
- 欢迎讨论