二叉树顺序结构的实现(堆)

news2024/12/25 12:51:01

二叉树的基本概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

有一个特殊的结点,称为根结点,根结点没有前驱结点

除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1)因此,树是递归定义的。

就像这样

这样

二叉树的基本性质

结点的度

一个结点含有的子树的个数称为该结点的度

如上图:A的为6(BCDEFG)

叶结点或终端结点

度为0的结点称为叶结点

如上图:B、C、H、I...等结点为叶结点(没有孩子的就是叶子节点)

非终端结点或分支结点

度不为0的结点

如上图:D、E、F、G...等结点为分支结点

双亲结点或父结点

若一个结点含有子结点,则这个结点称为其子结点的父结点

如上图:A是B的父结点

孩子结点或子结点

一个结点含有的子树的根结点称为该结点的子结点

如上图:B是A的孩子结点

兄弟结点

具有相同父结点的结点互称为兄弟结点

如上图:B、C是兄弟结点

树的度

一棵树中,最大的结点的度称为树的度

如上图:树的度为6(最大的多少就是多少)(BCDEFG)

结点的层次

从根开始定义起,根为第1层,根的子结点为第2层

以此类推;(一般情况下从1开始 有些会从0开始)

但是为了方便计算,右兄弟左孩子的时候我是从0 开始的

树的高度或深度

树中结点的最大层次

如上图:树的高度为4

  • 树的深度是4,因为从根节点A到最远的叶子节点F,需要经过4条边。

堂兄弟结点

双亲在同一层的结点互为堂兄弟

如上图:H、I互为兄弟结点

结点的祖先

从根到该结点所经分支上的所有结点

如上图:A是所有结点的祖先

子孙

以某结点为根的子树中任一结点都称为该结点的子孙

如上图:所有结点都是A的子孙

森林

由m(m>0)棵互不相交的树的集合称为森林

简单说 的说就是有好几个根节点,也就是好几个A组成(多棵树。并查集)

二叉树的定义

树的关键点是不知道定义几个树的度

1,明确知道的话我们可以写

2,不知道几个树的度,顺序表来写

3,右兄弟左孩子写法

不管多少,我们只定义两个树的度

特殊的二叉树:

1. 满二叉树:

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

2. 完全二叉树:

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。从左到右必须连续(完全二叉树)

对比

右兄弟左孩子

右兄弟左孩子表示法,实际上是一种完全二叉树,需要按照顺序在二叉树里面进行放入

优缺点

优点

  • 空间效率:这种写法允许我们使用数组来存储完全二叉树,而不需要为每个节点分配两个指针的空间。

  • 简单性:它简化了对完全二叉树的遍历和操作。

缺点

  • 限制性:这种方法只适用于完全二叉树,对于非完全二叉树,这种写法不适用。

  • 复杂性:对于不熟悉这种表示法的人来说,理解和实现可能会有些复杂。

定义

 在定义里面不管多少,我们只定义两个树的度

逻辑讲解

右兄弟左孩子写法

在这种写法中,每个节点有两个子节点(左孩子和右兄弟),如果一个节点没有左子节点,那么它的左指针会指向它的右兄弟,而不是指向一个子节点。这种表示方法可以有效地利用数组来存储二叉树,同时保持树的结构信息。

例子

假设我们有以下完全二叉树:

在这个数组中,每个元素代表一个节点:

  • A是根节点。
  • B是A的左孩子。
  • C是A的右兄弟。
  • D是B的左孩子。
  • E是B的右兄弟,同时也是C的左孩子。
  • F是C的右兄弟。
  • G是F的右兄弟。

简单的说就是,我们存储的时候,我们会按照顺序进行存储,A下面只放两个数值,放满了,我们就往A下面放,A下面放满了两个,我们往B下面放,循环套娃

树的存储方式

顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。

存储方式

二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

计算节点方式

我们可以通过计算找到父子节点,左右兄弟节点

