轻松掌握二叉树和堆(保姆级详解,小白必看系列)

news2024/11/27 9:35:36

目录

一、前言

二、二叉树的概念和结构

🍎二叉树的概念

🍐特殊的二叉树(重点)

🍉二叉树的性质 (超重点------面试做题会用)

🍓二叉树的概念选择题

🍌二叉树的存储结构

三、二叉树顺序结构存储及实现

🍊二叉树的顺序结构

🍇堆的概念及结构

🍋堆的概念选择题

🥝堆的实现

⭐ 堆的准备工作

💦创建堆的结构

 💦堆的初始化

 💦堆的打印

 💦堆的销毁

 ⭐ 堆的调整

💦堆的数值交换

💦堆的向上调整算法 (应用于堆的数据插入)

 💦堆的向下调整算法(应用于堆的数据删除)

 ⭐ 堆的核心功能

💦堆的数据插入

💦堆的数据删除

 💦堆的判空

 💦取堆顶数据

⭐堆的总代码

💦Heap.h 文件

 💦Heap.c 文件

 💦Test.c 文件

 💦代码运行的菜单界面

 四、共勉


一、前言

        在之前的几篇文章中已经详细介绍了什么是数据结构,什么是非线性结构,什么是。那么这篇博客,将会继续为大家讲解有关树的其他结构 ------------ 二叉树和堆

        如有还有 老铁 对树的基础概念不太清楚可以先去看看这篇文章 哦:  树的详解

二、二叉树的概念和结构

🍎二叉树的概念

 一棵二叉树是节点的一个有限集合,该集合:
1、或者为空
2、由一个根节点加上两棵别称为左子树和右子树的二叉树组成

 

 💦二叉树的特点

1️⃣:二叉树不存在度大于 2 的结点

2️⃣:二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

⚠ 注意:对于任意的二叉树都是由以下几种情况复合而成的:


❓ 现实中的存在这种二叉树吗 ❓

当然啦,在人为的干涉的情况下一定是存在的,因为没有什么是一电锯解决不了的事

🍐特殊的二叉树(重点)

1️⃣:满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为 K,且结点总数是 2^k - 1,则它就是满二叉树。

2️⃣:完全二叉树:完全二叉树的前 k - 1 层都满的,第 k 层不一定满 (这就意味着满二叉树是完全二叉树,但完全二叉树不一定是满二叉树),但是从最后一层从左到右必须是连续的,也就是说完全二叉树中度为 1 的节点最少 0 个,最多 1 个。完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为 K 的,有 n 个结点的二叉树,且每个结点都与深度为 K 的满二叉树中编号从 1 至 n 的结点 一一 对应称之为完全二叉树。要注意的是满二叉树是一种特殊的完全二叉树。
 

 💦 满二叉树的节点个数 (k表示二叉树的层数)

利用公式所以满二叉树的节点个数就是 : 2^k - 1


💦完全二叉树的节点个数

▶  最多:2^k - 1  这是满二叉树

▶  最少:2^(k-1) - 1 + 1    2(k-1)

⚠ 注意:2^(k-1) - 1 这是前 k-1 层节点的个数,+1 则是第 k 层至少一个

🍉二叉树的性质 (超重点------面试做题会用)

1️⃣:若规定根节点的层数为 1,则一棵非空二叉树的第 i 层上最多有 2^(i-1) 个结点
 

2️⃣:若规定根节点的层数为 1,则深度为 h 的二叉树的最大结点数是 2^h - 1

3️⃣:对任何一棵二叉树, 如果度为 0 其叶结点个数为 n₀, 度为 2 的分支结点个数为 n₂,则有 n₀= n₂+1



4️⃣:若规定根节点的层数为 1,具有 n 个结点的满二叉树的深度为 h = log₂(n+1)  ps:log₂(n+1)是 log 以 2 为底, n+1 的对数



5️⃣:对于具有 n 个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从 0 开始编号,则对于序号为 i 的结点有:

▶ 若 i>0,i 位置节点的双亲序号:(i-1)/2;i=0,i 为根节点编号,无双亲节点

▶ 若 2i+1<n,左孩子序号:2i+1,2i+1>=n 否则无左孩子

▶ 若 2i+2<n,右孩子序号:2i+2,2i+2>=n 否则无右孩子

🍓二叉树的概念选择题

1、某二叉树共有 399 个结点,其中有 199 个度为 2 的节点,则该二叉树中的叶子节点数为( )

