二叉树中堆的实现

news/2024/5/20 7:38:34 标签: 数据结构, 算法,

完全二叉树

满二叉树:二叉树每个节点的度都达到最大值(2),由此可有等比求和计算出节点总数:2^k-1

完全二叉树:除了最后一层。前面节点的度都满了,最后一层可以不满,但是必须从左至右连续,所以满二叉树也是完全二叉树的一种特殊形式,其总结点的范围在:2^(k-1) ~ 2^k-1

而满二叉树也是完全二叉树的一种特殊形式 

 二叉树的顺序结构与实现

其实就是一种特殊的二叉树结构(完全二叉树),但是实现排序相较于冒泡排序是相当快的,并且也可以迅速的找出一数据中的前几个最值,而恰恰就是用顺序表数组来实现的,所以就先来了解一下二叉树顺序结构。

 二叉树的顺序结构

完全二叉树的形式可以很好的与数组契合,因为节点的连续性,所以用顺序表结构的数组存储完全二叉树是十分合适的,并且可以直接通过数组下标的关系找到对应的子节点和父节点。

 而对于二叉树的其他形式是不适合用数组来存储的,可能会复杂并且存在空间的浪费。

的概念与结构 

首先我们要知道,的结构是完全二叉树,并且推也分为两种:大(根),小(根)

 而也只有大和小两种形式,其他不满足条件的都不属于

大(根):树中的任意一个节点存储的值都小于等于其父节点存储的的值

小(根 ):树中的任意一个节点存储的值都大于等于其父节点存储的的值

的基本实现

 是可以用顺序表结构的数组来实现的,所以可以借鉴前面栈的实现方法,而下面介绍的是大(根)的写法:

需要实现的函数

typedef struct Heap
{
	int sz;
	int capacity;
	int* arr;

}Heap;

void Init(Heap* hp);//初始化
void Push(Heap* hp, int x);//增数据
void Pop(Heap* hp);//删数据
int GetTop(Heap* hp);//得到根数据
void Destroy(Heap* hp);//空间释放
void Init(Heap* hp)
{
	hp->arr = (int*)malloc(sizeof(int) * 3);
	hp->capacity = 3;
	hp->sz = 0;//指向实际数据的下一个节点
}

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 =  *p2;
	*p2 = tmp;
}

void Adjust_up(int* arr,int child)
{
	while (child > 0)
	{
		int parent = (child - 1) / 2;//不可以作为while的条件,child==0时
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
		}
		else
			return;

	}
}
void Push(Heap* hp, int x)
{
	if (hp->sz == hp->capacity)
	{
		hp->capacity *= 2;
		int* tmp = (int*)realloc(hp->arr, sizeof(int) * hp->capacity);
		assert(tmp);
		hp->arr = tmp;
	}
	hp->arr[hp->sz] = x;
	
	hp->sz++;
	//向上调整保证是
	Adjust_up(hp->arr,hp->sz-1);
}

void Adjust_down(int* arr,int last)
{
	int parent = 0;
	int child = parent * 2 + 1;//假设较大值是左孩子
	while (child<last)
	{
		if (child + 1 < last && arr[child] < arr[child + 1])//先防止越界,再验证较大值(只有一个左孩子时可能会越界)
			child += 1;

		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			return;
	}
}
void Pop(Heap* hp)//删除根节点的数据
{
	assert(hp->sz);//保证有数据
	//防止改变的父子大小关系,保证其他数据的关系不变,则选择尾元素换到头
	hp->arr[0] = hp->arr[hp->sz - 1];
	hp->sz--;

	//向下调整
	Adjust_down(hp->arr,hp->sz);
}

int GetTop(Heap* hp)
{
	assert(hp->sz);
	return hp->arr[0];
}

void Destroy(Heap* hp)
{
	free(hp->arr);
	 
	hp->capacity = hp->sz = 0;
}

这里其实看一下Adjust_down 和 Adjust_up 这两个函数的实现就行了,主要就是插入数据时要用到向上调整数据,而删除根元素时会用到向下调整数据,这两个函数使用之前都要保证原数据的父子关系不会发生改变,即:除了增加或删除的数据,其余子树都是的形式。

进行排序 

经过的实现,我们知道一个数据按照去存放,根节点的值要么是最大值要么就是最小值,所以我们多进行几次不就可以依次得到最大值(最小值)。假如用实现大(根)的方式来实现,首先将数组中的数一个个按照向上调整的方式插入进去,这样根就是最大的一个数,此时接下来就有两种途径:1.将剩余的的数继续按照向上调整的方式再次建,找第二大的数(这样就改变了其他数之间的父子关系,故要全部重新插入)。2.将最后一个数和根(最大值)进行交换,再运用向下调整(类似于Pop 函数)实现接下来操作(这样不会改变原数据的父子关系,所以子树依旧是大的形式)。所以第二种方式就轻松了很多。