节点计算总结

在数组中,我们可以通过节点的索引来确定其左孩子和右兄弟:

  • 假设父亲在数组里面的下标为i

  • 左孩子位于索引 2 * i + 1

  • 右兄弟位于索引 2 * i + 2

  • 假设孩子在数组里面的下标为j

  • 父亲位于(j-1)/2

如果一个节点是叶子节点,或者在最后一层并且没有右兄弟,那么它的右兄弟指针将指向一个空值或者一个表示终止的特殊值。

二叉树使用注意事项

数组存储只适合满二叉树,或者特殊二叉树

像下面的情况就是不适合,不是不能实现,是不适合实现的

非完全二叉树,适合的实现方式方式是:二叉树链式结构实现

完全二叉树,适合的实现方式是:二叉树顺序结构的实现

下面我们实现,二叉树顺序结构的实现

二叉树顺序结构(堆):堆的概念:

存储方式

二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

还是那句话,堆是一种顺序结构实现的完全二叉树

在逻辑上不是连续的,但是在实际上,是数组进行实现的

堆的性质:

在数据结构中,“堆”是一种特殊的完全二叉树,它满足以下两个性质:

  1. 结构性质:堆是一个完全二叉树,如果用数组表示,那么除了最后一层外,每一层都被完全填满,且最后一层从左到右填充。
  2. 堆性质:树中任何节点的值都必须大于或等于其子节点的值(最大堆),或小于或等于其子节点的值(最小堆)。

最大堆(大堆)

在最大堆中,父节点的值总是大于或等于其子节点的值。这意味着堆中的最大值位于根节点。最大堆常用于实现优先级队列,其中最大元素具有最高的优先级。

特点:
  • 根节点是堆中的最大元素。
  • 任何父节点的值都不小于其子节点的值。

应用场景:
  • 堆排序:在堆排序算法中,最大堆用于选择序列中的最大元素。
  • 优先级队列:在需要频繁访问最大元素的场景中使用。

最小堆(小堆)

在最小堆中,父节点的值总是小于或等于其子节点的值。这意味着堆中的最小值位于根节点。最小堆同样用于实现优先级队列,但最小元素具有最高的优先级。

特点:
  • 根节点是堆中最小元素。
  • 任何父节点的值都不大于其子节点的值。

应用场景:
  • 堆排序:在堆排序算法中,最小堆用于选择序列中的最小元素。
  • 优先级队列:在需要频繁访问最小元素的场景中使用。

二叉树顺序结构的实现(堆):堆的实现:

实现(小堆)

在实际的编程实现中,堆通常用数组来表示,因为这样可以有效地利用内存空间,并且可以快速地通过索引访问父节点和子节点。节点的索引和其父节点或子节点的索引之间有一定的关系:

  • 父节点索引:(i - 1) / 2
  • 左孩子节点索引:2 * i + 1
  • 右孩子节点索引:2 * i + 2

其中 i 是节点的索引。

理解大堆和小堆的概念对于在实际应用中选择合适的数据结构和算法非常重要。

创建文件(小堆)

这里我们依旧是创建三个文件来实现顺序结构的堆

创建堆(小堆)

定义一个堆(Heap)的数据结构

这里所需要的头文件,是文件所需要的,这里不做过多解释,主要看的是定义的是数据结构,这里我们是用数组实现的

  1. 定义数据类型 HPDataType:使用 typedef 创建了一个新的类型别名 HPDataType,这里指定为 int 类型。这意味着 HPDataType 可以用来声明整数类型的变量,但在堆结构中,它可以被用作更通用的数据类型。

  2. 定义结构体 Heap:创建了一个结构体 Heap,它将用于表示整个堆的元数据和存储空间。

  3. 成员变量

    • HPDataType* _a:这是一个指针,指向堆中第一个元素的地址,用于访问和操作堆中的元素。
    • int _size:表示当前堆中元素的数量,即已使用的元素个数。
    • int _capacity:表示堆的最大容量,即 _a 指针所指向的数组能够容纳的元素个数。

