数据结构初阶:树与二叉树(1)——堆

news2024/10/6 3:36:45

许久没发博客,在这里跟各位看客道声久等了~

冬至已至,各位有没有吃上热乎的饺子呢

下面给各位奉上承载着满满干货的饺子吧:

目录

一、树

        1. 树的结构定义

        2. 树的相关概念

        3. 树的表示

                孩子兄弟表示法

二、二叉树

        1. 二叉树的结构定义

        2. 特殊的二叉树

        3. 二叉树的性质

        4. 二叉树的存储结构

                顺序存储

                链式存储

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

        1. 堆的概念及结构

        2. 定义堆结构

        3. 初始化堆

        4. 销毁堆

        5. 向堆中插入数据

        6. 删除堆顶元素

        7. 取堆顶元素

        8. 堆构建的方式

                8.1 向上调整构建堆

                8.2 向下调整构建堆

        9. 构建堆

        10. 堆排序

        11. TOP-K问题

四、堆实现总代码


本期博客将会对树和二叉树进行全面的详解:

一、树

在说二叉树之前,首先要弄清树,因为二叉树本来就是树的特殊类型。

        1. 树的结构定义

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

如图:A~L都是树的节点 

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

在上图中A就是根节点。

注:树形结构中,子树之间不能有交集,否则就不是树形结构

如下图这些都不是树:

另外一棵有n个节点的树有n-1条边

        2. 树的相关概念

下面我拿这个树举例来介绍树的相关概念:

节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的度为6。

叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点。

非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点。

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:  A是B的父节点。

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点。

兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点。

树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。

树的高度或深度:树中节点的最大层次; 如上图:树的高度为4。

堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点。

节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先。

子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙 。

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

        3. 树的表示

我们已经知道树的结构了,接下来我们如何用代码来表示它们呢?

我们想到树无非就是父节点和孩子节点之间的关系,那我们就可以使用结构体来储存一个节点的所以孩子节点就行:

typedef int DataType;
struct TreeNode
{
	//数据
	DataType data;
	//所有的孩子节点
	struct TreeNode* child1;
	struct TreeNode* child2;
	struct TreeNode* child3;
	//...
};

但是我们如果不知道这棵树的度,那我们到底要定义多少个TreeNode*类型的孩子节点呢?

显然这种方法是不合理的。

那使用指针数组或者定义一个指针顺序表来储存节点的所以孩子节点呢?

例如:

#define N 10
typedef int DataType;
struct TreeNode
{
	//数据
	DataType data;
	//指针数组
	struct TreeNode* children[N];
};
typedef int DataType;
typedef struct TreeNode* SLDataType
struct TreeNode
{
	//数据
	DataType data;
	//指针顺序表
	Seqlist children;
};

但是这样子做太过于复杂

下面有一种非常简便的方式来表示树:

                孩子兄弟表示法

即定义一个结构体节点,该结构体只有两个指针,一个指向该节点的第一个孩子节点,另一个指向其兄弟节点。这样就可以将整个树表示出来:

typedef int DataType;
struct TreeNode
{
	//数据
	DataType data;
	struct TreeNode* child;//指向第一个孩子节点
	struct TreeNode* brother;//指向其兄弟节点
};

我们使用该方法表示一下这棵树:

该树使用孩子兄弟表示法表示的结构图如下: 

二、二叉树

在所以树类型中二叉树是最常见,使用最广泛的树。

        1. 二叉树的结构定义

二叉树即度为二的树,即树的每个节点的度不大于二

 上图就是一个二叉树。

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

        2. 特殊的二叉树

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

 

完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树(2^(k-1) ≤ n ≤ 2^k-1),当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树(就是该二叉树的K-1层及以上的结构是个满二叉树,第K层的节点可以不满但是一定要连续)。 要注意的是满二叉树是一种特殊的完全二叉树。

        3. 二叉树的性质

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

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

3. 对任何一棵二叉树, 如果度为0的叶结点个数为n , 度为2的分支结点个数为x ,则有 x=n+1

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

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

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

        > 若2i+1=n否则无左孩子

        若2i+2=n否则无右孩子

        4. 二叉树的存储结构

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

                顺序存储

        顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆(一种完全二叉树)才会使用数组来存储,关于堆在下面会专门讲解。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

                链式存储

        二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面的高阶数据结构如红黑树等会用到三叉链。

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

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

        1. 堆的概念及结构

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

