二叉树,堆排序及TopK问题

news2024/11/6 23:35:02

要讲二叉树的概念,就要先讲树的概念。
树是什么呢?
树其实是一种储存数据的结构,因为他的结构倒过来和生活中的树很相似所以才被称之为树。
在这里插入图片描述

这是一颗多叉树,从最顶端的节点可以找到下边的几个节点,下边的节点又可以找到他的下一级节点,注意观察,如下边的g h i节点,他们返回上一级只有一条路径,从最上边找到他也只有唯一的路径。
树的结构之间不可以交叉,一个节点只能有一个爸爸节点,但是一个爸爸可以有多个孩子。
联想一下这个结构,是不是和我们windows下的文件夹很相似,一个文件夹打开后可以找到很多文件夹,从某个文件夹内找到另一个文件夹只有一条路径。
讲几个比较重要的概念。
节点的度:一个节点含有的子树的个数,即一个爸爸有几个孩子。
在这里插入图片描述
A的度为4,B的度为2。

树的度:一棵树里面所有节点中,最大的节点的度为树的度
在这里插入图片描述
A的节点为2,B的节点为3,该树的度为3。
树的高度:就是树的深度
父节点:有孩子结点的节点,上图ABC都为父节点
子节点:上图除了A别的都可以称作子节点,他们都有父节点
节点的祖先:所有节点的祖宗,即A
兄弟节点:有相同的父节点的节点。F和G就不是相同的父节点,就不是兄弟节点。但是他们的爸爸在同一层,所以称为堂兄弟节点。、


多叉树的实现比较难搞,每个节点要保存父节点,还要保存他的兄弟节点,
所以我们先来实现一些简单的树形结构:二叉树。
二叉树
概念:这棵树的每个节点的子节点最多只能有两个,左孩子或者右孩子。
任意二叉树都由以下几种情况复合而成
在这里插入图片描述
特殊的二叉树

  1. 满二叉树:一个二叉树,如果他的每一层的节点数都达到最大值,这棵树就是满二叉树,如果该二叉树的层数为K,总结点个数为2k -1。
    例如:
    在这里插入图片描述
    每个父亲节点都有两个孩子。

现实中的二叉树如图:

在这里插入图片描述在这里插入图片描述
是不是超级标准。
2,完全二叉树
完全二叉树由满二叉树发展而来(满二叉树是完全二叉树的一种),如果一棵树有K个节点,这些节点从左往右依次数都是连着的,假设这里有一棵完全二叉树,节点个数为9。

在这里插入图片描述
只能4有左孩子后才能有右孩子,同一层节点,如果左边的节点没有两个儿子,后边的节点都不能有孩子。
下边两棵树都不是完全二叉树。
二叉树的存储结构
1,顺序结构
顺序结构用数组来存储,但一般只适用于完全二叉树,完全二叉树使用数组存储的话不会因为有些地方是空的会造成空间的浪费,所以现实中只有用堆才会使用数组存储。
二叉树顺序存储在物理上是一个数组,在逻辑上是一棵完全二叉树。

在这里插入图片描述
第二种是链式储存,用链表表示一棵二叉树,这种结构在数据结构高阶中才会用到,我们不做仔细讲解。

普通二叉树是不适合用数组建堆的,会造成大量的空间浪费,但是完全二叉树不一样,他的节点是挨个建立的,我们接下来建堆就是用这种结构。
堆的概念
有一堆元素,以顺序表按照完全二叉树的顺序将其储存起来,堆有两种,大堆和小堆。
大堆
all父节点大于子节点,根节点的值最大,这样的堆叫做大堆
如果根节点最小,所有父节点的值小于子节点,那这个堆被称为小堆。
堆的创建
给一个数组,

a[5,6,2,8,9,4,7]

这个数组在逻辑上是一棵完全二叉树,但还不是一个堆。
想要将它变成一个大堆,就要调整他的顺序。
首先我们会想到让根节点小于其子节点,然而其子节点也必须小于自己的子节点,所以想要让一棵树变为一个大堆,就要让自己的子树也变成一个大堆。