这个结构体定义为后续实现堆的操作(如插入、删除、调整等)提供了必要的数据结构支持。在实际使用中,你还需要实现一些函数来操作这个 Heap 结构体,比如初始化堆、插入元素、删除最大元素(在最大堆中)或最小元素(在最小堆中)、销毁堆等。

创建了 Heap 结构体后,你通常会通过调用相关函数来初始化堆、使用它进行操作,并在最后销毁堆以释放分配的内存

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
#include<time.h>
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;//首元素地址
	int _size;//元素个数
	int _capacity;//元素容量
}Heap;

堆的初始化和销毁(小堆)

这里的初始化和销毁和顺序表的初始化以及销毁是差不多的

这里知识掌握不牢固的同学,可以看一下我写的顺序表的篇章

顺序表(增删减改)+通讯录项目(数据结构)+顺序表专用题型-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/137484207

//堆的初始化
void HeapInit(Heap* 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;
}

HeapInit 函数接收一个指向 Heap 结构体的指针 hp。这个函数的作用是将 hp 指向的 Heap 结构体初始化为一个空堆:

  • _a 成员被设置为 NULL,表示没有分配任何内存空间。
  • _capacity 成员被设置为 0,表示堆的最大容量目前为 0,即没有预留空间。
  • _size 成员被设置为 0,表示堆中目前没有任何元素。

HeapDestory函数接收一个指向 Heap 结构体的指针 hp,并执行以下操作来销毁堆:

  • 使用 assert 确保传入的 hp 不是 NULL
  • 使用 free 函数释放 _a 指针指向的内存空间。由于 _a 在 HeapInit 中被初始化为 NULL,这里释放前应确保 _a 非 NULL(这通常在其他函数中进行判断和分配)。
  • 将 _a 设置回 NULL,确保指针不再指向任何内存空间。
  • 将 _capacity 和 _size 重置为 0,恢复堆为一个空状态。

加入数据(小堆)

首先我们需要看堆实现的时候是如何调整的

1,这里最后一个数值是新加入的数值,那么此时我们需要向上调整,也就是我们需要和上一个父亲节点进行对比,

2,如果孩子节点小于父亲节点,那么我们就需要进行交换,因为小堆根是最小的数值

3,并且更新父亲节点想下标和孩子节点的下标

4,备注:这里我们不需要三方进行对比,意思就是,我们不需要三个数值进行对比,因为如果左孩子存在,按照这个逻辑,左孩子最后如果小于父亲节点就一定会进行交换,不小于就不会进行交换,然后插入右孩子时候,右孩子只需要和父亲进行比较就可以了,就算右孩子比父亲数值小,那么此时会和父亲节点进行交换,这个时候我们发现,左孩子一定大于或者等于右孩子,而右孩子也一定大于或者等于父亲节点,因为这里是小堆,越往上越小。

解释一下备注4:

此时我们发现,我们还没有插入左右孩子的节点

此时插入节点还没有交换

进行交换,此时我们发现左节点一定大于父亲节点

插入右孩子,此时还没有交换

进行交换,我们只需要对新插入数值和父亲节点进行对比就可以,不需要1,7,0 三个数值进行对比,从而决定交换不交换,因为左孩子一定小于父亲,如果父亲小于左孩子,交换之后,那么左孩子也一定小于右孩子

这里我们了解一下循环逻辑

循环三要素

1,初始条件

2,循环条件

3,结束条件

那么向上调整的结束逻辑应该是什么,那就是当插入的数值,也就是孩子元素下标到最上面的时候,也就是到0的时候,也就是应该结束循环,所以chile>0,这里是这一种情况也是三种情况下唯一不会产生越界的逻辑。

代码实现

