【数据结构】--- 堆

 个人主页:星纭-CSDN博客

系列文章专栏 :数据结构

踏上取经路,比抵达灵山更重要!一起努力一起进步!

目录

一.堆的介绍

二.堆的实现 

1.向下调整算法 

2.堆的创建 

 3.堆的实现

4.堆的初始化和销毁

5.堆的插入 

5.1扩容

5.2向上调整与向下调整

5.2.1向上调整

6.堆的删除 

5.2.2 向下调整

 7.取堆顶数据

8.堆的数据个数

9.判读堆是否为空 


一.堆的介绍

1.1堆的概念以及结构

简单来说,堆总是一个完全二叉树,如果每一个节点都比其子节点的值大就叫大堆,反之就叫做小堆。

二.堆的实现 

1.向下调整算法 

现在我们给出一个数组,逻辑上看做一个完全二叉树。我们通过从根节点开始的向下调整算法可以把他调整成为一个小堆。前提是:左右子树必须是一个堆,这样才能调整。

首先是27与15和19之间最小的数字交换,也就是与15交换位置。然后再与18和28之间最小的数字交换,即与18交换,再与25交换。

通过这三步,最终得到的就是一个小堆。

2.堆的创建 

下面我们给出一个数组,这个数组逻辑上可以看作一个完全二叉树,当时它还不是一个堆,现在我们通过算法,把他构建成一个堆。可是这个完全二叉树的根节点的左右两个子树也不是堆,如何进行调整呢?只需要从倒数第一个非叶子节点的子树开始调整即可,一直到根节点。 

接下来尝试把下面的数组调整成为一个大堆。 

int a[] = {1,5,3,8,7,6}

 3.堆的实现

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity; 
}Heap;
//堆的初始化
void HeapInit(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);

堆的实现,我们仍然采用多个文件的方式进行,以上是头文件中的代码,也是我们需要完成的函数。

 在上述的代码中的结构体中size表示数组的大小(存入有效数据的个数),capacity表示容量。

4.堆的初始化和销毁

初始化和销毁比较简单。 

初始化:当这个数组没有使用过的时候,其中容量和大小都未0。也没有动态开辟内存。

销毁:释放掉动态开辟的内存,将容量和大小设置为0。

void HeapInit(Heap* hp) {
	assert(hp);
	hp->_a = NULL;
	hp->_capacity = hp->_size = 0;
}

void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->_a);
	hp->_a = NULL;
	hp->_capacity = hp->_size = 0;
}

5.堆的插入 

 因为在堆的初始化的时候,是将大小和容量均初始化为0的,所以在插入数据的时候,要判断这个堆中的数组是否需要扩容,由于关于扩容的代码不会经常用到,所以这里就不单独封装函数。

5.1扩容

 当size和capacity相等的时候,就代表此时的数组已满,需要扩容。

	if (hp->_capacity == hp->_size) {
		int newcapacity = hp->_capacity == 0 ? 4 : hp->_capacity * 2;
		HPDataType* tmp = realloc(hp->_a,newcapacity * sizeof(HPDataType));
		if (tmp == NULL) {
			perror("malloc fail");
			exit(1);
		}
		hp->_a = tmp;
		hp->_capacity = newcapacity;
	}

通常情况下扩容都是扩大二倍,当时由于初始化为0了,所以需要判读是否为0,这里采用三目操作符,如果为0,就设置为4,不为0就乘2。

调整动态开辟的内存的大小需要使用realloc函数。

或许有读者疑惑,realloc函数的第一个参数需要填要调整的动态内存块的起始地址。可是在开始的时候hp->_a是NULL。这怎么办呢???

其实realloc函数的第一个参数是NULL的时候,它的功能就和malloc函数的功能一样了,就会直接开辟空间。

第二个参数是内存的大小(单位字节),所以是newcapacity * HPDataType。

最后不要忘记将tmp和newcapacity赋值给hp。