建堆过程如图
在这里插入图片描述
用数组的方式存储,但其逻辑上可以看作是一棵二叉树,完全二叉树
在这里插入图片描述

这里的1,2,3,4,5,6,7是数组的下标,
可以发现,若知道一个子节点的下标为child,其父节点的下标为(child-1)/2。
知道一个父节点parent,其左孩子节点的下标为parent2+1。右孩子的下标为parent2+2。

建堆的时间复杂度
向下调整法
向下调整法通过父节点的下标找左右孩子,不断判断值的大小,交换建堆。
代码如下

//向下调整
void AdjustDown(HeapStyle* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child<n)
	{
		//找出小的孩子
		if (child + 1 < n && a[child]> a[child + 1])
		{
			++child;
		}
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

为了容易计算,且满二叉树是完全二叉树的一种,为了化简过程就利用满二叉树来证明。
假设树的高度为n
第一层一个节点最多需要向下调整n-1层
第二层21个节点,最多每个节点要调整h-2层
第三层有22个节点,最多每个节点要调整h-3层

第h-1层2h-2个节点,最多向下调整一层
在这里插入图片描述
建堆的时间复杂度为O(N)。
向上调整法建堆
第一层不需要调整
第二层21个节点,每个节点最多需要向上调整1次。
第三层22个节点,每个节点最多向上调整2次

第h-1层2h-2个节点,最多每个节点向上调整h-2次。
第h层2h-1个节点,每个节点最多向上调整h-1次。
不用算就可以得出向上调整的时间复杂度为O(N2)。
向上调整法通过孩子找父亲判断交换建堆
代码如下

//向上调整
void AdjustUp(HeapStyle* a, int child)//向上调整
{
	int  parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] > a[parent])
		{
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

建堆过程
通过上边的分析,我们已经知道向下调整法相对于向上调整优势巨大,所以在建堆的过程中,使用向下调整法,遍历数组的每个节点,使用向下调整法建大堆,上边的流程图可知如果从根节点开始建堆,不能确保左子树和右子树是否为堆,最后一层不需要调整,从倒数第二层开始调整,使其变为一个堆。

//找出小的孩子
		if (child + 1 < n && a[child]> a[child + 1])
		{
			++child;
		}

找出该节点左右孩子中大的那个。

if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}

如果小于,就互换,越小越往上,所以建的是小堆,将判断条件更改为大于号,即建大堆。这里的大于号和小于号是建大堆还是建小堆的关键。
利用循环,交换后将parent改为child,child也更新一次,是因为更换过来的父节点有可能比该子树的子节点更小。
如图所示
在这里插入图片描述
给定一个数组a[k]

	for (int i = (k - 2) / 2; i >= 0; --i)//这里要思考一下
	{
		AdjustDown(minheap, k, i);
	}

从倒数第二层开始建堆即可,k-1是数组最后一个函数,要想知道其父节点下标,要减一再除以2,所以就变成了(k-2)/2。
堆排序
假设我们要排升序
建立一个大堆,堆顶元素一定是最大的那个,如果直接取出根节点,那么我们建的堆将被破坏,左子树和右子树可能就不再是大堆。
就像上图数组,建堆后下标为0,1,2,3,4的数字为4 6 5 7 7,如果取出4,剩余的6,5,7,7明显不再是一个堆,我们又要重新建堆,这样堆排序有什么优势可言,时间复杂度就变为O(N2)。如何取出根节点又不破坏左子树和右子树的大堆状态呢?
将堆顶元素与最后一个交换,最大的数就到了数组最后边,然后只需要对换过去的根节点向下调整即可。
代码如下