//交换
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向上调整
void AdjustUp(HPDataType* a, int chile)
{
	//获取父母所在位置
	int parent = (chile - 1) / 2;
	while (chile > 0)//这个循环条件不会越界,其余两个循环条件都会导致越界,但是也会正常运行
	{
		if (a[chile] < a[parent])
		{
			//传递地址,指针接收
			Swap(&a[chile], &a[parent]);
			//更新父母和孩子的下标
			chile = parent;
			parent = (chile - 1) / 2;
		}
		else
		{
			break;
		}
	}

	这个循环条件产生了越界,但是	printf("%d ", ps._a[-1]);(特别大) printf("%d ", ps._a[100]);(特别小)
	//while (a[chile] < a[parent])
	//{
	//	//传递地址,指针接收
	//	Swap(&a[chile], &a[parent]);
	//	//更新父母和孩子的下标
	//	chile = parent;
	//	parent = (chile - 1) / 2;
	//}
}

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	//判断空间大小,并且开辟空间
	if (hp->_capacity == hp->_size)
	{
		int newcapacity = hp->_a == 0 ? 4 : hp->_capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("HeapPush:tmp:");
			exit(1);
		}
		hp->_a = tmp;
		hp->_capacity = newcapacity;
	}
	//插入
	hp->_a[hp->_size] = x;
	hp->_size++;
	//向上调整
	AdjustUp(hp->_a, hp->_size - 1);
}

AdjustUp 函数用于在插入新元素后,保持最小堆的性质。它接收一个数组 a 和一个元素的索引 child。这个元素通常是刚刚插入到数组中的,并且可能违反了堆的性质(即子节点的值可能小于其父节点的值)。

  • parent 变量用于存储当前 child 元素的父节点索引。
  • 循环会一直执行,直到 child 元素不再违反堆的性质或者 child 元素已经是根节点(此时 child 为 0)。
  • 如果子节点的值小于父节点的值,那么通过 Swap 函数交换这两个元素的位置,并更新 child 和 parent 以继续进行比较。

HeapPush 函数用于将一个新元素 x 插入到堆 hp 中,并保持堆的性质。

  • 首先,使用 assert 确保传入的 Heap 结构体指针 hp 不是 NULL
  • 如果当前堆的 _size 等于 _capacity,意味着堆已经满了,需要扩展空间。通过 realloc 函数为数组分配新的更大的空间。新的空间大小是当前容量的两倍或至少为 4(如果数组尚未分配空间)。
  • 如果 realloc 失败,会打印错误并退出程序。
  • 将新元素 x 插入到数组的末尾,即索引 hp->_size 处,并增加 _size
  • 最后,调用 AdjustUp 函数来调整堆,确保插入新元素后的堆仍然满足最小堆的性质。

删除数据(小堆)

数据的删除的逻辑这里我们不能--,或者++什么的,这些会导致堆变的不是堆

所以我们需要的是逻辑:

1,数组的首元素和尾元素进行交换

2,删除尾元素

3,首元素向下调整(这里看清楚了,这里是选取最小的数值进行交换)

代码实现

//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{
	//我们假设左孩子的数值是最小的
	int chile = (parent * 2) + 1;
	while (chile < size)
	{
		//我们需要判断,右孩子是不是存在,并且筛选出最小的数值
		if (chile + 1 < size && a[chile] > a[chile + 1])
		{
			++chile;
		}
		//交换条件
		if (a[chile] < a[parent])
		{
			Swap(&a[chile], &a[parent]);
			parent = chile;
			chile = (chile * 2) + 1;
		}
		else
		{
			break;
		}
	}
}
// 堆的删除
void HeapPop(Heap* hp)
{
	//首先删除之前不能为null
	assert(hp && hp->_size > 0);
	//删除我们删除的是堆头元素,删除尾部是没有意义的
	//1,进行交换
	//2,删除尾部
	//3,向下排序
	Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
	hp->_size--;
	//传递的参数分别是,数组的头,头下标(parent是需要变化的,所以需要传递一个参数)
	AdjustDown(hp->_a, hp->_size, 0);
}

