您的位置:首页 > 游戏 > 游戏 > 【数据结构】--- 堆

【数据结构】--- 堆

2024/9/24 21:20:39 来源:https://blog.csdn.net/CNDS_lili/article/details/140289568  浏览:    关键词:【数据结构】--- 堆

 个人主页:星纭-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;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com