【数据结构】堆的应用+TOP-K问题+二叉树遍历

news2024/11/25 20:29:13

在这里插入图片描述

欢迎来到我的:世界

希望作者的文章对你有所帮助,有不足的地方还请指正,大家一起学习交流 !


目录

  • 前言
  • 堆的时间复杂度
    • 向下调整算法的时间复杂度
    • 向上调整算法的时间复杂度
  • 堆的应用
    • 堆排序
    • TOP—K问题
    • 链式二叉树
      • 二叉树的节点:
      • 初始化节点
      • 实现链式二叉树
    • 二叉树的概念:
      • 二叉树的遍历
        • 前序遍历
        • 中序遍历
        • 后序遍历
        • 层序遍历
  • 总结

前言

该篇文章写到主要是:堆排序、 TOP-K问题、二叉树链式结构的实现、二叉树的遍历等等;如果有朋友还不太了解堆以及二叉树可以翻看我的上一篇博客:堆和二叉树的概念;
最后老铁们准备发车喽!!!


堆的时间复杂度

紧接上一篇博客,我们刚刚实现了堆的实现,还没有拿他做点有意义的事情呢 ,咱们马上开始👉
如果问你:建堆的时间复杂度是多少?

实现堆有有两种方法:向下调整算法和向上调整算法,但是这两种方法有区别吗?哪个算法的时间复杂度更好呢?
接下来我们来带着问题进入下方:

向下调整算法的时间复杂度

时间复杂度就是看其最坏的情况,因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):

在这里插入图片描述
在这里插入图片描述
由于我们已经知道满二叉树结点和层数的关系N=2^h-1可转化为h=log2(N+1)(ps:Log2(n+1)是log以2为底,n+1为对数)

知道了这层关系可以推出:
T(N)= N-log2(N+1)~ N (约等于)
所以向下调整算法的时间复杂度:O(N)

向上调整算法的时间复杂度

向上调整算法的时间复杂度比向下调整算法要慢;如何得出呢?
同样的情况,利用的满二叉树来估计其时间复杂度;
在这里插入图片描述
a1(1-q^n)/(1-q)
得出T(N)=2^h *(h-1) - 2*(1-2^h)进行化简:T(N)=2^h*(h+1)-2

可转化为:
T(N)=2^h*(h+1)-2=(N+1)*(log2(N+1)+1)-2(ps:Log2(n+1)是log以2为底,n+1为对数)
T(N)=(N+1)*(log2(N+1)+1)-2 ~ N*log2(N)(ps:Log2(N)是log以2为底,N为对数)
所以向上调整算法的时间复杂度是:O(N*logN)

堆的应用

堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

1. 建堆
- 升序—建大堆
- 降序—建小堆
2. 利用堆删除思想来进行排序

建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
就拿升序来说:
实现升序怎么完成呢?
首先要明白升序是建大堆;
在这里插入图片描述

在这里插入图片描述
具体代码实现:

void HeapSort(int* a, int n)
{
	// 升序 -- 建大堆
	// 降序 -- 建小堆
	
	// 建堆--向上调整建堆--O(N*logN)
	//for (int i = 1; i < n; i++)
	//{
	//	AdjustUp(a, i);
	//}

	// 建堆--向下调整建堆 --O(N)
	for (int i = (n-1-1)/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;
	}
}

TOP—K问题

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

1. 用数据集合中前K个元素来建堆
- 前k个最大的元素,则建小堆
- 前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
举个列子:有随机100万个数储存在堆中(条件:每个元素都在int的范围内),现在需要找出最大或最小的5个数,如果全部进行堆排序,计算机的要算炸了,肯定不行;这就需要我们上面的思路:
如果要求出这组数据中最大的前K个数,就取数据的前K个元素来建小堆,假设是在所有数据中最大的前K个;在剩下的n-k个数据中,依次对小堆的第一个元素进行比较,如果比小堆的第一个元素还小就不可能是所有数据中最大的K个中的一个,如果比小堆的第一个元素大,那就让其代替小堆第一个的位置,在进行向下调整,让小堆中最小的那个元素到堆顶来,然后就再次比较下一个,直到所有元素都比完,那就代表堆中剩余的K个元素就是所求的前K个最大的元素。