AdjustDown 函数用于在堆中移除根节点后,重新调整堆以保持堆的性质。它接收一个数组 a,数组的当前大小 size,以及需要向下调整的节点的父节点索引 parent

  • child 变量用于存储当前 parent 节点的左孩子的索引。
  • 循环会一直执行,直到 child 变量超出数组的边界或者当前节点不再违反堆的性质。
  • 如果右孩子存在且右孩子的值小于左孩子的值,那么将 child 更新为右孩子的索引。
  • 如果子节点的值小于父节点的值,那么通过 Swap 函数交换这两个元素的位置,并更新 parent 和 child 以继续进行比较。

HeapPop 函数用于从堆中移除根节点(即堆顶元素),这通常是堆中最大或最小的元素。这个函数适用于最大堆或最小堆,具体取决于堆的性质。

  • 首先,使用 assert 确保传入的 Heap 结构体指针 hp 不是 NULL,并且堆中至少有一个元素。
  • 将堆顶元素(索引 0)与最后一个元素交换位置。这样做的原因是,移除最后一个元素的成本较低,因为它不需要调整堆。
  • 减少 _size,表示堆中的元素数量减少了一个。
  • 调用 AdjustDown 函数,传入数组的头部、新的堆大小和根节点索引(0),以调整堆并保持堆的性质。

取堆顶的数据(小堆)

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp && hp->_size > 0);
	return hp->_a[0];
}
  1. 参数接收:函数接收一个指向 Heap 结构体的指针 hp

  2. 断言检查

    • assert(hp);:确保传入的 hp 不是 NULL。如果 hp 是 NULLassert 将触发断言失败,这通常会导致程序终止。
    • assert(hp->_size > 0);:确保堆不为空(即 _size 大于 0)。如果 _size 不大于 0,说明堆为空,此时没有元素可以返回,assert 同样会触发断言失败。
  3. 返回堆顶元素

    • return hp->_a[0];:返回位于数组 _a 第一个元素的值,这个元素在堆的表示中对应堆顶元素。

堆的数据个数(小堆)

// 堆的数据个数
int HeapSize(Heap* hp)
{ 
	assert(hp);
	return hp->_size;
}
  1. 参数接收:函数接收一个指向 Heap 结构体的指针 hp

  2. 断言检查

    • assert(hp);:确保传入的 hp 不是 NULL。如果 hp 是 NULLassert 将触发断言失败,这通常会导致程序终止。
  3. 返回元素数量

    • return hp->_size;:返回 Heap 结构体中的 _size 成员的值。_size 成员表示堆中当前存储的数据元素的数量。

堆的判空(小堆)

// 堆的判空
int HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->_size == 0;
}
  1. assert(hp);:这行代码使用 assert 宏来确保传入的 hp 不是 NULL。如果 hpNULLassert 将触发一个断言失败,这通常会导致程序终止。assert 是一种运行时检查,用于调试目的,确保代码的正确性。

  2. return hp->_size == 0;:这行代码返回 hp 指向的 Heap 结构体的 _size 成员是否等于 0。_size 成员表示堆中元素的数量。如果 _size 等于 0,表示堆中没有任何元素,函数返回 1(在 C 语言中,非零值被视为真),表示堆为空。如果 _size 不等于 0,函数返回 0,表示堆不为空。

堆的实现代码(小堆)

