二叉树-堆(9.10)

接上节内容

目录

3.3 堆的实现

3.2.1 堆向下调整算法

3.2.2大堆的创建

3.4 堆的应用

3.4.1 堆排序

3.4.2 TOP-K问题

​编辑

二叉树的性质

练习

4.二叉树链式结构的实现

4.1 前置说明

4.2二叉树的遍历

4.2.1 前序、中序以及后序遍历

4.3 节点个数以及高度等

4.3.1二叉树节点个数

4.3.2二叉树叶子节点个数

4.3.3二叉树第k层节点个数

4.4练习


完全二叉树/满二叉树 的存储方式为数组存储,而 非完全二叉树/满二叉树 如果使用数组存储会有许多空位,造成空间的浪费。

完全二叉树/满二叉树 节点的下标之间的关系:

leftchild = parent *2+1

rightchild = parent *2+2

parent = (child-1)/2

大堆:任何父亲>=孩子

小堆:任何父亲<=孩子

孩子之间没有关系

小堆的插入:

在数组上尾插,再判断与所有祖先节点的大小,刚好大于父亲节点,无需改变。

 在数组上尾插,发现小于父亲节点,就要找到父亲节点并与它交换位置(数组中和逻辑结构中均交换)

 小于哪一个祖先节点就当它的父节点,其余子孙节点依次往下移。

3.3 堆的实现

3.2.1 堆向下调整算法

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整 成一个 小堆 。向下调整算法有一个 前提:左右子树必须是一个小堆,才能调整。
 
int array [] = { 27 , 15 , 19 , 18 , 28 , 34 , 65 , 49 , 25 , 37 };

 父节点不断向下遍历,找到最小的子节点与其交换值,直到叶节点。

//数据向下调整(找较小的孩子往上推)
void AdjustDown(HPDataType* a,int n, int parent)
{
	//假设较小孩子为左孩子
	int child = parent * 2 + 1;
	while (child<n)
	{
	//如果右孩子更小
	//child+1必须小于n,否则当child=n-1,child+1就越界
	    if (child+1<n&&a[child + 1] < a[child])
	    {
		child++;
	    }
		if (a[child] < a[parent])
		{
			//较小的子节点向上挪
			Swap(&a[child], &a[parent]);
			//父节点向下移动继续判断
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

3.2.2大堆的创建

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个大堆,现在我们通过算法,把它构建成一个大堆。如果用向下调整法,要求左右子树都是大堆,
可以观察到根节点左右子树都不是大堆,我们怎么调整呢?
叶节点只有一个值,我们可以随意把它看成是一个大堆或者小堆,因此无需调整, 这里我们从  倒数的第一个非叶节点的子树    开始往   根节点    向下调整,一直调整到根节点的树,就可以调整成堆。

//向下调整建堆
	for (int i = (n - 1-1) / 2; i >= 0; i--)
	{
		//从最后一个父节点开始往第一个节点向下调整
		AdjustDown(a, n, i);
	}

3.4 堆的应用

3.4.1 堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆
升序:建大堆
堆顶根最后一个位置交换,最大的值就排好了
剩下的数据依次向下调整,依次选出次大的,代价为 logN.
降序:建小堆
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;
	}
}

3.4.2 TOP-K问题

TOP-K 问题:即求数据结合中前 K 个最大的元素或者最小的元素,一般情况下数据量都比较大
比如:专业前 10 名、世界 500 强、富豪榜、游戏中前 100 的活跃玩家等。
对于 Top-K 问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了 ( 可能 数据都不能一下子全部加载到内存中) 。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前 K 个元素来建堆
k 个最大的元素,则建小堆,这样前k个最大元素一定能进入到
k 个最小的元素,则建大堆
2. 用剩余的 N-K 个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余 N-K 个元素依次与堆顶元素比完之后,堆中剩余的 K 个元素就是所求的前 K 个最小或者最大的元素。

用堆来解决TOP-K问题在效率和空间上都堪称完美。

问:我们如何直到自己用程序找出来的TOP-K是否正确?

在给全部数据赋随机值时,给它们都模一个数(例如1000000),也就是所有的随机值都小于1000000,然后将任意几个数的值改为大于1000000,检查结果中是否有这几个值即可。

实现:

//创文件并生成随机值
void CreateNDate()
{
	//总数据个数
	int n = 10000000;
	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() + i) % 10000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}