向上调整建

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void Push(int* arr,int child)
{
	while (child > 0)
	{
		int parent = (child - 1) / 2;
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
		}
		else
			return;

	}
}
void Pop(int* arr,int len)
{

	int parent = 0;
	int child = parent * 2 + 1;//假设较大值是左孩子
	while (child<len)
	{
		if (child + 1 < len && arr[child] < arr[child + 1])//先防止越界,再验证较大值(只有一个左孩子)
			child += 1;//可能存在越界

		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			return;
	}
}
int main()
{

	int arr[] = { 6,1,7,0,3,5,8,2,9,4 };
	int len = sizeof(arr) / sizeof(int);
	for (int i = 1; i < len; i++)//向上建
	{
		Push(arr, i);//传下标
	}

	while(len>0)
	{
		Swap(&arr[0], &arr[len-1]);//将最大值放到最后面
		Pop(arr, len - 1);//传最后一个数的下一个下标
		printf("%d ", arr[len-1]);
		len--;//每循环一次就排好了一个数
	}

	return 0;
}

有一点要注意的是,每次找到最大的数换到数组的最后,则每向下调整一次就得到当前推中的最大值,数据就会逐渐少一,所以依次进行之后数据就按照升序的方式排列了,即:运用大根法实现升序,运用小根法实现降序。

向下调整建

上面的排序其实是用到了向下调整建,向下调整找重建找最值。那么可不可以建也用向下调整的方式来完成呢,而在一份完整的数据面前我们想用向下调整的条件是:该节点的左右子树都是大(小)的形式才可以。

假如我们想要建立一个小。就以上面代码中的数组为例,我们现在的目的就是找可以向下调整的节点,即:该节点的左右子树都是小,而我们从图中可以确定的小就是最靠近叶节点的分支节点,因为该分支节点的两个分支是叶节点,故一定是小,即图中为3的节点,所以我们就从该位置开始向下调整,并且要逆着继续执行,找前面的分支节点,就依次从后向前,这样就可以保证你任何时候向下调整的节点的左右子树一定是

步骤即:

代码实现

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void Adjust_down(int* arr, int i,int len)
{
	int parent = i;
	int child = i * 2 + 1;

	while (child < len)
	{
		if (child + 1 < len && arr[child] > arr[child + 1])//防止有右孩子不存在的情况
			child++;//找较小的子节点
		if (arr[parent] > arr[child])//如果父节点本来就小,不用换
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			return;
	}

}
int main()
{
	int arr[]= { 6,1,7,0,3,5,8,2,9,4 };
	int len = sizeof(arr) / sizeof(int);
	//先向下调整建
	for (int i = (len - 1 - 1) / 2; i >= 0; i--)//i指向元素下标而len-1是最后一个元素下标
	{
		Adjust_down(arr, i,len);//len是最后一个元素的下一个下标
	}
	//向下调整找最值
	while (len > 0)
	{
		Swap(&arr[0], &arr[len - 1]);//交换一次就保留一个最小值放到最后面
		Adjust_down(arr, 0, len - 1);//此时最后一个元素就不计算在内
		printf("%d ", arr[len - 1]);//从后向前打印数据
		len--;
	}

	return 0;
}

复杂度分析

向上调整建:当层数节点多时,需要调整次数也多。所以数据越多,所执行调整的次数也就越多,时间复杂度也就越高。 


向下调整建:而相对于向上调整而言,向下调整建就明显避开了层数节点多时,需要调整次数也多等问题,恰恰相反,层数节点越多,需要调整的次数越少,时间复杂度是O(n-logn) 即:O(n) 。计算就留给你们了,和上面的方法一样。


而上面的方法实现排序的时间复杂度是O(n+n*logn),即:O(n log n)        

中的TOP-K问题

我们了解到排序相比冒泡排序而言,效率要高得多,而且向下调整建又比向上调整建效率高,所以我们就引申到中的 TOP-K 的问题,即:找所有数中最大(最小)的前 K 个数。

那么直接将这给的所有数据建成并向下调整排序不就成了,可是1.如果给的数据过大,空间不够用呢 2.只想要前 K 个最值,但是再向之前一样建存放所有数据的,是不是就存在严重的空间浪费了呢。

