【数据结构和算法初阶(C语言)】二叉树的顺序结构--堆的实现/堆排序/topk问题详解---二叉树学习日记②1

news2024/11/29 2:47:16

 

目录

 ​编辑

1.二叉树的顺序结构及实现

1.1 二叉树的顺序结构

2 堆的概念及结构

3 堆的实现

3.1堆的代码定义

3.2堆插入数据

3.3打印堆数据

3.4堆的数据的删除

3.5获取根部数据

3.6判断堆是否为空

3.7 堆的销毁 

4.建堆以及堆排序 

4.1堆排序---是一种选择排序

4.2升序建大堆,降序建小堆

 4.3 建堆的时间复杂度

4.3.1向下调整建堆

4.3.2向上调整建堆

4.4 topk问题

5.结语


1.二叉树的顺序结构及实现

1.1 二叉树的顺序结构

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

左孩子的下标 = 父亲下标*2+1

右孩子下标 = 父亲节点下标*2+2

父亲节点下标 = (子节点下标-1)/2 

2 堆的概念及结构

堆是非线性结构,是完全二叉树

如果有一个值的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足: = 且 >= ) i = 0,1, 2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。 堆的性质: 堆中某个节点的值总是不大于或不小于其父节点的值;

堆总是一棵完全二叉树。

通俗来说父节点小于等于子节点的完全二叉树就叫小根堆,或者小堆,根一定是整棵树最小的。

父节点值大于等于子节点的完全二叉树叫做大根堆。或者大堆,但是底层数组不一定降序。但是大堆的根是整棵树的最大值。

3 堆的实现

3.1堆的代码定义

底层是一个顺序表

typedef int HPDataType;

typedef struct Heap
{
	//底层是一个顺序表,但是数据不是随便存储,逻辑结构是二叉树
	HPDataType * a;
	int size;
	int capacity;
}HP;

堆的初始化:

void HeapInit(HP* php)
{
	assert(php);
	HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType) * 2);//先为i堆空间申请两个节点
	if (tmp == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	
	php->a = tmp;
	php->capacity = 2;
	
	php->size = 0;
}

3.2堆插入数据

实现关键

实现原理图:向上调整:

(以大堆的实现方式举例)

首先我们从有限个数据的层面来实现一下堆的实现,后面堆排序再来看对于一堆数据怎么建堆。

对于一组少量数据比如一个数组:

首先将数据一个一个插入到堆里面,由于数据有限可以使用这种数据插入的方式建立堆这种数据结构;

void HeapPush(HP* php, HPDataType x)
{
	//尾插

	assert(php);
	//判断空间够不够
	if (php->capacity == php->size)
	{
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(php->a) + sizeof(HPDataType) * 2);
		if (tmp == NULL)
		{
			perror("realloc");
			exit(-1);
		}
		php->a = tmp;
		php->capacity += 2;
	}
	php->a[php->size] = x;
	php->size++;
	//调整数据,变成堆
	AdjustUp(php->a, php->size-1);
	
}

 然后把这组数据调整成一个堆:

void Swap(HPDataType* child, HPDataType* parent)
{
	HPDataType tmp = 0;
	tmp = *child;
	*child = *parent;
	*parent = tmp;
}
void AdjustUp(HPDataType* a,int child)//向上调整
{
	//最坏调整到根
	int parent = (child - 1) / 2;
	while (child>0)//注意这个判断条件
	{
		if (a[child] > a[parent])
		{
			//交换
			Swap(&a[child], &a[parent]);
			//继续往上深入判断,将父亲的下标给孩子,父亲的父亲的下标给父亲
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
		{
			break;//跳出循环
		}
	}

}

3.3打印堆数据

为了看一下我们插入的效果我们来试一下插入一段数据 

void HeapPrint(HP* php)
{
	assert(php); 
	for (int i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
		
	}
}

 

 就建成了一个大堆。

3.4堆的数据的删除

堆这个数据结构有意义的一个点就是,大堆的根一定是这组数据中最大的值,小堆的根一定是这组数据中最小的值。所以如果我们能拿到这个根的数据,再删除就可以找到这堆数据中次小的数据了。那么删除根数据是这个结构比较有意义的。

想一个问题:根的删除能不能简单的数据覆盖?只是将后续的数据移动向前

答案是不能的,可以数据这样移动后续数据根本就不能成堆了。那么这里使用的方法是向下调整法

前提是左右子树是堆:

这里我们以小堆举例示范:

先删除

void HeapPop(HP* php) 
{
	assert(php);
	//不可挪动覆盖。可能就不是堆了
	//先交换根和最后一个值,再删除,左右子树依旧是小堆
	//向下调整的算法,左右子树都是小堆或者大堆。
	 
	assert(php->size > 0);
	Swap(&php->a[0],&php->a[php->size-1]);
	php->size--;//删除了数据
	AdjustDown(php->a,php->size, 0);
}

在调整

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+1可能越界访问
		{
			child++;
		}
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			//继续向下调整
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}