堆的性质:

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

堆总是一棵完全二叉树

由上图我们可以发现: 

孩子和父亲下标的关系:

leftchild = parent*2+1 (都为奇数)

rightchild = parent*2+2 (都为偶数)

parent = (child-1)/2

注:任何一个数组表示为一个完全二叉树不一定是堆

下面我们进入到代码实战:

        2. 定义堆结构

想要实现堆我们首先需要确定好怎么去定义一个堆的结构

由于使用数组来实现我们可以直接使用一个顺序表来定义堆结构:

typedef int HPDataType;//定义数据类型
//定义堆结构
typedef struct Heap
{
	HPDataType* data;//数据
	int size;//记录所存储的有效数据个数
	int capacity;//记录可以存储数据个数的容量
}HP;

        3. 初始化堆

下面我们来对堆进行初始化:

void HeapInit(HP* php)
{
	assert(php);//传入的指针不能为空
	php->capacity = php->size = 0;
	php->data = NULL;
}

        4. 销毁堆

void DestoryHeap(HP* php)
{
	assert(php);//传入的指针不能为空
	free(php->data);//释放数据空间
	php->data = NULL;
	php->capacity = php->size = 0;
}

        5. 向堆中插入数据

我们本次实现的是大堆,所以向堆中插入数据时要将数据于其祖先节点的数据进行比较大小,让顺序表一直是以堆形式存储的(想要实现小堆只需修改一下AdjustUp函数中的判断条件即可):

void Swap(HPDataType* parent, HPDataType* child)
{
	HPDataType temp = *parent;
	*parent = *child;
	*child = temp;
}
void AdjustUp(HPDataType* data,int child)
{
	int parent = (child - 1) / 2;
	while (data[child] > data[parent] && child > 0)//如果孩子节点大于父亲节点就进行交换调整,注意如果想实现小堆的话只需要将前一个>改成<即可
	{
		Swap(&data[parent], &data[child]);//交换孩子和父亲节点
		child = parent;
		parent = (child - 1) / 2;
	}
}
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->capacity == php->size)//判断是否需要扩容
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* new = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (new == NULL)//判断扩容是否成功
		{
			perror("realloc");
			exit(-1);
		}
		php->a = new;
		php->capacity = newcapacity;
	}
	int temp = php->size;
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);//对插入的数据进行调整
}

        6. 删除堆顶元素

我们要想删除堆顶元素是不是可以向顺序表一样头删完草草了事呢?

我们不妨试一试:

 由上图可以看到头删数据会改变整个数据的结构,可能使其不是堆

为了使数组在删除头元素后还能具有大堆的性质,我们可以先将头元素与尾元素进行互换,再将头元素数据与其孩子节点进行比大小,选取大的孩子进行位置交换,直到孩子节点的数据都小于其节点数据或者自己成为叶子节点时结束。(按小堆调整只需改变AdjustDown函数中的判断条件)

下面是该方法的逻辑模拟图:

 代码实现:

void AdjustDown(HPDataType* data, int n,int parent)
{
	int chlid = parent * 2 + 1;//找到其左孩子
	while (chlid < n)//防止数组越界
	{
		if (data[chlid + 1] > data[chlid] && chlid + 1 < n)//判断左右孩子的大小,chlid + 1 < n要防止存在左孩子而无右孩子时的数组越界(想要按小堆调整需将前一个>改为<号)
		{
			chlid += 1;
		}
		if (data[chlid] > data[parent])//(想要按小堆调整需将 > 改为 < 号)
		{
			Swap(&data[chlid], &data[parent]);//将大的孩子元素与其替换
			//继续向下比较替换
			parent = chlid;
			chlid = parent * 2 + 1;
		}
		else//如果孩子元素都没有其父元素大则直接跳出
		{
			break;
		}
	}
}

void HeapPop(HP*php)
{
	assert(php);//传入的指针不能为空
	assert(php->size > 0);//堆中有效数据个数不能为0
	Swap(&php->a[php->size - 1], &php->a[0]);//交换首元素与尾元素
	php->size--;//删除尾元素
	AdjustDown(php->a, php->size, 0);//进行调整
}

        7. 取堆顶元素