#include"Heap.h"
//堆的初始化
void HeapInit(Heap* 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;
}
//交换
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向上调整
void AdjustUp(HPDataType* a, int chile)
{
	//获取父母所在位置
	int parent = (chile - 1) / 2;
	while (chile > 0)//这个循环条件不会越界,其余两个循环条件都会导致越界,但是也会正常运行
	{
		if (a[chile] < a[parent])
		{
			//传递地址,指针接收
			Swap(&a[chile], &a[parent]);
			//更新父母和孩子的下标
			chile = parent;
			parent = (chile - 1) / 2;
		}
		else
		{
			break;
		}
	}

	这个循环条件产生了越界,但是	printf("%d ", ps._a[-1]);(特别大) printf("%d ", ps._a[100]);(特别小)
	//while (a[chile] < a[parent])
	//{
	//	//传递地址,指针接收
	//	Swap(&a[chile], &a[parent]);
	//	//更新父母和孩子的下标
	//	chile = parent;
	//	parent = (chile - 1) / 2;
	//}
}

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	//判断空间大小,并且开辟空间
	if (hp->_capacity == hp->_size)
	{
		int newcapacity = hp->_a == 0 ? 4 : hp->_capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("HeapPush:tmp:");
			exit(1);
		}
		hp->_a = tmp;
		hp->_capacity = newcapacity;
	}
	//插入
	hp->_a[hp->_size] = x;
	hp->_size++;
	//向上调整
	AdjustUp(hp->_a, hp->_size - 1);
}
//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{
	//我们假设左孩子的数值是最小的
	int chile = (parent * 2) + 1;
	while (chile < size)
	{
		//我们需要判断,右孩子是不是存在,并且筛选出最小的数值
		if (chile + 1 < size && a[chile] > a[chile + 1])
		{
			++chile;
		}
		//交换条件
		if (a[chile] < a[parent])
		{
			Swap(&a[chile], &a[parent]);
			parent = chile;
			chile = (chile * 2) + 1;
		}
		else
		{
			break;
		}
	}
}
// 堆的删除
void HeapPop(Heap* hp)
{
	//首先删除之前不能为null
	assert(hp && hp->_size > 0);
	//删除我们删除的是堆头元素,删除尾部是没有意义的
	//1,进行交换
	//2,删除尾部
	//3,向下排序
	Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
	hp->_size--;
	//传递的参数分别是,数组的头,头下标(parent是需要变化的,所以需要传递一个参数)
	AdjustDown(hp->_a, hp->_size, 0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp && hp->_size > 0);
	return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{ 
	assert(hp);
	return hp->_size;
}
// 堆的判空
int HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->_size == 0;
}

堆的实现代码(大堆)