//向下调整
void AdjustDown(HeapStyle* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child<n)
	{
		//找出小的孩子
		if (child + 1 < n && a[child]< a[child + 1])//找出左右子树大的那个
		{
			++child;
		}
		if (a[child] > a[parent])//建大堆,大于就交换
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void Heapsort(int* a, int n)//建大堆
{
	for (int i = (n-2)/2; i >=0; i--)
	{
		AdjustDown(a,n, i);
	 }//
	int end = n - 1;
	while (end > 0)
	{
		swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

int end=n-1。a[end]为最后一个节点,交换后传参为n-1,向下调整就不再带最后一个节点玩了,直到调整至第一个节点,这样这个数组就有序了。
TOPK问题
TOPK问题在生活中常常出现,就比如年级前几名,饭店味道排名等等等等,如何在一大地数据中找到前几名呢?
如果我们建一个小堆,那么堆顶节点即为整棵树最小的那个,假设有10000个数据,我们要找出其中的前10个,我们就可以使用前十个元素建一个小堆,然后再让剩下的元素与堆顶元素进行比较,如果大于对顶元素,就与对顶元素进行交换,然后向下调整重新建出一个小堆,这时堆顶元素会发生变化,会变成一个新的堆中最小的数,遍历完成之后队中剩下的元素即为这10000个数里面最小的那个。
创建一个数组向里面写入10000个随机数进行测试。
在这里插入图片描述
随机数的范围是32767,如果我们创建100000个数据,只会出现很多重复的数据,所以我们就使用一万个数据测试一下数据。
代码如下

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//int main()
//{
//	printf("%d ", RAND_MAX);
//
//	return 0;
//}

void  swap(int* a, int* b)
{
	int swp = 0;
	swp = *a;
	*a = *b;
	*b = swp;
}
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		//找出小的孩子
		if (child + 1 < n && a[child] > a[child + 1])
		{
			++child;
		}
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void TestTopk()
{
	int n = 10000;
	int* a = (int*)malloc(sizeof(int) * (n));
	srand(time(0));
	for (int i = 0; i < n; i++)
	{
		a[i] = rand() % 10000;
	}
	a[5] = 100000 + 1;
	a[6100] = 100002;
	a[1007] = 100003;
	a[8678] = 888888;
	a[3459] = 777777;
	int k = 10;
	for (int i = (k - 2) / 2; i >= 0; i--)//倒数第二层最后一个节点
	{
		AdjustDown(a, k, i);//传入下标
	}
	//以前k个数建堆完毕
	for (int i = k + 1; i < n; i++)
	{
		if (a[i] > a[0])
		{
			swap(&a[i], &a[0]);
		}
		AdjustDown(a, k, 0);
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", a[i]);
	}
}

int main()
{
	TestTopk();

	return 0;
}

为了测试方便,我们修改了数组中的几个数值。
运行后如下
在这里插入图片描述
是不是小堆呢?
在这里插入图片描述
答案是,他就是个小堆,这样一万个数据里的前10个最大的数据就找出来了。
然而10000个数据是不是有点太少了。
我们普通创建的数组存储在栈上,malloc出的数组存放在堆里,如果有很多很多数据,就会占据很多时间,我们可以考虑将这些数字存放在一个文件里,然后利用相关的文件操作找出这些数字里最大得前几个。
向文件里写入数据

//创造数据
void CreatNData()
{
	int n = 1000000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen,error");
		return;
	}

	for (int i = 0; i < n; ++i)
	{
		int x = rand() % 100000;//然而最大不过32767,有很多重复数据得了
		fprintf(fin, "%d\n", x);
	}
	int k = 10;
	//PrintTopk(file, k);

	fclose(fin);
}

创建完成之后可以打开修改修改数据,然后在main函数里将创建数据的函数注释,防止数据覆盖。
在这里插入图片描述
打开后直接就是一顿修改
在这里插入图片描述
那几个后边数字相同且贼大的就是修改的数据。

void PrintTopk(const char*file, int k)
{
	//1,建堆--用a中的前k个元素建堆
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		return;
	}
	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL)
	{
		perror("malloc fail");
		return;
	}
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);//依次先读K个数
	}
	
	for (int i = (k - 2) / 2; i >= 0; --i)//这里要思考一下
	{
		AdjustDown(minheap, k, i);
	}
	//2.将剩余的n-k个元素依次与堆顶元素
	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)
	{
		if (x > minheap[0])
		{
			//替换进堆
			minheap[0] = x;
			AdjustDown(minheap, k, 0);
		}
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);
	}
	fclose(fout);
}