5.2向上调整与向下调整

 将数据拿出来插入到数组的最后一个位置上,可是此时并不能肯定该完全二叉树就是堆。

所以我们需要根据该数据进行调整。

	hp->_a[hp->_size++] = x;
	AdjustUp();

首先将数据放在数组的最后一个位置上。 所以需要对这个数据进行向上调整。

假设此时我们需要得到一个大堆。

5.2.1向上调整
void AdjustUp(HPDataType* a, int child);

 首先我们知道,使用数组在逻辑上构成完全二叉树时。父亲和孩子的下标是有一定规律的。

	//左孩子 = 父亲 * 2 + 1 
	//右孩子 = 父亲 * 2 + 2 

由于是向上调整,我们知道的参数只有孩子的下标,又因为整数除法,很容易我们就可以知道父亲节点的下标。

	int parent = (child - 1) / 2;//整数除法

 如果孩子更大就和父亲交换位置。可是交换位置后仍然可能存在不是大堆的情况,所以需要继续调整,直到是大堆为止。

void AdjustUp(HPDataType* a, int child) { 
	//左孩子 = 父亲 * 2 + 1 
	//右孩子 = 父亲 * 2 + 2 
	int parent = (child - 1) / 2;//整数除法
	while (child > 0) {
		if (a[child] > a[parent]) {
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else {
			break;
		}
	}
}

当child不是0的时候,就代表还可以继续比较,所以结束条件就是child等于0时。或者是parent大于等于0,因为如果parent小于了代表越界了,此时以及不能比较了。

Swap函数是用于交换的。

void Swap(HPDataType* a, HPDataType* b) {
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

6.堆的删除 

// 堆的删除
void HeapPop(Heap* hp);

堆的删除是指删除堆顶(根位置)的数据 。那么该如何删除呢?

假设一下有这样一个数组并且按照小堆的方式存放。

此时需要删除数据1。如果我们采用向前移动覆盖的方式进行删除。那么会得到一个新的堆 

不难发现此时这个堆的关系全乱了,不再是小堆,而且我们无法轻易的进行更改。所以直接向前移动覆盖这个堆顶的数据是行不通的。

那么该如何删除数据呢?

将数组最后一个数据覆盖到第一个位置上就行了。

 

虽然此时的二叉树仍然不是一个小堆,但是只有堆顶的数据关系不正确而且。此时我们就需要用到向下调整算法来使其重新变成一个小堆。 

5.2.2 向下调整

 首先将最后一个数据放在第一个位置上。

//小堆
void HeapPop(Heap* hp)
{
	assert(hp);
	assert(hp->_size > 0);
	hp->_a[0] = hp->_a[hp->_size - 1];
	hp->_size--;
	AdjustDown(hp->_a, hp->_size, 0);
}

然后进行向下调整。

 已知父亲的下标,很轻易可以知道左右两个孩子的下标。然后就是找到两个孩子的最小值与父亲比较。

为了更快速的找到最小的那个孩子的下标,我们可以采用假设法。首先假设左孩子最小。如果右孩子比左孩子小,就可以直接+1,因为右孩子的下标比左孩子刚好大1。

然后就是比较。参考一下代码。

void AdjustDown(HPDataType* a, int size, int parent) {
	int child = parent * 2 + 1;//假设左孩子最小
	while (child < size) {
		if (child + 1 < size && a[child] > a[child + 1]) {
			++child;
		}
		if (a[parent] > a[child]) {
			Swap(&a[parent],&a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else {
			break;
		}
	}
}

 7.取堆顶数据

这个很简单,就不过多讲解。

HPDataType HeapTop(Heap* hp) {
	assert(hp);
	assert(hp->_size > 0);
	return hp->_a[0];
}

只需要保证此时堆不为空即可。

8.堆的数据个数

int HeapSize(Heap* hp) {
	assert(hp);
	return hp->_size;
}

9.判读堆是否为空 

bool HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->_size == 0;
}

  • 29
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值