假如说我们要找所有数据的前5个最大的值:所以我们就想到了建一个只能存放5个数的小,然后依次将后面的数与顶的值进行比较,如果大于顶的数就交换,进行向下调整,重新建,然后依次将后面的数进行上面操作就可以完成。

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void Adjust_down(int* arr, int i, int len)//i是下标,len是大小
{
	int parent = i;
	int child = parent * 2 + 1;
	while (child<len)
	{
		if (child + 1 < len && arr[child] > arr[child + 1])
			child += 1;
		if (arr[parent] > arr[child])
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			return;
	}
}
int main()
{
	srand((unsigned int)time(NULL));
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("pf=NULL");
		return 1;
	}
	for (int i = 0; i < 1000; i++)
	{
		int x = rand() % 1000;//产生一万个随机数放在文件里
		fprintf(pf, "%d\n", x);
	}
	fclose(pf);//处理完数据之后一定要及时关闭,否则数据可能丢失

	//直接开辟相应大小的空间
	int k = 7;
	int* arr  = (int*)malloc(sizeof(int) * k);
	assert(arr);
	//读取文件中前面7个数据放到数组中
	FILE* po = fopen("data.txt", "r");
	assert(po);
	
	for(int i=0;i<k;i++)
	{
		fscanf(po, "%d", &arr[i]);//将前7个数存到数组里
	}
	for (int i = (k - 2) / 2; i >= 0; i--)//七个数向下调整建小
	{
		Adjust_down(arr, i, k);
	}
	//读取文件后面的数据
	int val = 0;
	while (feof(po)==0)
	{
		fscanf(po, "%d", &val);
		if (val > arr[0])
		{
			arr[0] = val;
			Adjust_down(arr, 0, k);//每执行一次,顶都是最小值
		}
	}
	for (int i = 0; i < k; i++)
		printf("%d ", arr[i]);

	return 0;
}

而当数据过多时,我们就会把数据存在文件当中,从文件中逐一的读取数据。使用小其实就保证了最小值在顶的位置,所以我们每次新来一个值就和顶(最小值比较),大的话就可以直接换掉最小值,再次调整,现在中的最小值又在顶....这样就方便了很多。


http://www.niftyadmin.cn/n/379282.html

相关文章

【整数处理】个人练习-Leetcode-172. Factorial Trailing Zeroes

题目链接&#xff1a;https://leetcode.cn/problems/factorial-trailing-zeroes/ 题目大意&#xff1a;给出一个非负整数n&#xff0c;求n!有多少个尾随0 思路&#xff1a;尾随0只可能来自2*5和10这样的形式。 先看10部分&#xff1a;也就是以10为因数的数&#xff0c;只需要…

【配电网重构】高比例清洁能源接入下计及需求响应的配电网重构【IEEE33节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Matlab进阶绘图第22期—不等宽柱状图

不等宽柱状图是一种特殊的柱状图。 与常规柱状图相比&#xff0c;不等宽柱状图可以通过柱高与柱宽分别表达两个维度的数据&#xff0c;因此在多个领域得到应用。 在《Matlab论文插图绘制模板第91期》中&#xff0c;虽有介绍过利用Matlab自带bar函数绘制不等宽柱状图的方法&am…

《为什么精英都是清单控》读书笔记

评价一下这本书《为什么精英都是清单控》主要的内容提炼 这本书的主要内容是关于清单的重要性以及如何有效地制定和使用清单提高生产力和效率。书中指出&#xff0c;成功的人士往往是清单控&#xff0c;他们用清单来记录任务、计划和目标&#xff0c;使自己始终保持高效和有组…

RPM软件管理程序:rpm

文章目录 RPM软件管理程序&#xff1a;rpmRPM默认安装的路径RPM安装(install)使用案例 RPM升级与更新(upgrade/freshen)RPM查询(query)使用案例 RPM验证与数字签名使用案例数字签名 RPM反安装与重建数据库(erase/rebuilddb) RPM软件管理程序&#xff1a;rpm RPM是Red Hat Pack…

海昌海洋公园携手盖雅工场,数字化人才管理助力企业踏浪前行

五一假期刚刚结束&#xff0c;但各地主题公园里的游客依然爆满。在这种客流高峰期&#xff0c;游客更加关注乐园的细节和服务水平&#xff0c;保障服务品质和顾客体验是各地主题乐园在竞争中脱颖而出的关键因素之一。为此&#xff0c;乐园高峰期需要大量招聘短期工、临时工、兼…

【软件质量与软件测试 白盒测试与黑盒测试】

第十章 黑盒测试 10.1 等价类划分&#xff1a; 10.1.1 划分等价类 等价类是指所有数据中的一组&#xff0c;它们具有相同的测试结果或相同的响应。等价类划分是将输入数据分为多个等价类的过程。 10.1.2 划分等价类的方法 划分等价类方法主要包括以下几种&#xff1a; 特…

【UR3系统升级到CB3.12附带URcap1.05】

【UR3系统升级到CB3.12附带URcap1.05】 1. 前言1.1 Polyscope 3.12更新须知1.2 更新步骤 2. 对 PSU 电压进行控制的步骤2.1 启动机器人电源2.2 启动机器人程序2.3 查看PSU 电压 3. Polyscope 3.12 软件下载3.1 CB 系列机器人3.2 下载软件包URUP 4. CB3 软件安装的指导4.1 连接示…