逻辑和上边malloc出的数组找最大得前十个一样。建小堆,找到小于根节点的数据就进行交换,交换后再次进行向下调整。
要注意,rand出的数据最大32767,因为我们创建了一百万个数据,所以如果不修改数据的话,查找出来的都会是32767。
运行结果如图
在这里插入图片描述
是一个小堆
在这里插入图片描述
对顶元素在预料之中。这样Topk问题就解决啦。
这篇文章就讲解到这里,如果有什么问题欢迎大家提出指正。

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

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

相关文章

Apollo:前端开发者的全栈探索之旅

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

【C语言必知必会 | 第四篇】一文带你精通顺序结构

引言 C语言是一门面向过程的、抽象化的通用程序设计语言&#xff0c;广泛应用于底层开发。它在编程语言中具有举足轻重的地位。 此文为【C语言必知必会】系列第四篇&#xff0c;进行C语言顺序结构的专项练习&#xff0c;结合专题优质题目&#xff0c;带领读者从0开始&#xff0…

Halcon手眼标定

手眼标定&#xff08;参考&#xff1a;B站王佳琪老师) 这里说的手眼标定中的手指的是机械手或者电机运动的轴&#xff0c;眼表示摄像头 就是两个空间坐标系的转换&#xff0c;这个转换需要一个转换矩阵&#xff0c;那么转换矩阵需要根据两个坐标系的对应的九个点来通过vec_to…

如何用记事本制作一个简陋的小网页(3)——注册信息表

目录 前提须知&#xff1a; 一、表格建立之前&#xff1a; 二、表格的建立&#xff1a; 三、信息表的内容填充&#xff1a; 1.昵称 和 电话 &#xff1a; 2.密码&#xff1a; 3.性别&#xff1a; 4. 爱好&#xff1a; 5.民族&#xff1a; 6. 出生日期&#xff1a; 7.…

Python用selenium实现自动登录和下单的项目实战

本文主要介绍了Python用selenium实现自动登录和下单的项目实战&#xff0c;文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值&#xff0c;需要的朋友们下面随着小编来一起学习学习吧− 前言 学python对selenium应该不陌生吧 Selenium…

什么是低代码开发平台?有什么优势?

目录 一、低代码平台演进 1. 低代码概念 2. 低代码衍生历程 二、为什么要用低代码&#xff1f; &#xff08;1&#xff09;降本提效&#xff0c;便捷开发 &#xff08;2&#xff09;降低开发门槛&#xff0c;扩大应用开发劳动力 &#xff08;3&#xff09;加快数字化转型建设 三…

“第四十五天” 数据结构基本概念

目前看的有关数据结构的课&#xff0c;估计这周就看完了&#xff0c;但感觉差很多&#xff0c;还是和c一样&#xff0c;这样过一下吧。但可能比较急&#xff0c;目前是打算争取寒假回家之前把四大件都先大致过一遍。 数据结构里面有很多新的定义和概念&#xff0c;学到现在&am…

054协同过滤算法的电影推荐系统

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

vue el-dialog弹出框自定义指令实现拖拽改变位置-宽度-高度

前言 在实际开发中我们经常使用el-dialog弹出框做表单&#xff0c;一般情况都是居中。遮挡到了一部分数据 当我们想要查看弹出框下面的数据时&#xff0c;就只能先把弹出框关闭&#xff0c;查看完数据之后在打开弹框 我们通过动态样式&#xff0c;和鼠标事件就可以实现。但自…