//找出TopK
void PrintTopK(const char* filename,int k)
{
	// 1. 建堆--用a中前k个元素建堆
	
	//读文件
	FILE* fout = fopen(filename,"r");
	if (fout == NULL)
	{
		perror("fopen error");
		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]);
	}
	//将数组中的数据向下调整建堆
	//从最后一个父节点开始调整
	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])
		{
			//替换x进堆
			minheap[0] = x;
			//向下调整,去合适的位置
			AdjustDown(minheap, k, 0);
		}
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);
	}
	printf("\n");
	//释放数组空间,关闭文件
	free(minheap);
	fclose(fout);
}
int main()
{
	//建文件
	CreateNDate();
	PrintTopK("data.txt", 10);

	return 0;
}

二叉树的性质

重要结论:n0=n2+1;

增加一个n2等于减少一个n0,增加两个n0,增加一个n2,因此增加一个n2就等于增加一个n0。

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

练习

n0=n2+1=200;

A

关键点:完全二叉树

完全二叉树度为1的节点有一个或零个,

n0+n1+n2=2n

n0=n2+1

n0+n1+n0-1=2n

n1只能为奇数,即1

一颗高度为h的满二叉树的总节点数为2^(h-1),当h为9时,满二叉树有512个节点,因此此二叉树高度为10,为不满10层的完全二叉树。

完全二叉树n1为0或者1    此处n1=0

n0+n2=767

n0=n2+1

2n0-1=767

n=384

4.二叉树链式结构的实现

4.1 前置说明

堆能够实现排序,TOPK等问题,而非完全二叉树/满二叉树没有任何实际意义,他们的用处在于

1.为更复杂的二叉树搜索树AVL (红黑树)打基础

例如搜索树:左子树总是小于右子树,能实现而二分查找类似的功能(中序),排序(先序)

2.很多二叉树的题在这上面出

4.2二叉树的遍历

4.2.1 前序、中序以及后序遍历

完全二叉树通过数组存储和遍历,非完全二叉树通过不同的方式储存和遍历。

遍历非完全二叉树我们要习惯于将一个二叉树无限拆分为 根,左子树和右子树