HPDataType HeapTop(HP* php)
{
	assert(php);//传入的指针不能为空
	assert(php->size > 0);//堆中有效数据个数不能为0
	return php->a[0];//返回堆顶元素
}

        8. 堆构建的方式

在这里我们提一下堆构建的方式为下面构建堆做一个准备:

现在我们有一个随机数组怎么将其调整成堆呢?

下面有两种方法供大家参考:

                8.1 向上调整构建堆

我们可以将数组看成一个完全二叉树,再从第二层开始向下一个一个元素进行向上调整,如果该元素对于它的父节点的元素不满足大(小)堆的条件,就将其与其父节点节点元素进行交换,一直到满足堆的条件为止,再去调整下一个元素,直到最后一个元素被调整完毕:

下面用大堆来进行举例:

先看第二层第一个元素,比其父节点小满足大堆条件,再看第二层第二个元素,发现比其父节点大不满足大堆条件,因此进行交换:

 接着看到第三层第一个元素,发现比其父节点大不满足大堆条件,因此进行交换:

交换之后发现其满足大堆条件,再看到第三层第二个元素比其父节点小满足大堆条件

接着看到第三层第三个元素,发现比其父节点大不满足大堆条件,因此进行交换:

交换之后满足大堆条件,再看到第三层第四个元素比其父节点小满足大堆条件

接着看到第四层第一个元素,发现比其父节点大不满足大堆条件,因此进行交换:

交换之后满足大堆条件

接着看到第四层第二个元素,发现比其父节点大不满足大堆条件,因此进行交换:

交换之后发现比其父节点大不满足大堆条件,因此继续进行交换:

交换之后发现比其父节点大不满足大堆条件,因此继续进行交换:

交换之后满足大堆条件

最后一个元素已调整完毕,大堆构建已完成

上代码:

void Heap_createUp(int* a, int n)
{
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);//向上调整一直到最后一个元素
	}
}

很好理解,向上调整进行堆排序的时间复杂度为:O(N*㏒⑵N)

                8.2 向下调整构建堆

我们可以将数组看成一个完全二叉树,再从最后一个节点的父节点开始调整,如果该节点的元素对于它的孩子节点的元素不满足大(小)堆的条件,就将其与其大的一个孩子点节点元素进行交换,接着对其孩子节点进行调整直到满足大堆条件为止,再找到上一个节点进行如上调整,直到第一个元素被调整完为止:

下面用大堆来进行举例:

找到最后一个节点的父节点(第三层第一个),发现其与右孩子不满足大堆条件,因此进行交换:

调整完毕发现满足大堆条件

再看到其节点的上一个节点(第二层第二个)发现其孩子节点元素都比其节点元素小,满足大堆条件

接着找到再上一个节点(第二层第一个)发现其与左孩子不满足大堆条件,因此进行交换:

 调整完毕发现不满足大堆条件,再将其孩子节点与其较大的孩子节点进行交换:

调整完毕发现满足大堆条件

再看到其节点的上一个节点(第一层第一个)发现不满足大堆条件,再将其孩子节点与其较大的孩子节点进行交换:

  调整完毕发现不满足大堆条件,再将其孩子节点与其大的孩子节点进行交换:

调整完毕发现不满足大堆条件,再将其孩子的孩子节点与其大的孩子节点进行交换:

调整完毕发现满足大堆条件

第一个元素调整完毕,大堆排序已完成

上代码:

void Heap_createDown(int* a, int n)
{
	//向下调整一直到第一个
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)//n-1是最后一个元素的物理位置((n-1)-1)/2是其父节点的物理位置
	{
		AdjustDown(a, n, i);//向下调整
	}
}

下面我们对向下调整进行堆排序进行时间复杂度的推导:

我们假设有一个h层的完全二叉树:

每一层的节点数为:2^(这一层数-1)

 我们从倒数第二层的最后一排的父节点开始调整时,每个节点最多调整一次

从倒数第三层的倒数最后第二排的父节点开始调整时,每个节点最多调整两次

从倒数第四层的倒数最后第三排的父节点开始调整时,每个节点最多调整三次