调整是由于每次都是取两个子节点中的较小的值,所以先假设一个大,如果假设错了,就改变下标 

if (child+1<n&&a[child + 1] < a[child])//child+1可能越界访问
        {
            child++;
        }

对调整循环结束的判定所示孩子下标小于n

3.5获取根部数据

//获取根部数据
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

3.6判断堆是否为空


//判断堆是否为空函数
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;

}

3.7 堆的销毁 

void HeapDestory(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

那么如果现在我们每次拿到堆的元素在删除在获取,就可以得到一个有序的数据了:

4.建堆以及堆排序 

上面我们已经掌握了堆这个数据结构的一些方法,最后通过插入数据建堆。删除数据将数据排序。可是如果我有十亿个数据,想找出最大的十个数据,如果用堆得插入10亿次数据吗?那就失去了使用这个数据结构的意义,通常来说我们只用建立一个大堆模型,这个堆的前十个数据自然就是10亿个数据中的最大的一个。

4.1堆排序---是一种选择排序

使用堆结构对一组数据进行排序,方便对数据进行处理。粗暴办法就是将原数组数据插入堆,再取堆数据覆盖,这种方法首先得有堆结构,其次插入数据就要额外开辟空间。

最好的方式就是直接将原数组或者原来的这组数据变成堆。将原数组直接看成一颗完全二叉树,一般都不是堆。那么就要将原数据之间调成堆----建堆

建堆不是插入数据,只是使用向上调整的思想。在原有数据上进行更改,调换

int a[] = { 2,3,5,7,4,6,8,65,100,70,32,50,60 };
HeapSort(a, sizeof(a) / sizeof(a[0]));

void HeapSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		AdjustUp(a, 1);
	}

}

void AdjustUp(HPDataType* a,int child)//向上调整
{
	//最坏调整到根
	int parent = (child - 1) / 2;
	while (child>0)//注意这个判断条件
	{
		if (a[child] < a[parent])
		{
			//交换
			Swap(&a[child], &a[parent]);
			//继续往上深入判断,将父亲的下标给孩子,父亲的父亲的下标给父亲
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
		{
			break;//跳出循环
		}
	}

}

4.2升序建大堆,降序建小堆

一般我们要利用堆结构将一组数据排成升序,就建立大堆

要利用堆结构将一组数据排成降序,就建立小堆。

排序和删除的原理是一样的,先找最大/最小然后次大/次小,每次选取数据后不会将后面数据覆盖上来,否则就会导致关系全乱,可能次大数据就要重新建堆,增加了工作量了。而是通过堆顶元素和最后一个数据交换位置过后,向下调整思想,将排除刚刚调整的尾部最大数据除外的剩下数据看成堆,循环排序。

最后发现:大堆这样处理的数据最大的数据在最后,最小的在最前,小堆相反。 

void HeapSort(int* a, int n)
{
	//对数据进行建堆
	for (int i = 0; i < n; i++)
	{
		AdjustUp(a, 1);
	}
	//堆排序---向下调整的思想
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;//让n-1个数据调整成堆选出次小
	}

}

 4.3 建堆的时间复杂度

4.3.1向下调整建堆

4.3.2向上调整建堆

4.4 topk问题

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

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。 对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能 数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,

基本思路如下:

1. 用数据集合中前K个元素来建堆 前k个最大的元素,则建小堆 ;找前k个最小的元素,则建大堆

2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

比如现在我们的磁盘中有十亿个数据,我们要在十亿数据中找到前100个最大的数

第一步:读取文件的前100个数据,在内存中建立一个小堆

第二步:再依次读取剩余数据与堆顶部元素进行比较,大于堆顶就替换进堆,向下调整,所有数据读完,堆里面就是最大的100个。

首先堆顶元素就是这100个数据中最小的,如果比这个堆顶数据小的肯定不是要找的前100个最大数中的一个,如果比堆顶元素大进替换堆顶元素进堆,向下调整后找到这100个数据中次二小的,最后遍历完就得到这100个最大的数据。堆顶元素就是第100大数据,如果建立大堆一轮遍历只能找到一个最大的值,就没有必要建堆了。

先向磁盘中写入1万个数据

void CreateNDate()
{
	int n = 10000;
	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() % 1000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}

将前k个数据从磁盘中读入内存,

// 1. 建堆--用a中前k个元素建堆
//首先读文件
FILE* fout = fopen(filename, "r");
if (fout == NULL)
{
	perror("fopen");
	return;
}
int* minhead = (int*)malloc(sizeof(int) * k);
if (minhead == NULL)
{
	perror("malloc fail");
	return;
}
for (int i = 0; i < k; i++)
{
	fscanf(fout, "%d", &minhead[i]);
}

