【数据结构与算法】粽子树?二叉树_关于堆你不知道的事情


💡本章重点

  • 堆的概念
  • 堆的结构&实现
  • 向上调整&向下调整重要算法思想

**🍞一.**堆的概念

**🥐Ⅰ.**什么是堆

  • 堆总是一颗完全二叉树满二叉树为特殊的完全二叉树】

  • 堆又称为二叉树的顺序结构

    • 因为普通的二叉树不适合用数组来存储,可能会造成大量的空间浪费
    • 而完全二叉树用顺序结构存储是完全嵌合的,不会存在空间浪费

这里是引用

综上:

  • 堆的逻辑结构为完全二叉树
  • 堆的物理结构为数组

**🥯Ⅱ.**总结

✨综上:就是堆的概念啦~

➡️简单来说:堆为二叉树的顺序结构

【后续我们还会学习二叉树的链式结构哦~】


**🍞二.**堆

**🥐Ⅰ.**性质

💡在堆中: 某个节点的值总是不大于或不小于其父节点的值

  • 大根堆:即当每个父亲结点的值总是孩子结点的值

在这里插入图片描述

  • 小根堆:即当每个父亲结点的值总是孩子结点的值

在这里插入图片描述

特别注意:

  • 小根堆的堆顶数据【即最上面的结点的值】:一定是整个完全二叉树中值最小的结点
  • 大根堆的堆顶数据【即最上面的结点的值】:一定是整个完全二叉树中值最大的结点
  • 若不满足上述条件,则表明不属于大根堆小根堆中的任意一个,也就是说此完全二叉树不是

【虽然根结点的值一定是全部结点的值中最大最小的,但大根堆小根堆并不代表说数组元素是按照降序升序排放的,这两者没有任何关系】

➡️重要规律:

通过数组的特性随机访问,如何用下标去找到父亲结点or孩子结点

  • 假设某一个父亲结点下标为parent
  • 所以此父亲结点的左孩子结点为:leftchild = parent*2 + 1
  • 所以此父亲结点的右孩子结点为:leftchild = parent*2 + 2
  • 所以:我们可以通过+1+2的下标调整找到左孩子右孩子
  • 那此时我们就可以反推出:parent = (child - 1)/ 2
  • 这是因为计算的是整型计算,即使是右孩子去用此算式虽然计算出来的下标是带有小数的,但下标的类型为整型,就会自动抹去小数,那此时的整数也就为右孩子的父亲结点的下标了
  • 所以:我们就可以用一条算式去求得左、右孩子的父亲结点的下标了,无需用两条算式

综上:

  • 我们便用顺序结构去实现

❗所以下面我们开始实现堆的接口


**🍞三.**堆接口实现

对于数据结构的接口实现,一般围绕的内容

💡如下的实现围绕此原码进行: 以下以建大根堆为例

typedef int HPDataYtpe;

typedef struct Heap
{
	HPDataYtpe \*a;
	int size; //记录目前数组内有几个数据
	int capacity;
	
}HP;

**🥐Ⅰ.**初始化堆(建堆)

👉简单来说:

  • 拿一个数组的全部元素进行建堆,即对此数组的逻辑结构变为大堆

➡️实现:

  • 1️⃣先将原先数组的内容全部拷贝至新创建的堆中
  • 2️⃣将此堆里的元素排序进行调整,建为 大堆

🔥重点: 如何调整为大堆 - 也就是建堆

1.首先:我们假设要建堆的数组的元素除了根节点不满足大堆,左右子树都满足大堆的情况
在这里插入图片描述


2.思路:此时我们就需要将值为2的结点进行向下比较,最终放到适合的位置,从而使整个堆满足大堆的要求

👉向下调整算法: 当左右子树都为大堆小堆的时候,便可通过此算法调整根结点的位置,使整棵树满足大堆小堆【我们这里本质操控的是数组

  • 1️⃣先将值为2的结点(父亲结点)的左、右孩子比较值的大小,因为是要建大堆,所以选出左右孩子中的较大值
  • 2️⃣比较父亲结点与左右孩子中的较大值哪个值更大,若孩子节点的值大于父亲节点,则交换调整,并将原来孩子的位置当成父亲(因为前面已经交换位置了),继续重复调整下去,直至父亲结点走到叶子结点,说明父亲结点已经走到树的最后一层,完成调整了
  • 3️⃣若当孩子结点的值小于父亲结点,说明此时父亲结点处在的位置再往下已经满足大堆的要求,就可以停止调整了
    在这里插入图片描述

特别注意:

  • 比较孩子结点的时候,有可能只有左孩子结点,没有右孩子结点的时候(就如上述情况),不可访问右孩子结点,即需要防止数组越界【child + 1 < n

✊综上:向下调整算法的代码实现【时间复杂度:

O

(

l

o

g

N

)

O(logN)

O(logN)】