大堆
//堆的初始化
void HeapInit(Heap* hp)
{
	hp->_a = NULL;
	hp->_capacity = hp->_size = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->_a);
	hp->_capacity = hp->_size = 0;
}
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向上排序
void AdjustUp(HPDataType* a ,int chile)
{
	int parent = (chile - 1) / 2;
	while (chile > 0)
	{
		if (a[chile] > a[parent])
		{
			Swap(&a[chile], &a[parent]);
			chile = parent;
			parent= (chile - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	if (hp->_capacity == hp->_size)
	{
		int new_capacity = hp->_capacity == 0 ? 4 : hp->_capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * new_capacity);
		if (tmp == NULL)
		{
			perror(" ");
			exit(1);
		}
		hp->_capacity = new_capacity;
		hp->_a = tmp;
	}
	//插入数值
	hp->_a[hp->_size] = x;
	hp->_size++;

	//向上排序
	//首元素地址,孩子所在地址
	AdjustUp(hp->_a, hp->_size - 1);
}
//向下调整(大堆)
void AdjustDown(HPDataType* a, int n, int parent)
{
	int chile = parent * 2 + 1;
	//循环条件不能是父亲,不然会导致越界
	while (chile < n)
	{
		//三个孩子进行比较
		if (chile + 1 < n && a[chile] < a[chile + 1])
		{
			chile++;
		}

		if (a[chile] > a[parent])
		{
			Swap(&a[chile], &a[parent]);
			parent = chile;
			chile = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
// 堆的删除
void HeapPop(Heap* hp)
{
	Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
	hp->_size--;
	//数值的长度,和父亲下标
	AdjustDown(hp->_a, hp->_size, 0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp && hp->_size > 0);
	return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{ 
	assert(hp);
	return hp->_size;
}
// 堆的判空
int HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->_size == 0;
}

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

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

相关文章

浙江大学数据结构MOOC-课后习题-第九讲-排序1 排序

题目汇总 浙江大学数据结构MOOC-课后习题-拼题A-代码分享-2024 题目描述 文章目录 冒泡排序插入排序希尔排序堆排序归并排序 冒泡排序 void buble_Sort() { int A[MAXSIZE];int N;std::cin >> N;for (int i 0; i < N; i)std::cin >> A[i];bool flag false;i…

滑动窗口-java

主要通过单调队列来解决滑动窗口问题&#xff0c;得到滑动窗口中元素的最大值和最小值。 目录 前言 一、滑动窗口 二、算法思路 1.滑动窗口 2.算法思路 3.代码详解 三、代码如下 1.代码如下 2.读入数据 3.代码运行结果 总结 前言 主要通过单调队列来解决滑动窗口问题&#xff…

(免费领源码)java#SSM#mysql第三方物流系统37852-计算机毕业设计项目选题推荐

摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&#xff0c;应用软件的工作…

EasyCode生成的SQL语句中无逗号分隔

EasyCode生成的SQL语句中无逗号分隔 EasyCode是一款非常好用的插件&#xff0c;可以帮助我们生成相关的一些代码&#xff0c;但是在生成SQL对应的xml文件之后&#xff0c;发现语句中多个字段之间没有逗号分隔&#xff0c;而是直接连在了一起。接下来&#xff0c;让我们一起去解…

Kubernetes——Kubectl详解

目录 前言 一、陈述式资源管理方法 二、Kubectl命令操作 1.查 1.1kubectl version——查看版本信息 1.2kubectl api-resources——查看资源对象简写 1.3kubectl cluster-info——查看集群信息 1.4配置Kubectl补全 1.5journalctl -u kubelet -f——查看日志 1.6kubec…

C-数据结构-树状存储基本概念

‘’’ 树状存储基本概念 深度&#xff08;层数&#xff09; 度&#xff08;子树个数&#xff09; 叶子 孩子 兄弟 堂兄弟 二叉树&#xff1a; 满二叉树&#xff1a; 完全二叉树&#xff1a; 存储&#xff1a;顺序&#xff0c;链式 树的遍历&#xff1a;按层遍历&#xff0…

Qt for android 串口库使用

简介 由于Qt for android并没有提供android的串口执行方案&#xff0c;基于需要又懒得自己去造轮子&#xff0c; 使用开源的 usb-serial-for-android 库进行串口访问读写。 如果有自己的需要和库不满足的点&#xff0c;可以查看库的底层调用的Android相关API C/C 串口库 对应…

驱动开发:内核MDL读写进程内存

100编程书屋_孔夫子旧书网 MDL内存读写是最常用的一种读写模式,通常需要附加到指定进程空间内然后调用内存拷贝得到对端内存中的数据,在调用结束后再将其空间释放掉,通过这种方式实现内存读写操作,此种模式的读写操作也是最推荐使用的相比于CR3切换来说,此方式更稳定并不会…

Kruskal算法求最小生成树(加边法)

一、算法逻辑 想要轻松形象理解Kruskal算法的算法逻辑&#xff0c;视频肯定比图文好。 小编看过很多求相关的教学视频&#xff0c;这里选出一个我认为最好理解的这一款安利给大家。 因为他不仅讲解细致&#xff0c;而且还配合了动画演示&#xff0c;可以说把一个抽象的东西讲…

【软件设计师】网络安全

1.网络安全基础信息 网络安全的五个基本要素&#xff1a; 机密性&#xff1a;确保信息不暴露给未授权的实体或进程 完整性&#xff1a;只有得到允许的人才能修改数据&#xff0c;并且能判断出数据是否已被修改 可用性&#xff1a;得到授权的实体在需要时可以访问数据&#xff0…

Web安全:文件上传漏洞详解,文件上传漏洞原理、绕过方式和防御方案。

「作者简介」&#xff1a;2022年北京冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础对安全知识体系进行总结与归纳&#xff0c;著作适用于快速入门的 《网络安全自学教程》&#xff0c;内容涵盖系统安全、信息收集等…

【Mac】 CleanMyMac X for mac V4.15.2中文修复版安装教程

软件介绍 CleanMyMac X是一款为Mac设计的优秀软件&#xff0c;旨在帮助用户优化其设备的性能并提供清理和维护功能。以下是 CleanMyMac X的一些主要功能和特点&#xff1a; 1.系统性能优化&#xff1a;软件可以扫描和修复潜在的性能问题&#xff0c;包括无效的登录项、大文件…

【设计模式】创建型-抽象工厂模式

前言 在软件开发领域&#xff0c;设计模式是一种被广泛接受的解决方案&#xff0c;用于解决特定问题并提供可维护和可扩展的代码结构。抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;是其中之一&#xff0c;它提供了一种方法来创建一系列相关或相互依赖的对象…

(三)MobaXterm、VSCode、Pycharm ssh连接服务器并使用

背景&#xff1a;根据前两篇文章操作完成后&#xff0c; 手把手教学&#xff0c;一站式安装ubuntu及配置服务器-CSDN博客 手把手教学&#xff0c;一站式教你实现服务器&#xff08;Ubuntu&#xff09;Anaconda多用户共享-CSDN博客 课题组成员每人都有自己的帐号了&#xff0…

上海亚商投顾:沪指震荡反弹 半导体产业链午后爆发

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指昨日震荡反弹&#xff0c;尾盘涨幅扩大至1%&#xff0c;深成指、创业板指同步上行&#xff0c;科创50指数…

实时直播技术革新:视频汇聚管理EasyCVR平台助力景区游览体验全面升级

自年初以来&#xff0c;各地文旅热点不断。“温暖驿站”“背诗免票”“王婆说媒”等等&#xff0c;吸引了不少人奔赴远方。2024年在国人消费意愿榜上&#xff0c;旅游又一次占据榜首的位置&#xff0c;有三分之一以上的人&#xff0c;今年会在旅游方面增加消费。中国旅游的发展…

【已解决】./start-base.sh: line 5: $‘\r‘: command not found

问题&#xff1a;在linux下运行启动服务的脚本&#xff0c;提示很多‘\r’不可用。 原因&#xff1a;windows下编辑的文件&#xff0c;放在linux下运行&#xff0c;文件格式有问题&#xff0c;需要转换。 解决方法&#xff1a; 1、用vim编辑器打开文件 vim 文件名 2、进入…

Matplotlib(可视化)小案例

一.认识&#xff1a; Matplotlib是一个Python 2D绘图库&#xff0c;它可以在各种平台上以各种硬拷贝格式和交互式环境生成出具有出版品质的图形。Matplotlib可用于Python脚本&#xff0c;Python和IPython shell&#xff0c;Jupyter笔记本&#xff0c;Web应用程序服务器和四个图…

K8S-pod资源 探针

一.pod资源限制&#xff1a; 对pod资源限制原因&#xff1a;高并发占用所有的cpu资源、内存资源、会造成雪崩 当定义 Pod 时可以选择性地为每个容器设定所需要的资源数量。 最常见的可设定资源是 CPU 和内存大小&#xff0c;以及其他类型的资源。 方式&#xff1a; 对pod做…

二叉树的实现(递归实现)

前言&#xff1a;本文讲解通过递归的方式实现二叉树的一些基本接口。 目录 通过左右子树的方式实现二叉树&#xff1a; 二叉树的遍历&#xff1a; 求二叉树结点的个数&#xff1a; 二叉树所有节点的个数&#xff1a; 二叉树叶子节点的个数&#xff1a; 求第k层节点的节点…