在这里插入图片描述
但是:这样就行了么?在100万个数字里,有什么依据肯定一定输出的就是所有元素中的前K个最大元素呢?
所以我们需要检测的话就需要赋值几个特定的值,假如在设定输出随机值的时候肯定会有其限制:在1~100万的范围里生成随机值,我们赋值几个大于100万的数字就可以判断有没有成功找出最大的K个值;
假如我设定几个值

	a[5] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[531] = 1000000 + 3;
	a[5121] = 1000000 + 4;
	a[115] = 9999999;

代码的实现:

void PrintTopK(int* a, int n, int k)
{
	// 1. 建堆--用a中前k个元素建堆
	int i = 0;
	for (i = (k - 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--;
	}

	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	for (i = n-k-1; i < n; i++)
	{
		if (a[i] > a[0])
		{
			a[0] = a[i];
			Adjustdown(a, k, 0);//向下调整算法
		}
	}
	//打印出来
	for (i = 0; i < k; i++)
	{
		printf("%d\n", a[i]);
	}
}

void TestTopk()
{
	int n = 10000;
	int* a = (int*)malloc(sizeof(int) * n);
	if (a == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	srand(time(0));
	for (int i = 0; i < n; ++i)
	{
		a[i] = rand() % 1000000;
	}
	int k = 5;
	a[5] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[531] = 1000000 + 3;
	a[5121] = 1000000 + 4;
	a[115] = 9999999;
	
	PrintTopK(a, n, k);
}

int main()
{
	TestTopk();
	return 0;
}

最后会打印出来:如果就是你设置的那K个值,那就代表已经实现了TOP-K问题

在这里插入图片描述

链式二叉树

在前面的文章中我们是使用数组的方式实现二叉树,虽然物理存储是和二叉树一样的,但是并不直观的可以感受到;下面我们来使用链表的方式来实现堆,可以更直观的实现二叉树;

二叉树的节点:

二叉树数的每个节点至多有两个度,所以只需要一左一右指针用来链接其子节点就可以完成链式二叉树;

typedef int Treetypedef;

typedef struct TreeNode
{
    Treetypedef val;
    struct TreeNode* left;
    struct TreeNode* right;
}TNode;

初始化节点

初始化节点,设置节点值;

TNode* Node(Treetypedef n)
{
    TNode* ret = (TNode*)malloc(sizeof(TNode));
    if (ret == NULL)
    {
        perror("malloc");
        exit(-1);
    }

    ret->left = NULL;
    ret->right = NULL;
    ret->val = n;
}

实现链式二叉树

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

假如要实现下列二叉树:
在这里插入图片描述

    struct TreeNode* node1 = Node(10);
    struct TreeNode* node2 = Node(15);
    struct TreeNode* node3 = Node(56);
    struct TreeNode* node4 = Node(25);
    struct TreeNode* node5 = Node(30);
    struct TreeNode* node6 = Node(70);

    node1->left = node2;
    node1->right = node3;
    node2->left = node4;
    node2->right = node5;
    node3->left = node6;

注意:这并不是建造链式二叉树的方式,只是为了今天二叉树的遍历问题,而简易直接搭建的链式二叉树。

二叉树的概念:

二叉树有两种是:
1.空树。
2.由根节点,根节点的左子树、根节点的右子树组成的。

在这里插入图片描述
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

二叉树的遍历

二叉树的遍历就是依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。
遍历可分成一下几种:
1. 前序遍历:先访问根节点再访问左子树最后访问右子树;
2. 中序遍历:先访问左子树再访问根节点最后访问右子树;
3. 后序遍历:先访问左子树再访问右子树最后访问根节点;
4. 层序遍历:以层来访问,一层一层往下访问,每一层是从左往右访问;

前序遍历

先访问根节点再访问左子树最后访问右子树
访问顺序:根节点—左子树—右子树

// 二叉树前序遍历
void PreOrder(TNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

在这里插入图片描述

中序遍历

先访问左子树再访问根节点最后访问右子树
访问顺序:左子树—根节点—右子树

// 二叉树中序遍历
void InOrder(TNode* root)
{
    if (root == NULL)
    {
        printf("NULL ");
        return;
    }

    InOrder(root->left);
    printf("%d ", root->val);
    InOrder(root->right);
}

在这里插入图片描述

后序遍历

先访问左子树再访问右子树最后访问根节点
访问顺序:左子树—右子树—根节点

// 二叉树后序遍历
void PostOrder(TNode* root)
{
    if (root == NULL)
    {
        printf("NULL ");
        return;
    }

    PostOrder(root->left);
    PostOrder(root->right);
    printf("%d ", root->val);
}

在这里插入图片描述

层序遍历

以层来访问,一层一层往下访问,每一层是从左往右访问

如其访问结果应该是:
10 15 56 25 30 70 NULL NULL NULL NULL NULL NULL NULL

该便利需要利用队列来一起实现,在本篇文章就不详解了,在下一篇中我会详细解释;


总结


到了最后:感谢支持

我还想告诉你的是:
------------对过程全力以赴,对结果淡然处之
也是对我自己讲的

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

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

相关文章

ThreadLocal线程局部变量

1.原理 ThreadLocal是用来保存当前线程数据的&#xff0c;每一个线程的内部都有一个ThreadLocalMap&#xff0c;当前这个map中存储了以当前ThreadLocal作键&#xff0c;具体的数据作值的一个个Entry对象。 为什么非得以ThreadLocal对象作键呢&#xff1f;因为一个线程可能使用了…

手游联运平台是什么?

手游联运平台是一种服务于手游联运的专业平台&#xff0c;旨在为游戏开发商、发行商和代理商提供联运合作所需的技术、工具和资源。这些平台通常提供以下功能和服务&#xff1a; 游戏接入和管理&#xff1a;允许游戏开发商将他们的游戏接入联运平台&#xff0c;以便发行到不同的…

linux 环境变量详解/etc/proflie

Linux 环境变量是可以在多个文件中进行配置的&#xff0c;如/etc/proflie&#xff0c;/etc/profile.d/*.sh&#xff0c;~/.bashrc&#xff0c;~/.bash_profile等但是这些之间有什么区别呢 bash的运行模式可以分为 login shell 和 non-login shell。 例如&#xff1a;通过终端&a…

【送书活动】强势挑战Java,Kotlin杀回TIOBE榜单Top 20!学Kotlin看哪些书?

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

王道考研操作系统

王道考研操作系统 计算机系统概述操作系统的概念操作系统的特征操作系统的发展历程操作系统内核中断和异常![在这里插入图片描述](https://img-blog.csdnimg.cn/162452b4c60144e0bd500e180127c447.png)系统调用操作系统结构虚拟机错题 进程与线程进程控制进程通信线程和多线程模…

Python+Selenium定位不到元素常见原因及解决办法(报:NoSuchElementException)

这篇文章主要介绍了PythonSelenium定位不到元素常见原因及解决办法(报&#xff1a;NoSuchElementException),文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值&#xff0c;需要的朋友们下面随着小编来一起学习学习吧 在做web应用的自动…

elasticsearch17-自动补全

个人名片&#xff1a; 博主&#xff1a;酒徒ᝰ. 个人简介&#xff1a;沉醉在酒中&#xff0c;借着一股酒劲&#xff0c;去拼搏一个未来。 本篇励志&#xff1a;三人行&#xff0c;必有我师焉。 本项目基于B站黑马程序员Java《SpringCloud微服务技术栈》&#xff0c;SpringCloud…

2023/9/17总结

Vue defineOptions 为什么要使用defineOptions 在有<script setup> 之前 如果需要定义props emit 可以很容易的添加一个与setup 平级的属性 但是用了 <script setup> 后 就不能这样做了 setup 属性也就没有了&#xff0c;就不能添加 与其平级 的属性 为了解…

基于springboot+vue的网上商城系统

一、选题背景意义 &#x1f30a;项目背景&#xff1a;该电子商城系统旨在为商家和消费者提供一个直观、易用的购物平台&#xff0c;通过该平台销售商品和宣传品牌&#xff0c;消费者通过该平台购买商品&#xff0c;享受更便捷的购物体验。在该电子商城系统中&#xff0c;商家可…

腾讯Behaviac Designer 和Unity连调行为树

1. 克隆源码 https://github.com/Tencent/behaviac/ 2. 编译生成BehaviacDesigner.exe 3. 找到并打开BehaviacDesigner.exe&#xff08;先不急着填弹出的路径workspace 设置框&#xff09; 4. 新建一个Unity 空工程&#xff0c;并在此处下载behaviac unitypackage 5. Unity中…

C#引用Microsoft.Office.Interop.Excel

1.添加相关包 在项目的引用上&#xff0c;鼠标右键点击&#xff0c;选择管理“NuGet程序包”&#xff0c;如下图所示。 2.搜索Microsoft.Office.Interop.Excel 打开后&#xff0c;按照下图所示进行操作。 3.查看引用 此时&#xff0c;在引用中&#xff0c;可以看见&#xff0c;…

K8s(Kubernetes)学习(五)——Service:ClusterIP、NodePort、LoadBalancer、 ExternalName

第五章 Service 什么是 Service为什么需要 ServiceService 特性Service 与 Pod 关联Service type 类型如何使用 Service多端口配置 1 什么是 Service 1.1 定义 官网地址: https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/ 将运行在一个或一组 Pod…

怎么推广自己抖店的商品?最适合0经验新手操作的办法,来看看

我是王路飞。 抖店开通后&#xff0c;想要把自己店铺的商品卖出去&#xff0c;就需要进行推广了。 但是怎么推广呢&#xff1f; 要么利用抖音的搜索和推荐流量&#xff0c;获取曝光&#xff0c;实现点击和转化。 不过这种玩法有个弊端&#xff0c;就是需要你有一定的电商经…

分布式事务-介绍

个人名片&#xff1a; 博主&#xff1a;酒徒ᝰ. 个人简介&#xff1a;沉醉在酒中&#xff0c;借着一股酒劲&#xff0c;去拼搏一个未来。 本篇励志&#xff1a;三人行&#xff0c;必有我师焉。 本项目基于B站黑马程序员Java《SpringCloud微服务技术栈》&#xff0c;SpringCloud…

K8S:pod资源限制及探针

文章目录 一.pod资源限制1.pod资源限制方式2.pod资源限制指定时指定的参数&#xff08;1&#xff09;request 资源&#xff08;2&#xff09; limit 资源&#xff08;3&#xff09;两种资源匹配方式 3.资源限制的示例&#xff08;1&#xff09;官网示例&#xff08;2&#xff0…

QSlider风格设置

QT的滑动条在开发的过程中经常使用&#xff0c;默认的QSlider风格比较简陋&#xff0c;一般达不到UI设计的效果&#xff0c;本篇记录一个QSlider使用过程中风格的设置。 1.qss常用的字段属性 1.1背景属性 属性值意思background Background背景属性background-colorBrush背景…

【探索C++】string类详解

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…

pytest一些常见的插件

Pytest拥有丰富的插件架构&#xff0c;超过800个以上的外部插件和活跃的社区&#xff0c;在PyPI项目中以“ pytest- *”为标识。 本篇将列举github标星超过两百的一些插件进行实战演示。 插件库地址&#xff1a;http://plugincompat.herokuapp.com/ 1、pytest-html&#xff1…

浏览器缓存学习笔记

浏览器存储 示例&#xff1a;网页的搜索历史 application->Local Storage&#xff0c;数据通过key-value保存 保存数据 <button onclick"saveData()">点击保存数据</button><script type"text/javascript">let p { name: Jack, ag…

智慧食堂操作教程,建议收藏!

医院食堂作为医疗机构不可或缺的一部分&#xff0c;不仅要提供高质量、健康的餐饮选择&#xff0c;还需要为患者和医护人员提供便捷的用餐体验。 随着科技的不断发展&#xff0c;智慧收银系统应运而生&#xff0c;它已经在医疗机构中引起了广泛关注。智慧收银系统不仅为患者和医…