……

以此类推,从第x层的节点开始调整时,每个节点最多调整x-1次

如此一来,第x层总调整数为(x-1)*2^(x-1)

所以h层的二叉树最多总调整次数为F(h)=2^(h-2)*1+2^(h-3)*2+···+2^1*(h-2)+2^0*(h-1)

一看这公式是等差等比数列的组合,我们拿出高中的看家本领错位相减法

2*F(h)=2^(h-1)*1+2^(h-2)*2+···+2^2*(h-2)+2^1*(h-1)

2*F(h)-F(h)=2^(h-1)+2^(h-2)+2^(h-3)+···+2^2+2^1-(h-1)

                 =2^(h-1)+2^(h-2)+2^(h-3)+···+2^2+2^1+1+h

                 =2^(h-1)+2^(h-2)+2^(h-3)+···+2^2+2^1+2^0-h

                 =2^h-1-h

刚好数的总节点数N=2^h-1

那就有F(N)=N-㏒⑵(N+1)

所以向下调整构建堆的时间复杂度为:O(N)=N

比向上调整进行堆排序有优势哦~

        9. 构建堆

我们已经有了HeapPush函数来向堆一个个插入数据,那直接插入一个数组来构建一个堆不更加方便吗?

构建一个堆我们可以用两种方式:

(1)复用之前的插入函数HeapPush(这种方法效率不高,就是向上调整建堆)

void HeapCreate(HP* php, HPDataType* data, int n)
{
	assert(php);//传入的指针不能为空
	HeapInit(php);//初始化堆
	for (int i = 0; i < n; i++)
	{
		HeapPush(php, data[i]);//复用HeapPush向堆中一个个插入数据
	}
}

 (2)直接将整个数组拷贝插入空间,再向下调整建堆

void HeapCreate(HP* php, HPDataType* data, int n)
{
	assert(php);//传入的指针不能为空
	//开辟空间
	HPDataType* newSpace = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (newSpace == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	//拷贝数据
	php->a = newSpace;
	php->capacity = php->size = n;
	memcpy(php->a, data, n * sizeof(HPDataType));
	//向下调整建堆
	for (int i = (n - 2) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, n, i);
	}
}

        10. 堆排序

这里给一个随机数组,要求对其升序排序,我们可以先建堆。

那建大堆还是小堆呢?

当然是大堆了,如果建小堆每次进行堆调整时都会将大的元素调至前面,如果不另外开辟空间,将不能很好的就行下一次的调整。

我们采用向下调整建堆的方式进行堆排序:

先给一随机数组,我们可以将其先用向下调整的方式构建一个大堆,再将第一个最大的元素与堆尾元素交换,接着将最大元素移除堆再进行调整,以此来得到一个升序的数组。

代码如下:

void HeapSort(int* a, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)//n-1是最后一个元素的物理位置((n-1)-1)/2是其父节点的物理位置
	{
		AdjustDown(a, n, i);//向下调整
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[end], &a[0]);//每一次调整之后将最大的元素挪到堆的最后面
		AdjustDown(a,end,0);
		end--;
	}
}

如果想要降序排列就构建一个小堆来进行排序。

堆排序的时间复杂度为:O(N*㏒⑵N)

非常快哦~

        11. TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。 

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

假如我们现在想要找到100亿个整数中,取出前K个最大的数,如果我们直接对100亿个整数进行排序至少需要40G的内存,这会造成空间上巨大的浪费。

我们可以先取K个数建立一个小堆,再将后100亿-K个数依次与堆顶元素相比较,如果比堆顶元素大就将其替换后重新向下调整为一个小堆,再接着与下一个数相比,这样最终就可以找到前K个最大的整数了。(节约了大量的空间)

该方法的时间复杂度为:O(N*㏒⑵K)

空间复杂度为O(K)

