【数据结构】树-二叉树-堆(下)
🍃 如果觉得本系列文章内容还不错,欢迎订阅🚩
🎊个人主页:小编的个人主页
🎀 🎉欢迎大家点赞👍收藏⭐文章
✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍
目录
- 🐼前言
- 🐼堆
- 🐼初始化堆
- 🐼堆的销毁
- 🐼堆的插入
- 🐼向上调整算法
- 🐼堆的判空
- 🐼 堆的删除
- 🐼向下调整算法
- 🐼取堆顶的数据
- 🐼堆的数据个数
- 🐼全部源码
- 🐼文末
🐼前言
🌟在上一节我们介绍了二叉树的相关概念,如果感兴趣的小伙伴,可以阅读我的上一篇文章:> 树-二叉树-堆,这一节我们介绍一种特殊的二叉树:堆
🐼堆
✨ 堆的概念
一般使用顺序结构的二叉树称之为堆,堆是一种特殊的二叉树,不仅具有二叉树的特性,还具有其他的特性。
😃如果有⼀个关键码的集合 K = {k0 , k1 , k2 , …,kn−1 } ,把它的所有元素按完全⼆叉树的顺序存储方式存储,在⼀个⼀维数组中,并满足: Ki <= K2∗i+1 ( Ki >=K2∗i+1 且 Ki <= K2∗i+2 ),i = 0、1、2… ,则称为堆(或大堆)。将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。
🐋小根堆
🐋大根堆
🐾现在我们来讨论二叉树的性质:
对于具有 n 个结点的完全二叉树,如果按照从上到下从左到右的数组顺序对所有结点从0 开始编号,则对于序号为 i 的结点有:
如果i>0,那么双亲节点序号: (i-1)/2 ;
i=0,为根节点,无双亲节点。
如果2i+1<n;左孩子节点: 2i+1
如果2i+2<n右孩子节点:2i+2
✨我们既然知道堆是基于数组来实现的。那和我们之前实现的顺序表,栈一样:现在我们来定义堆的结构:
typedef int HPDataType;
typedef struct Heap
{
HPDataType* arr;
int size;
int capacity;
}Hp;
以下是堆常见方法的实现以及解析:
🐼初始化堆
//初始化堆
void HeapInit(Hp* php)
{
assert(php);
php->arr = NULL;
php->size = php->capacity = 0;
}
🌻代码解析
✨ 我们将还未开辟的数组指针置为空,并将堆中有效元素个数和容量大小置为0。
时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量
🐼堆的销毁
// 堆的销毁
void HeapDestory(Hp* php)
{
assert(php);
if (php->arr)
free(php->arr);
php->arr = NULL;
php->size = php->capacity = 0;
}
🌻代码解析
✨ 如果指向堆数组的空间存在,释放指向数组的那块空间,并将其置为NULL,防止野指针的发生。最后重置size和capacity
时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量
🐼堆的插入
/ 堆的插入
void HeapPush(Hp* php, HPDataType x)
{
assert(php);
//检查是否需要增容
if (php->size == php->capacity)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->arr, newcapacity * sizeof(HPDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
php->arr = tmp;
php->capacity = newcapacity;
}
php->arr[php->size] = x;
//尾部插入.向上调整
AdjustUp(php->arr, php->size);
++php->size;
}
🌻代码解析
✨ 向堆内插入元素默认是在尾部插入元素。首先要检查堆是否为空,如果为空,那么就要通过realloc进行增容操作,并更新capacity。将要插入的元素插入。因为是堆结构,每插入一个元素就需要对堆进行重新排序。所以这里封装了一个AdjustUp函数,这个函数用于调整堆值的关系,目的是,如果插入一个元素,是大根堆,结果还是大根堆,是小根堆,结果还是小根堆。
🍀测试结果:
下面来探讨向上调整算法的实现。
🐼向上调整算法
//向上调整
void AdjustUp(HPDataType* arr, int child)
{
int parent = (child - 1) / 2;
//建小堆
while (child > 0)
{
//<小堆
//>大堆
if (arr[child] < arr[parent] )
{
Swap(&arr[child], &arr[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
🌻代码解析
✨ 我们调整参数的是堆数组,所以用arr接收,以及插入的孩子节点。
在上面二叉树的性质我们得知,已知孩子节点,则父节点是:parent = (child-1)/2,这里我们如果建小堆,即孩子节点要大于双亲节点的,不满足,则进行交换,并接着让child走到parent,parent再走到parent的父节点。如果已经满足,则跳出循坏。
🍂画图剖析:
如上图,每次在堆尾节点插入元素,需要向上调整,重新建堆
🚗这里来详细说明向上调整算法的时间复杂度:
因为堆是完全二叉树,这里近似看做满二叉树,对时间复杂度没有影响。
分析:
第1层, 2^0 个结点,需要向上移动0层.
第2层, 2^1 个结点,需要向上移动1层.
第3层, 2^2 个结点,需要向上移动2层.
第4层, 2^3 个结点,需要向上移动3层.
…
第h层, 2^(h-1) 个结点,需要向上移动h-1层.
则需要移动结点总的移动步数为:每层结点个数 * 向上调整次数(第⼀层调整次数为0)
🐼堆的判空
// 堆的判空
bool HeapEmpty(Hp* php)
{
assert(php);
return php->size == 0;
}
🌻代码解析
✨ 通过堆中有效元素的个数直接返回布尔值。
时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量
🐼 堆的删除
void HeapPop(Hp* php)
{
assert(!HeapEmpty(php));
//交换第一个和最后一个元素
Swap(&php->arr[0], &php->arr[php->size - 1]);
php->size--;
//向下调整
AdjustDown(php->arr, 0,php->size);
}
🌻代码解析
✨ 删除堆中的数据,指的是删除堆头的数据。删除堆头,不能直接将堆头删除,这样就破坏了堆结构,而是通过将要删除的第一个元素和最后一个元素互相交换位置。不过这样又会打乱堆,此时我们有需要再次调整堆,将堆调整成有序堆。
下面我们介绍令一种调整算法:向下调整算法。
🐼向下调整算法
//向下调整
void AdjustDown(HPDataType* arr, int parent, int n)
{
HPDataType child = parent * 2 + 1;
while (child < n)
{
//找最小的孩子--小堆
//找最大的孩子--大堆
if (arr[child] < arr[child + 1] && child+1<n)
{
child++;
}
//<小堆
//>大堆
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
🌻代码解析
✨ 首先,向下调整的还是堆数组,传过来的初始都是根节点,堆中有效元素个数是n。
📝向下调整算法:
将堆顶元素与堆中最后一个元素进行交换
• 删除堆中最后⼀个元素
• 将堆顶元素向下调整到满足堆特性为止。
🍏传过来的是父节点,所以先找到孩子节点child。我们上面已知双亲节点,求孩子节点:child =parent * 2 + 1; 。这里如果建小堆,要先找到孩子节点中较小的孩子(child),再与双亲节点(parent)交换,注意,右孩子(child)也要小于节点个数,接着,如果较小的孩子(child)小于双亲节点(parent),交换较小的孩子(child)和双亲节点(parent)保存的值,再让双亲节点(parent)走到孩子节点(child),再更新孩子节点,否则,直接,跳出循环。child最多不能超过有效节点个数n作为循环条件.
🍂画图剖析:
🚗这里来详细说明向下调整算法的时间复杂度
分析:
第1层, 2^0 个结点,需要向下移动h-1层.
第2层, 2^1 个结点,需要向下移动h-2层.
第3层, 2^2 个结点,需要向下移动h-3层.
第4层, 2^3 个结点,需要向下移动h-4层.
…
第h-1层, 2^(h-2) 个结点,需要向下移动1层.
则需要移动结点总的移动步数为:每层结点个数 * 向上调整次数(第⼀层调整次数为0)
总结,堆的向上调整算法和向下调整算法都既可以建大堆也可以建小堆。向上调整算法主要是堆的插入。而向下调整算法用于建堆和堆排序。这里能看出,向下调整算法时间复杂度比向上调整算法更优
测试结果同下面取堆顶的数据一起展示。
🐼取堆顶的数据
// 取堆顶的数据
HPDataType HeapTop(Hp* php)
{
assert(!HeapEmpty(php));
return php->arr[0];
}
🌻代码解析
✨ 堆不为空才可以取元素。将堆顶元素返回。
时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量
🍀测试结果:
🐼堆的数据个数
int HeapSize(Hp* php)
{
assert(php);
return php->size;
}
🌻代码解析
✨直接返回堆中有效元素个数
时间复杂度为O(1),程序执行常数次,空间复杂度为O(1),只创建有效个变量
🐼全部源码
Heap.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* arr;
int size;
int capacity;
}Hp;
//初始化堆
void HeapInit(Hp* php);
// 堆的销毁
void HeapDestory(Hp* php);
// 堆的插入//尾部插入
void HeapPush(Hp* php, HPDataType x);
// 堆的删除//头部删除
void HeapPop(Hp* php);
// 取堆顶的数据
HPDataType HeapTop(Hp* php);
// 堆的数据个数
int HeapSize(Hp* php);
// 堆的判空
bool HeapEmpty(Hp* php);
//交换
void Swap(HPDataType* x, HPDataType* y);
//向下调整
void AdjustDown(HPDataType* arr, int parent, int n);
//向上调整
void AdjustUp(HPDataType* arr, int child);
Heap.c
#include "Heap.h"
//初始化堆
void HeapInit(Hp* php)
{
assert(php);
php->arr = NULL;
php->size = php->capacity = 0;
}
// 堆的销毁
void HeapDestory(Hp* php)
{
assert(php);
if (php->arr)
free(php->arr);
php->arr = NULL;
php->size = php->capacity = 0;
}
void Swap(HPDataType* x, HPDataType* y)
{
HPDataType tmp = *x;
*x = *y;
*y = tmp;
}
//向上调整
void AdjustUp(HPDataType* arr, int child)
{
int parent = (child - 1) / 2;
//建小堆
while (child > 0)
{
//<小堆
//>大堆
if (arr[child] < arr[parent] )
{
Swap(&arr[child], &arr[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//向下调整
void AdjustDown(HPDataType* arr, int parent, int n)
{
HPDataType child = parent * 2 + 1;
while (child < n)
{
//找最小的孩子--小堆
//找最大的孩子--大堆
if (arr[child] < arr[child + 1] && child+1<n)
{
child++;
}
//<小堆
//>大堆
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
// 堆的插入
void HeapPush(Hp* php, HPDataType x)
{
assert(php);
//检查是否需要增容
if (php->size == php->capacity)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->arr, newcapacity * sizeof(HPDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
php->arr = tmp;
php->capacity = newcapacity;
}
php->arr[php->size] = x;
//尾部插入.向上调整
AdjustUp(php->arr, php->size);
++php->size;
}
// 堆的判空
bool HeapEmpty(Hp* php)
{
assert(php);
return php->size == 0;
}
// 堆的删除
void HeapPop(Hp* php)
{
assert(!HeapEmpty(php));
//交换第一个和最后一个元素
Swap(&php->arr[0], &php->arr[php->size - 1]);
php->size--;
//向下调整
AdjustDown(php->arr, 0,php->size);
}
// 取堆顶的数据
HPDataType HeapTop(Hp* php)
{
assert(!HeapEmpty(php));
return php->arr[0];
}
// 堆的数据个数
int HeapSize(Hp* php)
{
assert(php);
return php->size;
}
test.c
void test()
{
Hp hp;
HeapInit(&hp);
//堆的插入
HeapPush(&hp, 8);
HeapPush(&hp, 14);
HeapPush(&hp, 6);
HeapPush(&hp, 17);
//堆的删除
/*HeapPop(&hp);
HeapPop(&hp);
HeapPop(&hp);
HeapPop(&hp);*/
while (!HeapEmpty(&hp))
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
HeapDestory(&hp);
}
🐼文末
感谢你看到这里,如果觉得本篇文章对你有帮助,点个赞👍 吧,你的点赞就是我更新的最大动力 ⛅️🌈 ☀️