数据结构——堆(Heap)功能的实现

一、堆的基本概念

什么是堆?

  简单来说,堆就是一个完全二叉树,在这个完全二叉树中,每一个子树的根节点总是大于它的左右孩子,那就称为大堆,反过来,每一个子树的根节点总是小于它的左右孩子,那就称为小堆。

堆的性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

  现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

  小堆的顺序存储结构与完全二叉树逻辑结构:

  大堆的顺序存储结构与完全二叉树逻辑结构:

二、堆的功能实现

一、创建工程

创建好工程后,新建头文件Heap.h,源文件Heap.c、main.c

头文件Heap.h包含我们代码所需的头文件,还有结构体、函数的声明。

源文件Heap.c包含我们堆功能函数的实现。

源文件main.c包含我们运用堆的源代码。

二、功能函数实现

上面已经所说,堆是顺序结构存储,所以这里结构体的定义和顺序表相同

下面呈上头文件中结构体的定义与函数的声明

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;
//初始化函数
void InitHeap(HP* php);
//销毁函数
void DestroyHeap(HP* php);
//建堆函数
void PushHeap(HP* php,HPDataType x);
//删除根节点函数
void HeapPop(HP* php);
//返回堆顶数值函数
HPDataType HeapTop(HP* php);
//堆大小
size_t HeapSize(HP* php);
//判断堆是否为空
bool HeapEmpty(HP* php);

1、初始化函数

void InitHeap(HP* php)
{
	assert(php);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}

2、销毁函数

void DestroyHeap(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}

3、建堆

void PushHeap(HP* php,HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (realloc == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		php->capacity = newcapacity;
		php->a = tmp;
	}
	php->a[php->size] = x;

	php->size++;
	AdjustUp(php->a, php->size - 1);
}

  建堆的时候,我们将一个数组中的数一一入堆,那么就需要一个一个的申请空间,整体架构与顺序表相同,但是这里多了一个AdjustUp的函数,是因为我们堆只有大堆和小堆,源头数组里面的数不一定符合大堆或小堆的性质,所以我们需要对其进行调整。AdjustUp函数就是一个调整函数,通过名字我们也能知道,是向上调整。

  

例如:我们创建一个小堆,最后末尾插入10时,10作为孩子结点,我们将其与它的父结点作比较,如果10小于它的父结点,我们将他们两个交换位置即可,然后再将10与它新的父结点比较大小,依次比较进行,直到10大于父结点,停止交换。

向上调整函数:

void AdjustUp(HPDataType* a, int child)
{
	assert(a);
//由孩子的位置求出父结点
	int parent = (child - 1) / 2;
	while (child>0)//因为是向上调整,所以孩子在向上调整的过程中会逐渐往源头数组下标为0的地方靠近
	{
		if (a[child] < a[parent])//父亲与孩子作比较
		{
			Swap_HP(&a[child],&a[parent]);//交换函数
			child = parent;//交换后,孩子替代父亲位置
			parent = (parent - 1) / 2;//寻找新父结点的位置
		}
		else
		{
			break;//如果孩子大于父亲则跳出循环
		}
	}
}

交换函数:

void Swap_HP(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

4、删除函数

  删除函数,我们单纯删除堆最后一个叶子结点其实用处不大,在这里,我们来实现如何删除根结点。我们直接删除根节点,剩下的组成新堆的话,那他们可能就已经乱了,就不会是堆了。

  所以我们用一种比较有新意的方法来实现:

  删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。我们还是拿小堆为例。

  这里的向下调整算法与向上调整算法大致相同,向下调整算法是从上到下,如果父亲比较大的孩子大,则将父亲与较大的孩子交换位置,之后再与新的左右孩子中较大的一个进行比较,这样依次比较,直到它比孩子小为止。

void AdjustDown(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;//假设左孩子大
	while (child < size)
	{
		if (child+1<size && a[child + 1] < a[child])//假设法 我们上们先假设左孩子大,这里再作比 
                                                    //较,如果右孩子大,那么child++就好了
		{
			child++;
		}
		if (a[child] < a[parent])//比较
		{
			Swap_HP(&a[child], &a[parent]);//交换位置
			parent = child;
			child = 2 * child + 1;
		}
		else
		{
			break;
		}
	}
	
}

5、返回堆顶元素

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

6、堆大小

size_t HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

7、判断堆是否为空

bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;	
}

三、堆功能的运用

#include"Heap.h"
int main()
{
	HP php;
	InitHeap(&php);//初始化
	int a[] = { 4,7,2,3,9,5,1 };
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		PushHeap(&php, a[i]);//建堆
	}
	
	while (!HeapEmpty(&php))
	{
		printf("%d ", HeapTop(&php));//打印堆顶元素
		HeapPop(&php);//删除堆顶元素,并找出第二小堆顶元素
	}
    DestroyHeap(HP*php);
	return 0;
}

打印堆顶元素后,HeapPop函数,每次删除完堆顶的元素之后,向下调整可以帮我们找到第二小的元素作为堆顶元素,所以我们根据这样来排序。

  • 25
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值