void ADjustDown(int \* a, int n, int parent)
{
	int child = parent \* 2 + 1;

	while (child < n) 
	//当 孩子的下标 超出 数组的范围,则说明不存在
	{
		//1.选出左右孩子中,较小的一个
		//child -- 左孩子下标;child+1 -- 右孩子下标
		if (child + 1 < n && a[child + 1] > a[child])
		{
			//想象的时候:默认左孩子是比右孩子小
			//如果大的话,child就走到右孩子下标处
			child++;
		}

		//2.交换
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent \* 2 + 1;
		}
		else
		{
			//满足的情况
			break;
		}
	}
}

❓当左右子树都不是大堆时候,怎么办

  • 上述仅用于左右子树都满足 大堆小堆的情况,我们现在对于一个结点数值都是随机摆放的完全二叉树,是不能直接运用向下调整算法进行建堆的
  • 那此时我们便可以创造条件:从后往前建堆

➡️思路:

  • 1️⃣找到完全二叉树中最后一个结点的父亲结点
  • 2️⃣判断此父亲结点与其孩子结点构成的局部二叉树是否是我们想要的大堆,若不满足,则向下调整使这个局部的二叉树满足
  • 3️⃣调整完后,父亲结点对应的下标--【即往前找上一个结点】,再重复步骤2️⃣,直至调整为我们上面举的例子,最终调整最后一次后,这个完全二叉树便为大堆
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    👉这样,便正真建成了一个大堆

✊综上:建堆的代码实现【时间复杂度:

O

(

N

)

O(N)

O(N)】

for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
	AdjustDown(php->a, php->size, i);
}

特别注意:

  • a表示需要建堆的数组的首元素地址
  • n为数组的a的元素个数

1️⃣建堆的函数声明:

void HeapInit(HP\* php,HPDataYtpe\* a,int n);

2️⃣建堆函数的实现:

void HeapInit(HP\* php, HPDataYtpe\* a, int n)
{
	assert(php);

	php->a = (HPDataYtpe\*)malloc(sizeof(HPDataYtpe)\*n);
	if (php->a == NULL)
	{
		printf("malloc fail\n");

		exit(-1);
	}
	
	//1.拷贝
	memcpy(php->a, a, sizeof(HPDataYtpe)\*n);
	php->size = n;
	php->capacity = n;

	//2.建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, php->size, i);
	}
}

**🥐Ⅱ.**入堆操作

👉简单来说: 对堆插入一个数据

➡️实现: 即对数组尾插一个数据

特别注意:

  • 并不是插入堆就直接满足大堆,需要将刚插进来的数据经过比较放到能使整棵树满足大堆的位置,此时就涉及另外一个算法:向上调整算法

    • 对插入的数据进行向上调整,仅会对插入数据所在的路径产生影响,并不像向下调整算法会影响到整棵树

    • 因为我们插入进来的时候,完全二叉树已经满足大堆【即父亲结点的值孩子结点的值】,所以向上调整算法将插入的结点直接与父亲结点的值进行比较即可(无需与自己的兄弟结点比较)

      • 大于父亲结点的值,则不满足大堆,需要将插入进来的结点与其父亲结点交换,并继续向上与新的父亲结点进行比较
      • 直至插入的结点已经到达根部【即数组下标为0处】 ,就结束调整,表示已到达合适的位置
      • 小于父亲结点的值,则表明此时也已满足大堆,插入进来的结点已经到达合适的位置了

✊综上:向上调整代码实现

void Adjustup(int\*a, int child)
{
	int parent = (child - 1) / 2;
	while (child != 0)  //等于0的时候就中止 
	{
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

👉Eg: 假如现在对堆插入一个值为10的结点

动图示例:

在这里插入图片描述

1️⃣入堆的函数声明:

void HeapPush(HP\* php, HPDataYtpe\* x);

2️⃣入堆函数的实现:

void HeapPush(HP\* php, HPDataYtpe\* x)
{
	assert(php);
	//满了
	if (php->size == php->capacity)
	{
		//增容
		HPDataYtpe\* tmp = realloc(php->a, php->capacity \* 2 \* sizeof(HPDataYtpe));
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		php->a = tmp;
		php->capacity \*= 2;
	}

	php->a[php->size] = x;
	php->size++;

	//向上调整算法
	Adjustup(php->a, php->size);
}

**🥐Ⅲ.**删除堆顶数据

👉简单来说: 对堆顶删除一个元素

➡️实现: 即删除数组的第一个元素

特别注意:

  • 如果直接删除堆顶的数据的,那就需要后面的整体数据往前挪动【

O

(

N

)

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

);
}


### **🥐Ⅲ.**删除堆顶数据


👉**简单来说:** 对堆顶删除一个元素


➡️**实现:** 即删除数组的第一个元素


❗**特别注意:**


* 如果直接删除堆顶的数据的,那就需要后面的整体数据往前挪动【 
 
 
 
 
 O 
 
 
 ( 
 
 
 N 
 
 
 ) 
 
 


[外链图片转存中...(img-ELM1BQw6-1714176100063)]
[外链图片转存中...(img-XydZB9oi-1714176100063)]
[外链图片转存中...(img-TpPAjixZ-1714176100064)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值