四、堆实现总代码

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
typedef int HPDataType;//定义数据类型
//定义堆结构
typedef struct Heap
{
	HPDataType* a;//数据
	int size;//记录所存储的有效数据个数
	int capacity;//记录可以存储数据个数的容量
}HP;
void HeapInit(HP* php)
{
	assert(php);//传入的指针不能为空
	php->capacity = php->size = 0;
	php->a = NULL;
}
void DestoryHeap(HP* php)
{
	assert(php);//传入的指针不能为空
	free(php->a);//释放数据空间
	php->a = NULL;
	php->capacity = php->size = 0;
}
void Swap(HPDataType* parent, HPDataType* child)
{
	HPDataType temp = *parent;
	*parent = *child;
	*child = temp;
}
void AdjustUp(HPDataType* data,int child)
{
	int parent = (child - 1) / 2;
	while (data[child] > data[parent] && child > 0)//如果孩子节点大于父亲节点就进行交换调整,注意如果想实现小堆的话只需要将前一个>改成<即可
	{
		Swap(&data[parent], &data[child]);//交换孩子和父亲节点
		child = parent;
		parent = (child - 1) / 2;
	}
}
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->capacity == php->size)//判断是否需要扩容
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* new = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (new == NULL)//判断扩容是否成功
		{
			perror("realloc");
			exit(-1);
		}
		php->a = new;
		php->capacity = newcapacity;
	}
	int temp = php->size;
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);//对插入的数据进行调整
}
void AdjustDown(HPDataType* data, int n,int parent)
{
	int chlid = parent * 2 + 1;//找到其左孩子
	while (chlid < n)//防止数组越界
	{
		if (data[chlid + 1] > data[chlid] && chlid + 1 < n)//判断左右孩子的大小,chlid + 1 < n要防止存在左孩子而无右孩子时的数组越界(想要按小堆调整需将前一个>改为<号)
		{
			chlid += 1;
		}
		if (data[chlid] > data[parent])//(想要按小堆调整需将 > 改为 < 号)
		{
			Swap(&data[chlid], &data[parent]);//将大的孩子元素与其替换
			//继续向下比较替换
			parent = chlid;
			chlid = parent * 2 + 1;
		}
		else//如果孩子元素都没有其父元素大则直接跳出
		{
			break;
		}
	}
}
void HeapPop(HP*php)
{
	assert(php);//传入的指针不能为空
	assert(php->size > 0);//堆中有效数据个数不能为0
	Swap(&php->a[php->size - 1], &php->a[0]);//交换首元素与尾元素
	php->size--;//删除尾元素
	AdjustDown(php->a, php->size, 0);//进行调整
}
HPDataType HeapTop(HP* php)
{
	assert(php);//传入的指针不能为空
	assert(php->size > 0);//堆中有效数据个数不能为0
	return php->a[0];//返回堆顶元素
}
//void HeapCreate(HP* php, HPDataType* data, int n)
//{
//	assert(php);//传入的指针不能为空
//	HeapInit(php);//初始化堆
//	for (int i = 0; i < n; i++)
//	{
//		HeapPush(php, data[i]);//复用HeapPush向堆中一个个插入数据
//	}
//}
void HeapCreate(HP* php, HPDataType* data, int n)
{
	assert(php);//传入的指针不能为空
	//开辟空间
	HPDataType* newSpace = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (newSpace == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	//拷贝数据
	php->a = newSpace;
	php->capacity = php->size = n;
	memcpy(php->a, data, n * sizeof(HPDataType));
	//向下调整建堆
	for (int i = (n - 2) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, n, i);
	}
}
//void Heap_createUp(int* a, int n)
//{
//	for (int i = 1; i < n; i++)
//	{
//		AdjustUp(a, i);//向上调整一直到最后一个元素
//	}
//}
void Heap_createDown(int* a, int n)
{
	//向下调整一直到第一个
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)//n-1是最后一个元素的物理位置((n-1)-1)/2是其父节点的物理位置
	{
		AdjustDown(a, n, i);//向下调整
	}
}
void HeapSort(int* a, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)//n-1是最后一个元素的物理位置((n-1)-1)/2是其父节点的物理位置
	{
		AdjustDown(a, n, i);//向下调整
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[end], &a[0]);//每一次调整之后将最大的元素挪到堆的最后面
		AdjustDown(a,end,0);
		end--;
	}
}


本期的博客到这里就结束了,感谢各位看官的支持,后面会加快更新的速度,请大家不要走开哦~

本期代码量较多,如有纰漏,还请各位大佬不吝赐教。

最后祝大家圣诞快乐,一路平安~

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

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