A. 不存在这样的二叉树

B. 200

C. 198

D. 199

📝 分析:这里的叶子节点就是度为 0 的节点,已知二叉树中度为 2 的节点为 199 个,则度为 0 的节点等于度为 2 的节点 +1,所以选择 B 选项


2、下列数据结构中,不适合采用顺序存储结构的是( )注意此题可以先了解下面的二叉树的存储结构在来做

A. 非完全二叉树

B. 堆

C. 队列

D. 栈

📝 分析:顺序结构存储就是使用数组来存储,它只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。数组只适合存储完全二叉树或者满二叉树。


3、在具有 2n 个节点的完全二叉树中,叶子节点个数为( )

A. n

B. n+1

C. n-1

D. n/2

📝 分析:

假设度为 0 的个数是 x0,度为 2 的个数是 x2,度为 1 的个数是 x1,那么:

▶ x0 + x1 + x2 = 2n

▶ x0 = x2 + 1

由 x0 = x2 + 1 得到 x2 = x0 - 1

所以 x0 + x1 + x2 = 2n 同 x0 + x1 + x0 - 1 = 2n 同 2x0 + x1 - 1 = 2n

这时再回过头想想完全二叉树中度为 1 的节点最少 0 个,最多就只有 1 个,

所以 x1 = 0 or 1

所以 2x0 + x1 - 1 = 2n 就有 2 种情况:

▶ 2x0 + 0 - 1 = 2n

▶ 2x0 + 1 - 1 = 2n

所以结果一目了然,当 x1 = 0 时,x0为小数,显然不可能;当 x1 = 1 时满足,所以选择 A 选项


4、一棵完全二叉树的节点数为 531 个,那么这棵树的高度为( )

A. 11

B. 10

C. 8

D. 12

📝 分析:

假设完全二叉树的高度是 h,那么:最多有 2^h-1 个节点;最少有 2^(h-1) 个节点

▶ h = 11 时:最多 2047;最少 2014,所以不合理

▶ h = 10 时:最多 1023;最少 512,所以合情合理

▶ h = 8 时:最多 255;最少 128,所以不合理

▶ h = 12 时:最多 4095;最少 2048,所以不合理

所以选择 B 选项


5、一个具有 767 个节点的完全二叉树,其叶子节点个数为 ( )

A. 383

B. 384

C. 385

D. 386

📝 分析:此题类似于第 3 题

假设度为 0 的个数是 x0,度为 2 的个数是 x2,度为 1 的个数是 x1,那么:

▶ x0 + x1 + x2 = 767

▶ x0 = x2 + 1

由 x0 = x2 + 1 得到 x2 = x0 - 1

所以 x0 + x1 + x2 = 767 同 x0 + x1 + x0 - 1 = 767 同 2x0 + x1 - 1 = 767

这时再回过头想想完全二叉树中度为 1 的节点最少 0 个,最多就只有 1 个,

所以 x1 = 0 or 1

所以 2x0 + x1 - 1 = 767 就有 2 种情况:

▶ 2x0 + 0 - 1 = 767

▶ 2x0 + 1 - 1 = 767

所以结果一目了然,当 x1 = 0 时,满足条件;当 x1 = 1 时,不满足条件,所以选择 B 选项


🍌二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构

1️⃣:顺序存储:顺序结构存储就是使用数组来存储,它只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实使用中只有堆才会使用数组来存储,二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。如下图所见,数组只适合存储完全二叉树或者满二叉树。


❓ 怎么表示下标和树的关系❓

  左孩子:leftchild = parent * 2 + 1

  右孩子:rightchild = parent * 2 + 2

  父亲 (这里无论是左孩子还是右孩子都适用于以下公式)

            parent = (child - 1) / 2

2️⃣:链式存储:二叉树的链式存储结构是指用链表来表示一棵二叉树,即用链表来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链的存储地址。链式结构又分为二叉链和三叉链,现阶段本篇文章我们只了解二叉链,在以后的文章内写到高阶数据结构时,如红黑树等才会用到三叉链。

三、二叉树顺序结构存储及实现

🍊二叉树的顺序结构

1️⃣:普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。
2️⃣:而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆 (一种二叉树) 使用顺序结构的数组来存储。
3️⃣:需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

 

 ❓ 操作系统和数据结构这两门学科中都有栈和堆的概念,如何区分 ❓

🍇堆的概念及结构