1. 前序遍历(Preorder Traversal 亦称先序遍历 )—— 访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历 (Inorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历 (Postorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之后。

实现:

定义,创建,初始化

//定义二叉树节点
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	int val;
}BTNode;
//创建节点
BTNode* BuyNode(int x)
{
	//申请空间
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	//初始化节点
	node->val= x;
	node->left = NULL;
	node->right = NULL;
	
	return node;
}

前序遍历:

通过递归调用的方式来实现,当节点不为空时,调用本函数来访问左子节点,参数为左子节点,在栈帧上额外开辟一块空间用来存放数据,再以同样的方式访问右子节点,直到下一个节点为空,就逐步递归返回值。

//前序遍历
void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%d ", root->val);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

以此类推写出中序遍历

//中序遍历
void Postrder(BTNode* root)
{
	//节点为空,返回
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	
	//先左 后根 再右
	PrevOrder(root->left);
    printf("%d ", root->val);
	PrevOrder(root->right);
}

4.3 节点个数以及高度等

4.3.1二叉树节点个数

采用分治的思想,每个节点负责计算出本身以及其左右子树的节点之和,因此依旧采用递归的方法。

// 二叉树节点个数
int TreeSize(BTNode* root)
{
	//节点如果为空,返回0,不为空返回其左右子树的节点之和再加本身(1)
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

4.3.2二叉树叶子节点个数

1.空树没有叶子  reutrn 0

2.左右子树为空就为叶子   return  1

3.左子树中叶子节点个数+右子树中叶子节点个数

// 二叉树叶子节点个数
int TreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left ==NULL&& root->right == NULL)
	{
		return 1;
	}
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

4.3.3二叉树第k层节点个数

当前树的第k层等于左子树的第k-1层和右子树的第k-1层节点数之和

当k=1则为要求的节点本身,返回1

k应该>=1

// 二叉树第k层节点个数
int TreeLevelKSize(BTNode* root, int k)
{
	assert(k > 0);
	if (root == NULL)
	{
		return 0;
	}
	//K=1则为本身
	if (k == 1)
	{
		return 1;
	}
	return TreeLevelKSize(root->left, k-1) + 
		   TreeLevelKSize(root->right, k-1);
}

4.4练习

1.965. 单值二叉树 - 力扣(LeetCode)

 什么情况返回 ture: 访问完所有节点(访问到空节点),每个节点的值都等于根节点。

什么情况返回 false :有任意一个节点存在且值不等于它的根节点。

我们只需要找出返回 false 的情况,剩下的就都默认访问到空节点返回 true 了

递归的方式我们采用 &&,即左右任意一个递归调用的返回值为 false,结果就为 false 了

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
bool isUnivalTree(struct TreeNode* root) {
    //节点不存在,直接返回true
    if(root==NULL)
    {
        return true;
    }
    //如果左子节点存在且不等于跟
    if(root->left&&root->val!=root->left->val)
     {
         return false;
     }
     //如果右子节点存在且不等于根
    if(root->right&&root->val!=root->right->val)
     {
         return false;
     }
     //如果左右子节点都等于根或者不存在,往子节点走
     //其中一个返回false,则结果为false
    return isUnivalTree(root->right)&&isUnivalTree(root->left);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算不上最全,但都是必备——Mybatis这些不会不行啊

Mybatis篇 ORM&#xff08;Object Relational Mapping&#xff09;&#xff0c;对象关系映射&#xff0c;是一种为了解决关系型数据库数据与简单Java对象&#xff08;POJO&#xff09;的映射关系的技术。简单的说&#xff0c;ORM是通过使用描述对象和数据库之间映射的元数据&am…

天气越来越寒冷,一定要注意保暖

你们那里下雪了吗&#xff1f;听说西安已经下了今年的第一场雪&#xff0c;我们这里虽然隔了几百公里&#xff0c;但是只下雨没有下雪&#xff0c;不过气温是特别的冷&#xff0c;尤其是对我们这些上班族和上学的人而言&#xff0c;不管多冷&#xff0c;不管刮风下雨&#xff0…

根据店铺ID或店铺昵称或店铺链接获取阿里巴巴店铺所有商品数据接口|阿里巴巴店铺整店商品数据接口|阿里巴巴API接口

阿里巴巴店铺所有商品数据接口是阿里巴巴开放平台提供的API接口之一&#xff0c;它可以帮助开发者获取到店铺内所有商品的信息&#xff0c;包括商品的ID、标题、价格、图片、链接等。通过该接口&#xff0c;开发者可以快速地获取到大量的商品数据&#xff0c;并进行进一步的数据…

自定义注解实现服务的动态开关

shigen日更文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 &#x1f9d1;‍&#x1f4bb;&#x1f9d1;‍&#x1f4bb;&#x1f9d1;‍&#x1f4bb;Make things differe…

matlab语言的由来与发展历程

MATLAB语言的由来可以追溯到1970年代后期。当时&#xff0c;Cleve Moler教授在New Mexico大学计算机系担任系主任&#xff0c;他为了LINPACK和EISPACK两个FORTRAN程序集开发项目提供易学、易用、易改且易交互的矩阵软件而形成了最初的MATLAB。 1984年&#xff0c;MATLAB推出了…

JVM 内存区域

JVM内存结构模型 程序计数器&#xff1a; 1.线程私有的&#xff0c;是一块较小的内存空间&#xff0c;当前线程所执行的字节码的行号指示器 2.每个线程都有一个独立的程序计数器&#xff0c;各线程之间程序计数器互不影响&#xff0c;独立存储 3.此内存区域是唯一一个在java虚拟…

C++ vector中capacity()和size() 的区别

文章目录 1 capacity()和size() 介绍2 vector满了之后&#xff0c;capacity()会自动了扩充为原来的2倍 &#xff1f; 1 capacity()和size() 介绍 size是指容器当前拥有元素的个数&#xff0c; capacity是指容器在必须分配新的存储空间之前可以存放的元素总数。 如vector<i…

Linux常用命令——bzgrep命令

在线Linux命令查询工具 bzgrep 使用正则表达式搜索.bz2压缩包中文件 补充说明 bzgrep命令使用正则表达式搜索“.bz2”压缩包中文件&#xff0c;将匹配的行显示到标注输出。 语法 bzgrep(参数)参数 搜索模式&#xff1a;指定要搜索的模式&#xff1b;.bz2文件&#xff1a…

【教3妹学编程-算法题】K 个元素的最大和

3妹&#xff1a;“太阳当空照&#xff0c;花儿对我笑&#xff0c;小鸟说早早早&#xff0c;你为什么背上炸药包” 2哥 :3妹&#xff0c;什么事呀这么开发。 3妹&#xff1a;2哥你看今天的天气多好啊&#xff0c;阳光明媚、万里无云、秋高气爽&#xff0c;适合秋游。 2哥&#x…

【前端开发】JS Vue React中的通用递归函数

目录 前言 一、递归函数的由来 二、功能实现 1.后台数据 2.处理数据 3.整体代码 总结 &#x1f642;博主&#xff1a;冰海恋雨. &#x1f642;文章核心&#xff1a;【前端开发】JS Vue React中的通用递归函数 前言 大家好&#xff0c;今天和大家分享一下在前端开发中j…

基于springboot实现校园医疗保险管理系统【项目源码】

基于springboot实现校园医疗保险管理系统演示 系统开发平台 在线校园医疗保险系统中&#xff0c;Eclipse能给用户提供更多的方便&#xff0c;其特点一是方便学习&#xff0c;方便快捷&#xff1b;二是有非常大的信息储存量&#xff0c;主要功能是用在对数据库中查询和编程。其…

旺店通·企业版对接打通金蝶云星空查询调拨单接口与分布式调入单新增接口

旺店通企业版对接打通金蝶云星空查询调拨单接口与分布式调入单新增接口 源系统:旺店通企业版 旺店通是北京掌上先机网络科技有限公司旗下品牌&#xff0c;国内的零售云服务提供商&#xff0c;基于云计算SaaS服务模式&#xff0c;以体系化解决方案&#xff0c;助力零售企业数字化…

msys2 + MSVC(VS2019)编译ffmpeg6.0源码

以前使用的v1.2版&#xff0c;很多功能和使用方法发生了变化&#xff0c;需要重新编译新的ffmpeg版。 编译环境: windows 10 , VS2019, MSYS2 1. msys2 下载安装 MSYS2 , https://www.msys2.org/ 2. msys2 环境配置打开 msys2 2.1 安装相关软件 然后输入以下命令安装&…

gmpy2 GMP is_prime函数底层c代码分析

偶然看到一篇paper&#xff08;2018年发表&#xff09;&#xff0c;说GMP中的素性检测使用的是单独的Miller_Rabin方法&#xff0c;单独的Miller_Rabin素性检测会存在部分安全问题&#xff08;低概率&#xff09;&#xff0c;然后突然想求证一下最新版本的GMP中是否进行了修改。…

Python如何使用Matplotlib模块的pie()函数绘制饼形图?

Python如何使用Matplotlib模块的pie函数绘制饼形图&#xff1f; 1 模块安装2 实现思路3 pie()函数说明4 实现过程4.1 导入包4.2 定义一个类4.3 读取数据并处理4.4 定义饼图绘制方法 5 完整源码 1 模块安装 先安装matplotlib&#xff1a; pip install matplotlib安装numpy模块…

Linux Docker 图形化工具 Portainer远程访问

文章目录 前言1. 部署Portainer2. 本地访问Portainer3. Linux 安装cpolar4. 配置Portainer 公网访问地址5. 公网远程访问Portainer6. 固定Portainer公网地址 前言 Portainer 是一个轻量级的容器管理工具&#xff0c;可以通过 Web 界面对 Docker 容器进行管理和监控。它提供了可…

exsi的安装和配置

直接虚拟真实机 vcent server 管理大量的exsi SXI原生架构模式的虚拟化技术&#xff0c;是不需要宿主操作系统的&#xff0c;它自己本身就是操作系统。因此&#xff0c;装ESXI的时候就等同于装操作系统&#xff0c;直接拿iso映像(光盘)装ESXI就可以了。 VMware vCente…

在Vue.js中,什么是slot(插槽)?它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

2.6 Windows驱动开发:使用IO与DPC定时器

本章将继续探索驱动开发中的基础部分&#xff0c;定时器在内核中同样很常用&#xff0c;在内核中定时器可以使用两种&#xff0c;即IO定时器&#xff0c;以及DPC定时器&#xff0c;一般来说IO定时器是DDK中提供的一种&#xff0c;该定时器可以为间隔为N秒做定时&#xff0c;但如…

万宾科技内涝积水监测仪效果,预警城市积水

当城市之中出现强降雨或者大暴雨&#xff0c;可能会导致雨水不断堆积到城市排水管网之中&#xff0c;可能还会淹没城市的排水系统时&#xff0c;这种现象被称为城市之中的内涝&#xff0c;并且在许多城市之中内涝问题日益引起人们的关注。 内涝积水监测仪的出现成为了希望的灯塔…