相关文章

自己整理的vue实现生成分享海报(含二维码),看着网上的没实现

大家好&#xff0c;我是雄雄。 前言 相信大家在许多的场景下&#xff0c;看到过这样的案例。 当我们在某购物app上看好一件商品&#xff0c;想分享给别人时&#xff0c;app会给我们生成一张海报&#xff0c;我们将其保存在手机里面转发给其他人达到分享。当我们逛CSDN的时候&…

【Android弹窗】Dialog Bottom Translate Animation

文章目录1. 系统Dialog2. 自定义Dialog3. 其余1. 系统Dialog 首先先来使用回顾一下系统的Dialog弹窗&#xff0c;这里使用比较简单的AlertDialog为例&#xff1a; AlertDialog.Builder builder new AlertDialog.Builder(this).setTitle("弹窗标题").setMessage(&q…

【小程序】全局数据共享

目录 全局数据共享 1. 什么是全局数据共享 2. 小程序中的全局数据共享方案 全局数据共享 - MobX 1. 安装 MobX 相关的包 2. 创建 MobX 的 Store 实例 3. 将 Store 中的成员绑定到页面中 4. 在页面上使用 Store 中的成员 ​5. 将 Store 中的成员绑定到组件中 6. 在组件中…

【分布式技术专题】「架构实践于案例分析」盘点一下分布式模式下的服务治理和监控优化方案

什么是服务治理&#xff1f; 相信每一个软件公司&#xff08;企业&#xff09;都希望可以确保开发及项目运行流程可以顺利&#xff0c;但是如果要完美完结那么需要其中会有很多的因素存在。包括&#xff0c;最佳实践、架构原则、服务治理以及其他决定性的因素。而其中服务治理…

新冠确诊阳性的第七篇博客,Linux动态监控系统

新冠确诊阳性的第七篇博客&#xff0c;Linux动态监控系统1.动态监控进程2.动态监控网络1.动态监控进程 top命令和ps相似&#xff0c;都可以用来显示系统正在执行的进程&#xff0c;top和ps的最大不同之处就是在于top在执行一段时间可以更新正在运行的进程&#xff08;也可以理…

JavaScript:优先级队列的实现案例

优先级队列的定义&#xff1a;优先级队列&#xff08;priority_queue&#xff09;其实&#xff0c;不满足先进先出的条件&#xff0c;更像是数据类型中的“堆”。优先级队列每次出队的元素是队列中优先级最高的那个元素&#xff0c;而不是队首的元素。这个优先级可以通过元素的…

架构设计(九):估算

架构设计&#xff08;九&#xff09;&#xff1a;估算 作者&#xff1a;Grey 原文地址&#xff1a; 博客园&#xff1a;架构设计&#xff08;九&#xff09;&#xff1a;估算 CSDN&#xff1a;架构设计&#xff08;九&#xff09;&#xff1a;估算 估算在系统设计中非常重…

还在用定时器吗?借助 CSS 来监听事件

平时工作中很多场合都要用到定时器,比如延迟加载、定时查询等等,但定时器的控制有时候会有些许麻烦,比如鼠标移入停止、移出再重新开始。这次介绍几个借助 CSS 来更好的控制定时器的方法,一起了解一下吧,相信可以带来不一样的体验 一、hover 延时触发 有这样一个场景,在…

C++ WebSockSet服务器解决方案

使用C实现WebSocket服务器是为了解决Web直接访问本地应用程序最佳解决方案。解决云访问硬件最经济的方案或增加了一种解决方法。方案选用开源uWebSockets库。 开发工具选择Visual C 2017,所有源码或工程都用它编译或创建。 1. 准备工作 下载以下第三方库最新版源码并且编译供…

FMOC-PEG-COOH,FMOC-PEG-acid,芴甲氧羰基-聚乙二醇-羧基试剂供应

英文名称&#xff1a;FMOC-PEG-COOH&#xff0c;FMOC-PEG-acid 中文名称&#xff1a;芴甲氧羰基-聚乙二醇-羧基 蛋白质、肽和其他材料通过氨基酸或其他酸活性化学组&#xff0c;增加溶解度和稳定性&#xff0c;降低免疫原性&#xff1b;药物修饰或缓释药物研发&#xff0c;新…