如果有一个关键码的集合K = {k₀, k₁, k₃,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储,在一个一维数组中,并满足:Ki <= K2*i+1 且 Ki <= K2*i+2 (Ki >= K2*i+1 且 Ki >= K2*i+2) i = 0,1,2…,则称为小堆 (或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

❗ 堆的性质 ❗

      ▶ 堆中某个节点的值总是不大于或不小于其父节点的值;

      ▶ 堆总是一棵完全二叉树;


❗ 大(根)堆和小(根)堆 ❗

      ▶ 大(根)堆,树中所有父亲都大于或者等于孩子,且大堆的根是最大值;

      ▶ 小(根)堆,树中所有父亲都小于或者等于孩子,且小堆的根是最小值;

🍋堆的概念选择题

1、下列关键字序列为堆的是( )

A. 100, 60, 70, 50, 32, 65

B. 60, 70, 65, 50, 32, 100

C. 65, 100, 70, 32, 50, 60

D. 70, 65, 100, 32, 50, 60

E. 32, 50, 100, 70, 65, 60

F. 50, 100, 70, 65, 60, 32

📝 分析:根据堆的概念分析,A 选项为大根堆;


2、注,请理解下面堆应用的知识再做。已知小根堆为 8, 15, 10, 21, 34, 16, 12,删除关键字 8 之后需重建堆,在此过程中,关键字之间的比较次数是( )

A. 1

B. 2

C. 3

D. 4

📝 分析:C

此题考查的是建堆的过程
1. 运用 --- 向下调整算法 ,先比较左右孩子谁小

2. 交换堆定数据和堆尾数据,再次运用向下调整算法


3、注,请理解下面堆应用的知识再做。一组记录排序码为 (5 11 7 2 3 17),则利用堆排序方法建立的初始堆为( )

A. (11 5 7 2 3 17)

B. (11 5 7 2 17 3)

C. (17 11 7 2 3 5)

D. (17 11 7 5 3 2)

E. (17 7 11 3 5 2)

F. (17 7 11 3 2 5)

📝 分析:

此题考查的是堆排序建堆的过程

根据下面堆排序的过程分析,选择 C 选项

🥝堆的实现

⭐ 堆的准备工作

💦创建堆的结构

✨思路:由上文得知,堆的基本结构是数组,创建堆结构的时候就跟之前一样动态开辟即可,操作流程也是类似的,直接上代码。不过先以小根堆为例。

✨:Heap.h 文件:

// 定义 固定的数据类型  (以便于后期转换)
typedef int HPDatatype;
// 建立 堆的顺序表
typedef struct Heap
{
	HPDatatype* a;   // 动态数组
	HPDatatype size; // 有效数据个数
	HPDatatype capacity; // 动态数组的大小
}HP;
 💦堆的初始化

✨思路:对堆进行初始化,那么传过来的结构体指针不能为空,首先要断言。剩下的操作跟之前顺序表,栈初始化没两样。

  • Heap.h 文件:
  • // 堆的初始化
    void HPInit(HP* ps);
  • Heap.c 文件:
  • // 堆的初始化
    void HPInit(HP* ps)
    {
    	assert(ps);
    	ps->a = NULL;
    	ps->size = 0;
    	ps->capacity = 0;
    }
 💦堆的打印

✨思路:其实堆的打印很简单,堆的物理结构就是数组,打印堆的实质不还是类似于先前顺序表的打印嘛,依次访问下标打印即可。

  • Heap.h 文件:
  • // 打印堆
    void HPPrint(HP* ps);
  • Heap.c 文件:
  • // 打印堆
    void HPPrint(HP* ps)
    {
    	assert(ps);
    	for (int i = 0; i < ps->size; i++)
    	{
    		printf("%d ", ps->a[i]);
    	}
    	printf("\n");
    }
 💦堆的销毁

✨思路:对于动态开辟的内存在使用完毕后要即使进行销毁

  • Heap.h 文件:
  • // 堆的销毁
    void HPDesttory(HP* ps);
  • Heap.c 文件:
  • // 堆的销毁
    void HPDesttory(HP* ps)
    {
    	assert(ps);
    	free(ps->a);
    	ps->a = NULL;
    	ps->size = 0;
    	ps->capacity = 0;
    }

 ⭐ 堆的调整

💦堆的数值交换

✨思路:堆的交换还是比较简单的,跟之前写的没什么区别,记得传地址。

 ⚠ 注意:如果不清楚这里为什么要传入地址可以看这篇博客哦:函数的形参于实参

  • Heap.h 文件:
  • //数据交换
    void swap(HPDatatype* x, HPDatatype* y);
  • Heap.c 文件:
  • void swap(HPDatatype* x, HPDatatype* y)
    {
    	int temp = 0;
    	temp = *x;
    	*x = *y;
    	*y = temp;
    }
💦堆的向上调整算法 (应用于堆的数据插入)

✨思路:此算法是为了确保插入数据后的堆依然是符合堆的性质而单独封装出来的函数,就好比如我们后续要插入的数字10,画个图先(小堆

✨:为了确保在插入数字10后依然是个小根堆,所以要将10和28交换,依次比较父结点parent和子结点child的大小,当父小于子结点的时候,就返回,反之就一直交换,直到根部while(child>0)截止

✨:由前文的得知的规律,parent = (child - 1) / 2,我们操控的是数组,但要把它想象成二叉树。画图演示调整过程:

  • Heap.c 文件:
  • // 向上调整函数(没有改变动态数组的首地址,不需要用指针传输)
    // 时间复杂度O(logN)
    // 传入了 动态数组  和  最后一个位置的下标(也就是孩子的下标)
    // 向上调整的条件;上面的数据是堆
    void AdjustUp(HPDatatype* a, int child)
    {
    	// 计算父亲的小标
    	int parent = (child - 1) / 2;
    	// 当 child 的小标大于 0 就继续 (也就小于是根节点位置)
    	while (child > 0)
    	{
    		// 小堆 <
    		// 大堆 >
    		if (a[child] < a[parent])
    		{
    			swap(&a[child], &a[parent]);
    			child = parent;
    			parent = (child - 1) / 2;
    		}
    		else
    		{
    			break;
    		}
    	}
    }
 💦堆的向下调整算法(应用于堆的数据删除)

✨思路:此算法是为了确保删除数据后的堆依然是符合堆的性质而单独封装出来的函数,就好比如我们后续要插入的数字10,画个图先(小堆:删除数据28

✨:此时我们看到,这个二叉树整体上不符合堆的性质,但是其根部的左子树和右子树均满足堆的性质。 接下来,就要进行向下调整,确保其最终是个堆。只需三部曲。

▶:找出左右孩子中最小的那个

▶:跟父亲比较,如果比父亲小,就交换

▶:再从交换的孩子位置继续往下调整

变化图如下:

  • Heap.c 文件:
  • // 向下调整
    // 向下调整的前提:后面的数据是堆
    void AdjustDown(HPDatatype* a, int n, int parent)
    {
    	// 左孩子
    	int child = parent * 2 + 1;
    	// 孩子可能会超出数组范围
    	while (child < n)
    	{
    		// 如果右孩子小于左孩子
    		// 找出小的那个孩子
    		// 保证右孩子存在且不越界
    		// 大堆 >
    		// 小堆 <
    		if (child + 1 < n && a[child+1] < a[child])
    		{
    			//跳转到有孩子那里
    			child++;
    		}
    		// 开始向下调整
    		// 大堆 >
    		// 小堆 <
    		if (a[child] < a[parent])
    		{
    			swap(&a[child], &a[parent]);
    			parent = child;
    			child = parent * 2 + 1;
    		}
    		else
    		{
    			break;
    		}
    
    	}
    }

 ⭐ 堆的核心功能

💦堆的数据插入

✨思路:堆的插入不像先前顺序表一般,可以头插,任意位置插入等等,因为是堆,要符合大根堆或小根堆的性质,不能改变堆原本的结构,所以尾插才是最适合的,并且尾插后还要检查是否符合堆的性质。

✨:比如我们有一串数组,该数组是按照小根堆的性质存储的。现在想在数组尾部插入一个数字10,如图:

✨思路:这颗树在没插入数字10之前是个小堆,在插入后就不是了,改变了小根堆的性质了。因为子结点10小于其父结点28,那该怎么办呢?

✨:首先最基本的,在插入之前就要先判断该堆的容量是否还够插入数据,先检查要不要扩容,扩容完毕后。我们可以发现,插入的10只会影响到从自己本身开始到根,也就是祖先,只要这条路上符合堆的性质,插入即成功。

✨:核心思想:向上调整算法。当我们看到插入的10比父亲28小时,此时交换数字,但是此时10又要比18小,再次交换,最终发现10比15还小,再次交换,此时结束,到根部了。当然这是最坏的情况,如果在中间换的过程中满足了堆的性质,那么就不需要再换了,直接返回即可。这就叫向上调整算法,直接套用上面的函数即可。

  • Heap.h 文件:
  • // 堆的插入数据(尾插).
    void HPPush(HP* ps, HPDatatype x)
    {
    	assert(ps);
    	// 判断是否扩容
    	if (ps->size == ps->capacity)
    	{
    		// 重新创建一个空间大小
    		HPDatatype newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
    		// 重置新的数组地址(扩容)
    		HPDatatype* temp = (HPDatatype*)realloc(ps->a, sizeof(HPDatatype) * newcapacity);
    		if (temp==NULL)
    		{
    			perror("realloc fail!");
    			exit(-1);
    		}
    		ps->a = temp;
    		ps->capacity = newcapacity;
    	}
    	// 插入数据
    	ps->a[ps->size] = x;
    	ps->size++;
    	// 向上调整函数
    	// (没有改变动态数组的首地址,不需要用指针传输)
    	// 传输了两个参数  ps->a 表示整个动态数组的首地址,ps->size-1:表示最后一个数据的下标
    	AdjustUp(ps->a, ps->size - 1);
    }
💦堆的数据删除

✨思路:在上文堆的插入中,我们明确插完依旧是堆,而这里堆的删除同样也要确保删除后依旧是堆,注意:这里堆的删除是删除堆顶的数据。以小根堆为例,删除堆顶的数据,也就是把最小的数据删掉,那么还要保证依旧是堆,我给出的思路是:

  • 首先,把第一个数据和最后一个数据进行交换
  • 交换后,此时的堆就不符合其性质了,因为原先最后一个数据肯定是比第一个数据大的,现在最后一个数据到了堆顶,就不是堆了,但是根结点的左子树和右子树不受影响,单独看它们依旧是堆
  • 接着,--size,确保删除堆顶数据
  • 因为此时堆顶的数据已经到了堆尾,只需要像顺序表那样--size,确保有效数据减1也就是确保了堆顶的删除
  • 最后,运用向下调整算法,确保其是堆结构
    变化图如下:
  • ✨ 时间复杂度分析:
  • 第一个数据和最后一个数据交换是O(1),而向下调整算法的时间复杂度为O(logN),因为向下调整是调整高度次,根据结点个数N可以推出高度约为logN
  • Heap.h 文件:
  • // 堆的删除
    void HPPop(HP* ps);
  • Heap.c 文件:
  • // 堆的删除
    void HPPop(HP* ps)
    {
    	assert(ps);
    	// 保证有数据删除
    	assert(ps->size > 0);
    	// 头和尾进行交换
    	swap(&ps->a[0], &ps->a[ps->size - 1]);
    	// 删除数据
    	ps->size--;
    	// 需要进行向下调整
    	// 其中 ps->size 表示这个堆有几个数据,0 表示 parent 的位置下标,在栈顶。
    	AdjustDown(ps->a, ps->size, 0);
    }
    
 💦堆的判空

✨思路:堆的判空很简单,跟之前栈顺序表啥的没区别,若size为0,直接返回即可。

  • Heap.h 文件:
  • // 判空函数
    bool HPEmpty(HP* ps)
  • Heap.c 文件:
  • // 判空函数
    bool HPEmpty(HP* ps)
    {
    	assert(ps);
    	// 如果size = 0 就为空
    	// 注意这里是两个等于号
    	return ps->size == 0;
    }
 💦取堆顶数据

✨思路:直接返回堆顶即可。前提是得断言size>0

  • Heap.h 文件:
  • // 堆的顶
    HPDatatype HPTop(HP* ps);
  • Heap.c 文件:
  • // 堆的顶
    // 此时有返回值
    HPDatatype HPTop(HP* ps)
    {
    	assert(ps);
    	assert(ps->size > 0);
    
    	return ps->a[0];
    }

⭐堆的总代码

💦Heap.h 文件
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

// 用顺序表实现 二叉树

// 定义 固定的数据类型  (以便于后期转换)
typedef int HPDatatype;
// 建立 堆的顺序表
typedef struct Heap
{
	HPDatatype* a;   // 动态数组
	HPDatatype size; // 有效数据个数
	HPDatatype capacity; // 动态数组的大小
}HP;


// 要正确的进行的初始化结构体,我们需要传递  HP 的地址,通过指针对结构体的内容进行修改。
// 堆的初始化
void HPInit(HP* ps);
// 堆的销毁
void HPDesttory(HP* ps);
//数据交换
void swap(HPDatatype* x, HPDatatype* y);
// 堆的向上调整
void AdjustUp(HPDatatype* a, int child);
// 堆的插入数据(尾插)
void HPPush(HP* ps, HPDatatype x);
// 打印堆
void HPPrint(HP* ps);
// 向下调整
void AdjustDown(HPDatatype* a, int n, int parent);
// 堆的删除
void HPPop(HP* ps);
// 堆的顶
HPDatatype HPTop(HP* ps);
// 判空函数
bool HPEmpty(HP* ps);
 💦Heap.c 文件
#define  _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"

// 堆的初始化
void HPInit(HP* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}
// 打印堆
void HPPrint(HP* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}
// 堆的销毁
void HPDesttory(HP* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

// 交换函数(进行了数值改变,用到了传值调用)
// 此时改变了数值本身,需要用到传值条用
void swap(HPDatatype* x, HPDatatype* y)
{
	int temp = 0;
	temp = *x;
	*x = *y;
	*y = temp;
}



// 向上调整函数(没有改变动态数组的首地址,不需要用指针传输)
// 时间复杂度O(logN)
// 传入了 动态数组  和  最后一个位置的下标(也就是孩子的下标)
// 向上调整的条件;上面的数据是堆
void AdjustUp(HPDatatype* a, int child)
{
	// 计算父亲的小标
	int parent = (child - 1) / 2;
	// 当 child 的小标大于 0 就继续 (也就小于是根节点位置)
	while (child > 0)
	{
		// 小堆 <
		// 大堆 >
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}



// 堆的插入数据(尾插).
void HPPush(HP* ps, HPDatatype x)
{
	assert(ps);
	// 判断是否扩容
	if (ps->size == ps->capacity)
	{
		// 重新创建一个空间大小
		HPDatatype newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		// 重置新的数组地址(扩容)
		HPDatatype* temp = (HPDatatype*)realloc(ps->a, sizeof(HPDatatype) * newcapacity);
		if (temp==NULL)
		{
			perror("realloc fail!");
			exit(-1);
		}
		ps->a = temp;
		ps->capacity = newcapacity;
	}
	// 插入数据
	ps->a[ps->size] = x;
	ps->size++;
	// 向上调整函数
	// (没有改变动态数组的首地址,不需要用指针传输)
	// 传输了两个参数  ps->a 表示整个动态数组的首地址,ps->size-1:表示最后一个数据的下标
	AdjustUp(ps->a, ps->size - 1);
}


// 向下调整
// 向下调整的前提:后面的数据是堆
void AdjustDown(HPDatatype* a, int n, int parent)
{
	// 左孩子
	int child = parent * 2 + 1;
	// 孩子可能会超出数组范围
	while (child < n)
	{
		// 如果右孩子小于左孩子
		// 找出小的那个孩子
		// 保证右孩子存在且不越界
		// 大堆 >
		// 小堆 <
		if (child + 1 < n && a[child+1] < a[child])
		{
			//跳转到有孩子那里
			child++;
		}
		// 开始向下调整
		// 大堆 >
		// 小堆 <
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}

	}
}

// 堆的删除
void HPPop(HP* ps)
{
	assert(ps);
	// 保证有数据删除
	assert(ps->size > 0);
	// 头和尾进行交换
	swap(&ps->a[0], &ps->a[ps->size - 1]);
	// 删除数据
	ps->size--;
	// 需要进行向下调整
	// 其中 ps->size 表示这个堆有几个数据,0 表示 parent 的位置下标,在栈顶。
	AdjustDown(ps->a, ps->size, 0);
}

// 堆的顶
// 此时有返回值
HPDatatype HPTop(HP* ps)
{
	assert(ps);
	assert(ps->size > 0);

	return ps->a[0];
}

// 判空函数
bool HPEmpty(HP* ps)
{
	assert(ps);
	// 如果size = 0 就为空
	// 注意这里是两个等于号
	return ps->size == 0;
}
 💦Test.c 文件
void menu()
{
    printf("***********************************************************\n");
    printf("1、在堆中插入数据             2、在堆中删除数据\n");
    printf("\n");
    printf("3、打印堆                    -1、退出");                
    printf("\n");
    printf("***********************************************************\n");
}


int main()
{
    printf("*************  欢迎大家来到堆的测试  **************\n");
    int option = 0;
    HP ps;  // 创建一个空的堆结构
    HPInit(&ps); //初始化这个堆结构
    do
    {
        menu();
        printf("请输入你的操作: >");
        scanf("%d", &option);
        int sum = 0;
        switch (option)
        {
        case 1:
            printf("请依次输入你要向小堆中插入的数据:,以-1结束\n");
            scanf("%d", &sum);
            while (sum != -1)
            {
                HPPush(&ps, sum);
                scanf("%d", &sum);
            }
            break;
        case 2:
            HPPop(&ps);
            break;
        case 3:
            HPPrint(&ps);
            break;
        default:
            if (option == -1)
            {
                exit(0); //退出程序
            }
            else
            {
                printf("输入错误,请重新输入\n");
            }
            break;
        }
        
    } while (option != -1); // 退出程序
    HPDesttory(&ps);
    return 0;
}
 💦代码运行的菜单界面

 四、共勉

以下就是我对数据结构---二叉树和堆的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对数据结构-------堆的应用(排序,TopK问题)请持续关注我哦!!!!!  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1056053.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

三、逻辑代数基础

1.简介 在数字电路中&#xff0c;我们用两种数码1和0来表示一个信号的两种不同的逻辑状态。 例如&#xff1a;用1表示开关闭合&#xff0c;电路导通&#xff1b;用0表示开关打开&#xff0c;电路断开。 这两种对立的逻辑关系就称之为二值逻辑。 当两种数码表示不同的逻辑状态…

Acwing 845. 八数码

Acwing 845. 八数码 知识点题目描述思路讲解代码展示 知识点 BFS 题目描述 思路讲解 分析一下y总的思路&#xff0c;也相当于做个课堂笔记吧&#xff08;这也太巧妙了吧&#xff0c;讲解视频不到20分钟&#xff0c;我愣是半天没想出来 1、题目的目标 2、移动情况 移动方式…

基于Java的城市天然气费管理系统的设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

S5PV210裸机(一):裸机基础,arm指令,210启动刷机

本文主要探讨s5pv210裸机基础知识&#xff0c;arm指令&#xff0c;以及210启动刷机相关知识。 Soc与cpu Soc是cpu与其他外设的集合即SoC<>cpuDDRflashutral...... 地址总线与数据总线 cpu通过地址总线寻址即传输DDR或flash等地址,通过数据总线与外设进…

【算法优选】双指针专题——贰

文章目录 &#x1f60e;前言&#x1f332;[快乐数](https://leetcode.cn/problems/happy-number/)&#x1f6a9;题目描述&#x1f6a9;题⽬分析&#xff1a;&#x1f6a9;算法思路&#xff1a;&#x1f6a9;代码实现&#xff1a; &#x1f38b;[盛水最多的容器](https://leetco…

第四十章 持久对象和SQL - Object IDs

文章目录 第四十章 持久对象和SQL - Object IDsObject IDsID是如何确定的访问 ID 第四十章 持久对象和SQL - Object IDs Object IDs 每个对象在其所属的每个范围内都有一个唯一的 ID。在大多数情况下&#xff0c;使用此 ID 来处理对象。此 ID 是类中 %Persistent 的以下常用方…

如何制作在线流程图?6款在线工具帮你轻松搞定

流程图&#xff0c;顾名思义 —— 用视觉化的方式来描述一种过程或流程。它可以应用于各种领域&#xff0c;从业务流程&#xff0c;算法&#xff0c;到计算机程序等。然而&#xff0c;在创建流程图时&#xff0c;可能会遇到许多问题或者困惑&#xff0c;如缺乏专业的设计技能&a…

锚框_的标定

一、查漏补缺、熟能生巧&#xff1a; 1.关于fix.axis.add_patch在原来图像的坐标系同添加 边框的函数的使用&#xff1a; 2.torch.arange( h , device)生成tensor的等差数组: 3.torch.T&#xff08;&#xff09;就是transpose转置操作的函数咯: 4.torch.repeat操作&#xff0c…

静态数码管显示+动态数码管显示——“51单片机”

各位CSDN的uu们好呀&#xff0c;今天小雅兰的内容还是51单片机的知识&#xff0c;是为静态数码管显示和动态数码管显示&#xff0c;下面&#xff0c;让我们进入51单片机的世界吧&#xff01;&#xff01;&#xff01; 静态数码管显示 动态数码管显示 源代码 静态数码管显示 …

网络运营推广过程中客户说需要资质

大家好&#xff0c;我是网络工程师成长日记实验室的郑老师&#xff0c;您现在正在查看的是网络工程师成长日记专栏&#xff0c;记录网络工程师日常生活的点点滴滴 一个同学跟我学网络运营&#xff0c;他说他现在也学我做弱电。然后他说他接到电话&#xff0c;对方都是问资质&am…

首发Orin N芯片,腾势追赶「智驾第一梯队」

张祥威 编辑 | 德新 英伟达最新一代芯片—— Orin N&#xff0c;腾势拿下 首发。 9月26日&#xff0c;腾势N7推出「高快智驾包」。官方描述中&#xff0c;这一选装将“基于新一代NIVIDIA DRIVE ORIN的 高性能平台”&#xff0c;可以实现高速NOA。 此前&#xff0c;腾势的…

Acwing 843. n-皇后问题

Acwing 843. n-皇后问题 知识点题目描述思路讲解代码展示 知识点 DFS剪枝 题目描述 思路讲解 代码展示 第一种搜索方式&#xff1a; #include <iostream>using namespace std;const int N 20;int n; char g[N][N]; bool col[N], dg[N * 2], udg[N * 2];void dfs(in…

常见开发、测试模型

开发模型瀑布模型螺旋模型增量、迭代敏捷开发模型 测试模型V模型W模型 开发模型 瀑布模型 瀑布模型的每一个阶段都只执行一次&#xff0c;是线性顺序进行的软件开发模式。 优点&#xff1a;每个阶段做什么&#xff1b;产生什么非常清晰&#xff1b; 缺点&#xff1a;风险往…

Python3数据科学包系列(三):数据分析实战

Python3中类的高级语法及实战 Python3(基础|高级)语法实战(|多线程|多进程|线程池|进程池技术)|多线程安全问题解决方案 Python3数据科学包系列(一):数据分析实战 Python3数据科学包系列(二):数据分析实战 一: 数据分析与挖掘认知升维 我们知道在数据分析与数据挖掘中,数据…

15: 8种GPIO模式和其他资源

目录 一:GPIO 1:简历 2:模式 3:位结构 4:八种模式 A: 浮空/上拉/下拉输入 B:模拟输入 ----GPIO_Mode_AIN C: 开漏/推挽输出 D:复用开漏/推挽输出 E: 模式总结 二:其他资源 1:片上资源/外设 2:引脚定义表 一:GPIO 1:简历 GPIO&#xff08;General Purpose Input …

排序---P1012 [NOIP1998 提高组] 拼数

思路&#xff1a; 这道题的思路就是进行排序&#xff0c;但不同于以往是根据数的大小排序&#xff0c;这道题是根据最高位最大就放在越前面。那么要怎么解决最高位越大排得越前这个问题呢&#xff0c;我们就会想到用字符串比较大小&#xff0c;就可以解决&#xff0c;所以我们…

RESTFul风格接口如何设计

RESTFul风格设计规范 HTTP协议请求方式要求 REST 风格主张在项目设计、开发过程中&#xff0c;具体的操作符合HTTP协议定义的请求方式的语义。 操作请求方式查询操作GET保存操作POST删除操作DELETE更新操作PUT 需求分析 数据结构&#xff1a; User {id 唯一标识,name 用户名&a…

车牌超分辨率:License Plate Super-Resolution Using Diffusion Models

论文作者&#xff1a;Sawsan AlHalawani,Bilel Benjdira,Adel Ammar,Anis Koubaa,Anas M. Ali 作者单位&#xff1a;Prince Sultan University 论文链接&#xff1a;http://arxiv.org/abs/2309.12506v1 内容简介&#xff1a; 1&#xff09;方向&#xff1a;图像超分辨率技术…

MySQL进阶_1.数据类型约束

文章目录 第一章、名词解释第二章、数据类型2.1、数据类型简介2.2、数据类型对应属性2.3、整数类型2.4、浮点类型2.5、定点数类型2.6、日期和时间类型2.7、文本字符串类型2.7.1、CHAR和VARCHAR区别2.7.2、TEXT 2.8、小结和建议 第三章、约束3.1 约束的定义3.2 非空约束3.3 唯一…

浅谈OV SSL 证书的优势

随着网络威胁日益增多&#xff0c;保护网站和用户安全已成为每个企业和组织的重要任务。在众多SSL证书类型中&#xff0c;OV&#xff08;Organization Validation&#xff09;证书以其独特的优势备受关注。让我们深入探究OV证书的优势所在&#xff0c;为网站安全搭建坚实的防线…