利用向下调整的方式建堆,并且与磁盘中的元素进行一一比较

//建堆,也可以向上插入建堆
for (int i = (k - 2) / 2; i >= 0; i--)
{
	AdjustDown(minhead, k ,i);
}


// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
int x = 0;
while (fscanf(fout, "%d", &x) != EOF)
{
	if (x > minhead[0])
	{
		minhead[0] = x;//替换进堆
		AdjustDown(minhead, k, 0);
	}
}
//打印一下前100个最大的数据
for (int i = 0; i < k; i++)
{
	printf("%d ", minhead[i]);
}
printf("\n");
fclose(fout);

然后手动修改十个·最大的值在磁盘里检测结果:

 

在初始堆的时候就可以直接为这段数据开辟固定空间,然后初始化堆的时候就可以直接建堆;

void HeapInitArray(HP* php, int* a, int n)
{
	assert(php);
	assert(a);
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);//之间开辟好对应数据大小的空间
	if (php->a == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	php->size = n;
	php->capacity = n;
	memcpy(php->a, a, sizeof(HPDataType) * n);//将数据拷贝到空间中
	for (int i = 1; i < n; i++)
	{
		AdjustUp(php->a, i);//向上调整成堆
	}
}

5.结语

以上就是本期的所有内容,知识含量蛮多,大家可以配合解释和原码运行理解。创作不易,大家如果觉得还可以的话,欢迎大家三连,有问题的地方欢迎大家指正,一起交流学习,一起成长,我是Nicn,正在c++方向前行的奋斗者,数据结构内容持续更新中,感谢大家的关注与喜欢。

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

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

相关文章

微信小程序 canvas层级过高覆盖原生组件

一、背景 微信小程序中使用signature第三方插件完成签名效果&#xff0c;但真机调试时发现canvas层级过高遮挡了按钮 二、具体问题 问题原因&#xff1a;签名后点击按钮无法生效 问题代码&#xff1a; <template><view class"sign_page" v-cloak>&l…

nodejs基于vue超市信息管理系统flask-django-php

互联网的快速发展&#xff0c;使世界各地的各种组织的管理方式发生了根本性的变化&#xff0c;我国政府、企业等组织在上个世纪90年代就已开始考虑使用互联网来管理信息。由于以前的种种因素&#xff0c;比如网络的普及率不高&#xff0c;用户对它的认知度不够&#xff0c;以及…

计算机网络:数据交换方式

计算机网络&#xff1a;数据交换方式 电路交换分组交换报文交换传输对比 本博客介绍计算机之间数据交换的三种方式&#xff0c;分别是电路交换、分组交换以及报文交换。 电路交换 我们首先来看电路交换&#xff0c;在电话问世后不久&#xff0c;人们就发现要让所有的电话机都…

基于Spring Boot网络相册设计与实现

摘 要 网络相册设计与实现的目的是让使用者可以更方便的将人、设备和场景更立体的连接在一起。能让用户以更科幻的方式使用产品&#xff0c;体验高科技时代带给人们的方便&#xff0c;同时也能让用户体会到与以往常规产品不同的体验风格。 与安卓&#xff0c;iOS相比较起来&am…

【OCR】OCR开源文字识别工具

在日常的工作中&#xff0c;例如自动化测试开展时&#xff0c;经常涉及到一些验证码识别、文本识别、图像识别的场景&#xff0c;市面上虽也有很多识别工具&#xff0c;但质量、准确性参差不齐。 今天给大家推荐一个开源OCR项目:Umi-OCR&#xff0c;功能很强大&#xff0c;而且…

jquery 列表框可以手动修改(调用接口修改)

类似于这种 直接上代码 列表框 <td>//目的主要是获取属性名的(要更改的属性名) 在下面juqery的这一行(var field $(thisobj).prev(input).attr(name);)有体现<input type"hidden" name"voyage" value"${M_PSI_PERIOD_INFO.port}">…

Vulnhub - Hacker_Kid

希望和各位大佬一起学习&#xff0c;如果文章内容有错请多多指正&#xff0c;谢谢&#xff01; 个人博客链接&#xff1a;CH4SER的个人BLOG – Welcome To Ch4sers Blog Hacker_Kid 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/hacker-kid-101,719/ WP 参考&…

路由器里如何设置端口映射?

在互联网时代&#xff0c;我们经常需要将内部网络的服务暴露到公网以便其他人访问。直接将内部网络暴露在公网上存在一定的安全风险。为了解决这个问题&#xff0c;我们可以利用路由器里设置端口映射来实现将特定端口的访问请求转发到内部网络的特定设备上。 端口映射的原理 端…