复旦MBA第二学位:畅享顶尖国际商科资源,探索全球发展新可能

自2009年以来&#xff0c;复旦MBA项目一直致力于与顶级院校开展合作&#xff0c;拓宽学生的国际视野。目前&#xff0c;复旦MBA项目与三所国际顶尖合作院校达成了第二学位项目的合作&#xff1a;美国麻省理工学院斯隆管理学院管理学硕士学位(Master of Science in Management S…

【OpenCV-Python】教程:7-7 PCA

OpenCV Python PCA 【目标】 利用 pca 来计算目标的方向 【理论】 Introduction to Principal Component Analysis (PCA) PCA&#xff08;主成分分析&#xff09;是提取最重要特征的统计过程。 假设你有一组2D点&#xff0c;如上图所示。每个维度都对应于您感兴趣的特性。…

Blender——苹果的材质绘制

效果图 前言 在进行纹材质的绘制之前&#xff0c;首先要具有苹果的三维模型和进行苹果纹理绘制。 关于苹果的建模请参考&#xff1a;Blender——“苹果”建模_行秋的博客 关于苹果的纹理绘制请参考&#xff1a;Blender——苹果纹理绘制_行秋的博客 书接上回&#xff0c;由Te…

FMOC-PEG-acid,FMOC-PEG-COOH,芴甲氧羰基PEG羧基用于探究新型材料

用于探究新型材料的化学试剂芴甲氧羰基-聚乙二醇-羧基&#xff0c; 其英文名为FMOC-PEG-acid&#xff08;FMOC-PEG-COOH&#xff09;&#xff0c;它所属分类为Boc/Fmoc protected amine PEG Carboxylic acid PEG。 芴甲氧羰基peg羧基试剂的分子量均可定制&#xff0c;有&#…

使用nginx实现自定义大小预览缩略图,http_image_filter模块的安装使用

使用nginx实现自定义大小预览缩略图&#xff0c;http_image_filter模块的安装使用注意事项服务器配置方法安装模块备份http_image_filter模块用以调用配置文件调整引入模块修改配置文件设置访问入口随后重启nginx服务访问请求测试注意事项 本预览图功能使用的是nginx的http_im…

哦,原来事务传播是这样

引言 ​ 在介绍正文之前&#xff0c;让我们先一起来看下这段代码&#xff1a; Transactionalpublic void createProduct(Long skuId, Integer number, Long operatorUcid) {// 插入商品信息recordProduct(skuId, number);// 插入商品操作记录日志recordProductOperateLogClass…

关于操作数组元素的实际应用

sort()升序、降序排序方法应用 sort()排序方式原理&#xff1a;当sort()传入函数中的第一个参数a位于第二个参数b之前&#xff0c;则返回一个负数&#xff0c;相等则返回0&#xff0c;a位于b之后则返回正数。 比如&#xff0c;当要做升序排序时&#xff0c;我们需要想到前面的…

【SpringMVC】常用注解

1.RequestParam 1.1 使用说明 作用: 把请求中指定名称的参数给控制器中的形参赋值 属性&#xff1a; ​ **value&#xff1a;**请求参数中的名称 ​ **required&#xff1a;**请求参数是否必须提供此参数。默认值&#xff1a;true&#xff0c;表示必须提供&#xff0c;如果…

Linux下tree命令C/C++实现(以树状格式列出目录的内容)

在UNIX/LINUX系统中&#xff0c;tree是一个递归目录列表程序&#xff0c;它生成文件的深度缩进列表。在没有参数的情况下&#xff0c;树将列出当前目录中的文件。当给定目录参数时&#xff0c;树依次列出在给定目录中找到的所有文件或目录。列出找到的所有文件和目录后&#xf…

机器学习从零到入门 逻辑回归详解

逻辑回归详解 从零开始 从理论到实践一、逻辑回归的理解1.1、字面含义1.2、引申1.2.1、阶跃函数的引入1.2.2、可导的阶跃函数 - Logistic函数1.2.3、Logistic回归1.2.4、回归系数的求解 - 极大似然估计二、sklearn的使用参考一、逻辑回归的理解 前面介绍了线性回归及其衍生回归…