OpenLDAP LDIF详解

手把手一步步搭建LDAP服务器并加域 有必要理解的概念LDAPWindows Active Directory 服务器配置安装 OpenLDAP自定义安装修改对象&#xff08;用户和分组等&#xff09;修改olcSuffix 和 olcRootDN 属性增加olcRootPW 属性修改olcAccess属性验证新属性值 添加对象&#xff08;用…

Neo4j入门教程2(看不懂评论区随便骂)

1. ORDER BY create (s4:student{age:21,num:98}),(s5:student{age:22,num:86}),(s6:student{age:23,num:99})承接上文&#xff0c;创建三个学生节点&#xff0c;标签为student1、student2、student3&#xff0c;分别拥有age属性和num属性 match(s:student) return s查看我们…

驱动day2:LED灯实现三盏灯的亮灭

head.h #ifndef __HEAD_H__ #define __HEAD_H__ #define PHY_PE_MODER 0x50006000 #define PHY_PF_MODER 0x50007000 #define PHY_PE_ODR 0x50006014 #define PHY_PF_ODR 0x50007014 #define PHY_RCC 0x50000A28#endif 应用程序 #include <stdio.h> #include <sys/…

【C语言必知必会 | 第二篇】编译器的安装与使用

引言 C语言是一门面向过程的、抽象化的通用程序设计语言&#xff0c;广泛应用于底层开发。它在编程语言中具有举足轻重的地位。 此文为【C语言必知必会】系列第二篇&#xff0c;介绍C语言编译器的安装与使用&#xff0c;为之后的学习安装环境基础 文章目录 1️⃣ Dev-C的安装与…

【2024秋招】用友项目管理部门java后端二面2023.9.12

1 rpc框架 1.1 深入讲讲你做的这个rpc框架 答&#xff1a; 1.2 你这个注册中心有实现相关的监控吗 1.2.1 如何实现注册中心有实现相关的监控 是的&#xff0c;我可以为你提供关于RPC注册中心及其监控的相关信息。RPC注册中心是用于管理微服务之间调用关系的中心化服务&…

数学建模入门

一 数学建模简介 数学建模创办于1992年&#xff0c;每年一届&#xff0c;是首批列入“高校学科竞赛排行榜”的19项竞赛之一。2022年&#xff0c;来自全国及英国、马来西亚的1606所院校/校区、54257队(本科49424队、专科4833队)、超过16万人报名参赛。是目前奖项最具有含金量的…

基于材料生成优化的BP神经网络(分类应用) - 附代码

基于材料生成优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于材料生成优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.材料生成优化BP神经网络3.1 BP神经网络参数设置3.2 材料生成算法应用 4.测试结果…

Spring Boot + EasyUI 创建第一个项目(一)

创建一个Spring Boot和EasyUI相结合的项目。 一、构建一个Spring Boot项目 Spring Boot之创建一个Spring Boot项目&#xff08;一&#xff09;-CSDN博客 二、配置Thymeleaf Spring Boot Thymeleaf&#xff08;十一&#xff09;_thymeleaf 设置字体_人……杰的博客-CSDN博客…

轴承寿命相关细节的研究

数据集PHM2012 介绍一下IEEE PHM2012数据集_phm2012轴承数据集-CSDN博客 标签如何设置的? 剩余寿命预测的标签设置_rul 标签_兔子牙丫丫的博客-CSDN博客 参考自刘硕师兄的毕业答辩PPT 图 4.9 训练数据的切分方法 数据段的重叠切分&#xff0c;不仅可以覆盖更多的标签数据…

分布式锁之环境准备

文章目录 分布式锁1. 传统锁回顾1.1. 从减库存聊起1.2. 环境准备 分布式锁 在应用开发中&#xff0c;特别是web工程开发&#xff0c;通常都是并发编程&#xff0c;不是多进程就是多线程。这种场景下极易出现线程并发性安全问题&#xff0c;此时不得不使用锁来解决问题。在多线…