机器人离散化阻抗控制

机器人离散化阻抗控制是一种控制策略&#xff0c;它结合了阻抗控制的思想与离散化方法&#xff0c;以实现对机器人运动与外力之间动态关系的精细调节。这种控制方法旨在使机器人在与环境交互时能够表现出期望的阻抗特性&#xff0c;从而实现对接触力和位置的精确控制。 在离散…

AnyGo for Mac最新激活版:位置模拟软件打破地域限制

AnyGo for Mac&#xff0c;一款专为Mac用户打造的位置模拟软件&#xff0c;让您能够轻松打破地域限制&#xff0c;畅享无限可能。 软件下载&#xff1a;AnyGo for Mac v7.0.0最新激活版 通过AnyGo&#xff0c;您可以随时随地模拟出任何地理位置&#xff0c;无论是国内热门景点还…

轻松解锁微博视频:基于Perl的下载解决方案

引言 随着微博成为中国最受欢迎的社交平台之一&#xff0c;其内容已经变得丰富多彩&#xff0c;特别是视频内容吸引了大量用户的关注。然而&#xff0c;尽管用户对微博上的视频内容感兴趣&#xff0c;但却面临着无法直接下载这些视频的难题。本文旨在介绍一个基于Perl的解决方…

3D开发工具HOOPS如何助力3D项目实现扩展现实技术?

在当今数字化时代&#xff0c;扩展现实&#xff08;Augmented Reality&#xff0c;AR&#xff09;技术的应用已经逐渐深入到各行各业&#xff0c;为用户带来了前所未有的沉浸式体验。而在实现这种技术的开发过程中&#xff0c;HOOPS技术的运用无疑是一种强大的助力。HOOPS是一种…

WPF按钮相关

跟着官网敲的按钮相关的内容,还涉及了wpf很多其他的知识 1.创建基本按钮 <Grid><StackPanel HorizontalAlignment"Left"><Button>Button1</Button><Button>Button2</Button><Button>Button3</Button></StackPan…

WebMvcConfigurationSupport 注册自定义拦截器 Java SpringBoot

WebMvcConfigurationSupport 注册自定义拦截器 Java SpringBoot 说明 定义拦截器 拦截器&#xff08;Interceptor&#xff09;类&#xff0c;用于在处理请求之前进行一些操作。 实现方式&#xff0c;让类实现HandlerInterceptor。 在preHandle方法中进行请求拦截的逻辑操作…

海外社交营销为什么用云手机?不用普通手机?

海外社交营销作为企业拓展海外市场的重要手段&#xff0c;正日益受到企业的青睐。云手机以其成本效益和全球性特征&#xff0c;成为海外社交营销领域的得力助手。那么&#xff0c;究竟是什么特性使得越来越多的企业选择利用云手机进行海外社交营销呢&#xff1f;下文将对此进行…

31-Java前端控制器模式(Front Controller Pattern)

Java前端控制器模式 实现范例 前端控制器模式&#xff08;Front Controller Pattern&#xff09;是用来提供一个集中的请求处理机制&#xff0c;所有的请求都将由一个单一的处理程序处理该处理程序可以做认证/授权/记录日志&#xff0c;或者跟踪请求&#xff0c;然后把请求传给…

32位ARM微控制器: TLE9877QTW40XUMA1、CY9BF416NPMC-G-JNE2、CY9BF316NPMC-G-JNE2描述、器件参数

1、TLE9877QTW40 IC MCU 32BIT 64KB FLASH 48TQFP 描述&#xff1a;TLE9877QTW40属于TLE987x产品系列。TLE9877QTW40是一款单芯片三相电机驱动器&#xff0c;集成了行业标准的Arm Cortex -M3内核&#xff0c;能够实现先进的电机控制算法&#xff0c;例如磁场定向控制。它包括六…

使用 ArcGIS Pro 和 Google Earth Engine 可视化地表温度

在这项研究中,利用 Landsat 热数据通过各种方法检查了 2013 年和 2023 年恰纳卡莱省的地表温度变化。使用了 NDVI、大气层顶部、亮度温度、植被比例和地表温度等公式。研究结果表明,从热图像中获得的数据,特别是地表温度(LST),是土地解释的重要资源。 研究区域:恰纳卡莱…

Android14之selinux报错:ERROR: end of file in comment(一百九十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

NLP---Bert分词

目录&#xff1a; Q&#xff1a;bert分词步骤1&#xff1a;构建N * N 的相关性矩阵&#xff0c;计算相邻两个字的相关性&#xff0c;低的话&#xff08;<阈值&#xff09;就切割。2&#xff1a;将A词进行mask计算出A的embedding&#xff0c;然后将AB两个词一起mask&#xff…