二叉树【树的基本概念】

news2025/1/10 11:28:05

全文目录

    • 树的概念
    • 树的相关概念
    • 树的表示
    • 树的实际应用
  • 二叉树
    • 二叉树的概念
    • 二叉树的特殊类型
    • 二叉树的性质
    • 二叉树的存储结构
      • 顺序存储
      • 链式存储
    • 堆的概念
    • 向下调整算法
    • 向上调整算法
    • 堆的插入
    • 堆的删除
    • 堆的构建
      • 时间复杂度计算
    • 堆排序
    • TOP-K问题

树的概念

树是一种非线性的数据结构,它是由 n ( n > = 0 ) n(n>=0) nn>=0个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

它具有以下的特点:

  • 有一个特殊的结点,称为根结点,根节点没有前驱结点
  • 除根节点外,其余结点被分成 M ( M > 0 ) M(M>0) M(M>0)个互不相交的集合 T 1 、 T 2 、 … … 、 T m T_1、T_2、……、T_m T1T2……Tm,其中每一个集合 T i ( 1 < = i < = m ) T_i(1<= i <= m) Ti(1<=i<=m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继

在这里插入图片描述

注意:

成环的树是图,是另一种数据结构

树的相关概念

在这里插入图片描述

  • 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
  • 叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点
  • 非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G…等节点为分支节点
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
  • 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
    节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
  • 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
  • 森林:由m(m>0)棵互不相交的树的集合称为森林;

树的表示

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既要保存值域,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法

typedef int DataType;
struct Node
{
	struct Node* _firstChild1; // 第一个孩子结点
	struct Node* _pNextBrother; // 指向其下一个兄弟结点
	DataType _data; // 结点中的数据域
};

在这里插入图片描述

树的实际应用

文件系统的目录等就是树的一种应用:

在这里插入图片描述

二叉树

二叉树的概念

二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。二叉树特点是每个节点最多只能有两棵子树,且有左右之分 。

二叉树是递归定义的,其节点有左右子树之分,逻辑上二叉树有五种基本形态:
在这里插入图片描述

  1. 空二叉树——如图1(a)
  2. 只有一个根节点的二叉树——如图1(b)
  3. 只有左子树——如图1(c)
  4. 只有右子树——如图1(d)
  5. 完全二叉树——如图1(e)

二叉树的特殊类型

  1. 满二叉树:如果一棵二叉树只有度为0的节点和度为2的节点,并且度为0的节点在同一层上,则这棵二叉树为满二叉树 。
  2. 完全二叉树:深度为k,有n个节点的二叉树当且仅当其每一个节点都与深度为k的满二叉树中编号从1到n的节点一一对应时,称为完全二叉树 。

完全二叉树的节点数量:

高度为h的完全二叉树的节点个数在 [ 2 h − 1 , 2 h − 1 ] [2^{h - 1}, 2^h - 1] [2h1,2h1]

完全二叉树的特点是叶子节点只可能出现在层序最大的两层上,并且某个节点的左分支下子孙的最大层序与右分支下子孙的最大层序相等或大1 。

在这里插入图片描述

二叉树的性质

性质1: 二叉树的第 i i i 层上至多有 2 i − 1 ( i ≥ 1 ) 2^i-1(i≥1) 2i1i1个节点 。

性质2: 深度为 h h h 的二叉树中至多含有 2 h − 1 2^h-1 2h1 个节点 。

性质3: 若在任意一棵二叉树中,有 n 0 n_0 n0个叶子节点,有 n 2 n_2 n2 个度为 2 2 2的节点,则必有 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1

性质4: 具有 n n n 个节点的满二叉树深为 l o g 2 ( n + 1 ) log_2^{(n+1)} log2(n+1)

性质5: 若对一棵有 n n n 个节点的完全二叉树进行顺序编号 ( 1 ≤ i ≤ n ) (1≤i≤n) 1in ,那么,对于编号为 i ( i ≥ 1 ) i(i≥1) ii1的节点:

  1. i > 0 i>0 i>0 i i i 位置节点的双亲序号: ( i − 1 ) / 2 ; i = 0 , i (i-1)/2;i=0,i (i1)/2i=0i 为根节点编号,无双亲节点
  2. 2 i + 1 < n 2i+1<n 2i+1<n ,左孩子序号: 2 i + 1 , 2 i + 1 > = n 2i+1,2i+1>=n 2i+12i+1>=n 否则无左孩子
  3. 2 i + 2 < n 2i+2<n 2i+2<n,右孩子序号: 2 i + 2 , 2 i + 2 > = n 2i+2,2i+2>=n 2i+22i+2>=n 否则无右孩子

在这里插入图片描述

二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

顺序存储

顺序结构存储就是利用性质五使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。后面会用到的堆就是使用这种形式存储的。

在这里插入图片描述
在这里插入图片描述

链式存储

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。

二叉链: 链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该节点左孩子和右孩子所在的链节点的存储地址

在这里插入图片描述

// 二叉链
typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
	struct BinTreeNode* _pLeft; // 指向当前节点左孩子
	struct BinTreeNode* _pRight; // 指向当前节点右孩子
	BTDataType _data; // 当前节点值域
}

三叉链: 在二叉链的基础上增加父节点的存储地址

在这里插入图片描述

// 三叉链
struct BinaryTreeNode
{
	struct BinTreeNode* _pParent; // 指向当前节点的双亲
	struct BinTreeNode* _pLeft; // 指向当前节点左孩子
	struct BinTreeNode* _pRight; // 指向当前节点右孩子
	BTDataType _data; // 当前节点值域
}

堆的概念

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个结点的值总是不大于或不小于其父结点的值;
  • 堆总是一棵完全二叉树。

通常我们使用顺序结构的数组来存储。

将根结点最大的堆叫做最大堆或大根堆

在这里插入图片描述

根结点最小的堆叫做最小堆或小根堆

在这里插入图片描述

向下调整算法

向下调整算法是在堆的前提下维护堆的特性

算法思想:

选取子节点中较大或者较小的一个(大跟堆选较大,小跟堆选较小),如果大于或者小于当前节点即交换父子节点的值(大跟堆取大于,小跟堆取小于),依次迭代,直到子节点超过了堆的大小。

在这里插入图片描述

// 向下调整算法
void AdjustDown(HeapDataType* data, size_t parent, size_t size)
{
	assert(data);

	size_t child = parent * 2 + 1;
	
	while (child < size)
	{
		//if (child + 1 < size && data[child + 1] < data[child]) // 小堆
		if (child + 1 < size && data[child + 1] > data[child]) // 大堆
			child++;

		//if (data[child] < data[parent])		// 小堆
		if (data[child] > data[parent])		// 大堆
		{
			Swap(&data[child], &data[parent]); 
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

向上调整算法

在堆的基础上维护堆,向堆尾插入节点时,需要向上维护堆。

算法思想:

如果当前节点是否大于或小于父节点(大跟堆取大于,小跟堆取小于),交换父子节点,依次迭代,直到子节点成为根节点

在这里插入图片描述

// 向上调整算法
void AdjustUp(HeapDataType* data, size_t child)
{
	assert(data);

	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		if (data[child] > data[parent])	// 大堆
		//if (data[child] < data[parent])		// 小堆
		{
			Swap(&data[child], &data[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

堆的插入

在堆的基础上进行插入,插入后使用向上调整算法维护堆。

// 堆的插入
void HeapPush(Heap* heap, HeapDataType x)
{
	assert(heap);
	
	// 扩容检查
	CheckCapacity(heap);
	
	heap->data[heap->size] = x;
	AdjustUp(heap->data, heap->size);
	heap->size++;
}

在这里插入图片描述

堆的删除

将堆顶堆底元素进行交换,删除最后一个元素,并对堆顶进行向下调整,维护堆的性质:

在这里插入图片描述

// 堆的删除
void HeapPop(Heap* heap)
{
	assert(heap);
	assert(heap->size);

	heap->size--;
	Swap(&heap->data[heap->size], &heap->data[0]);
	AdjustDown(heap->data, 0, heap->size);
}

堆的构建

当然可以从零开始构建一个堆,这样就是正常的HeapPush 和 HeapPop,没什么好说的。要说的是在原数组的基础上进行构建。

当给出一个数组,,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。

在这里插入图片描述

// 构建堆
for (int i = (size - 1 - 1) / 2; i >= 0; i--)
	AdjustDown(data, i, size - 1);

时间复杂度计算

建堆的时间复杂度需要计算每个细节,看似是 O ( n l o g n ) O(nlog^n) O(nlogn) 但其实是 O ( N ) O(N) O(N)

需要操作多少个节点,每个节点需要进行多少次操作:

在这里插入图片描述

堆排序

堆排序就是堆的基础上实现的,分为一下几个步骤:

  1. 建堆
  2. 排序

堆排序使用的是 HeapPop 操作,选取一个最大或者最小的数与尾部,操作 n n n 次。

 // 堆排序
void HeapSort(HeapDataType* data, int size)
{
	assert(data);
	
	// 建堆
	for (int i = (size - 1 - 1) / 2; i >= 0; i--)
		AdjustDown(data, i, size - 1);
	
	// 排序
	while (size > 0)
	{
		Swap(&data[0], &data[size - 1]);
		size--;
		AdjustDown(data, 0, size);
	}
}

需要注意的是:升序建大堆,降序建小堆

TOP-K问题

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

一般想到的是排序,排序的时间复杂度是: O ( n l o g n ) O(nlog^n) O(nlogn)

如果数据量是100亿个整数:

1GB = 1024MB
1024MB = 1024 * 1024KB
1024 * 1024KB = 1024 * 1024 * 1024byte ≈ 10亿
100亿个整数 = 400亿字节 = 40GB内存

很显然不可能有这么大的内存,那么最佳的方式就是用堆来解决,堆的优势就展现出来了,我们只需要在内存中存放前 K K K 个数即可:

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

时间复杂度: O ( K + ( N − k ) l o g K ) O(K + (N - k)log^K) O(K+(Nk)logK)

// TopK问题
void TopK(HeapDataType* data, int n, int k)
{
	assert(data);

	HeapDataType* a = (HeapDataType*)malloc(sizeof(HeapDataType) * k);
	assert(a);

	// 建堆
	for (int i = 0; i < k; i++) a[i] = data[i];
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
		AdjustDown(a, i, k);

	// TopK
	for (int i = k; i < n; i++)
	{
		if (data[i] >= a[0])
		{
			a[0] = data[i];
			AdjustDown(a, 0, k);
		}
	}

	for (int i = 0; i < k; i++) printf("%d ", a[i]);
}

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

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

相关文章

华为CD32键盘使用教程

华为CD32键盘使用教程 用爱发电写的教程&#xff01; 最后更新时间&#xff1a;2023.9.12 型号&#xff1a;华为有线键盘CD32 基本使用 此键盘在不安装驱动的情况下可以直接使用&#xff0c;但是不安装驱动指纹识别是无法使用的&#xff01;并且NFC功能只支持华为的部分电脑…

固定资产预算怎么管理的

在现代企业管理中&#xff0c;固定资产预算的管理是一项至关重要的任务。它不仅关系到企业的经济效益&#xff0c;更关系到企业的长远发展。那么&#xff0c;如何进行有效的固定资产预算管理呢&#xff1f; 明确固定资产预算的目标和原则  我们需要明确固定资产预算的目标和…

HCS 中的一些概念(二)

一、Service OM 1、首页&#xff08;资源状态&#xff09; 2、服务列表 计算资源&#xff1a;计算资源又分为可用分区&#xff08;AZ&#xff09;、规格和虚拟机组&#xff0c;可在此处创建虚拟机、虚拟机组、主机组和规格 网络资源&#xff1a;网络资源又分为物理网络…

使用融云 CallPlus SDK,一小时实现一款 1V1 视频应用

9 月 21 日&#xff0c;融云直播课 社交泛娱乐出海最短变现路径如何快速实现一款 1V1 视频应用&#xff1f; 欢迎点击小程序报名~ 1V1 音视频、远程服务类应用的实现利器——融云 CallPlus SDK 上线&#xff01; 关注【融云全球互联网通信云】了解更多 作为新一代音视频通话场…

Transformer模型 | 个人理解

一、Transformer整体架构图 二、Encoder端的输入 以机器翻译任务为例子&#xff0c;训练数据是法语句子“Je suis etudiant”和翻译成英文后的句子“I am a student”。 Inputs是法语句子“ J e {Je } Je s u i s {suis} suis e t u d i a n t {etudiant} etudiant”&#x…

9月16日相约openGauss Meetup(杭州站)

由云和恩墨、图尔兹、浙江鲲鹏、openGauss社区联合主办的“openGauss Meetup &#xff08;杭州站&#xff09;”活动将于9月16日在杭州市拱墅区祥园路108号中国智慧信息产业园G座3楼连廊1号会议室举办&#xff01;我们诚邀您的莅临&#xff01; 扫码报名 数据库作为企业核心的重…

Jetty服务器好处

Jetty可以同时处理大量连接而且可以长时间保持连接&#xff0c;适合于web聊天应用等等。 Jetty的架构简单&#xff0c;因此作为服务器&#xff0c;Jetty可以按需加载组件&#xff0c;减少不需要的组件&#xff0c;减少了服务器内存开销&#xff0c;从而提高服务器性能。 Jetty默…

最新遥感数据与作物模型同化教程

详情点击公众号链接&#xff1a;最新遥感数据与作物模型同化教程一&#xff1a;遥感基础1.遥感平台&#xff08;如无人机&#xff09;与传感器、国内外主要陆地卫星&#xff08;如Landsat、SPOT、HJ、GF&#xff09; 2.遥感基本原理、光谱响应函数、遥感数据处理流程 3.遥感在陆…

接口自动化测试的概述及流程梳理~

接下来开始学习接口自动化测试。 因为之前从来没接触过&#xff0c;所以先了解一些基础知识。 1.接口测试的概述 2.接口自动化测试流程。 接口测试概述 接口&#xff0c;又叫API&#xff08;Application Programming Interface&#xff0c;应用程序编程接口&#xff09;&a…

Java“牵手”天猫商品详情数据,天猫商品详情接口,天猫API接口申请指南

天猫平台API是天猫应用程序编程接口的缩写&#xff0c;它是一套允许程序员从他们的应用程序中访问天猫网站上数据的规范。通过API&#xff0c;程序员可以编写能够与天猫网站交互的程序&#xff0c;从而绕过网页浏览器的限制&#xff0c;直接访问和操作数据。 天猫平台API可以提…

模拟考试系统提升备考效果和成功机会

备考是每位参加考试的学生都非常重视的阶段。为了提高备考效果和增加成功机会&#xff0c;模拟考试系统是一种非常有效的工具。 备考对于任何一位参加考试的学生来说都是至关重要的阶段。无论是应对学校期末考试、入学考试还是国家级考试&#xff0c;备考都需要充分的准备和调…

怎样建立一个班级查分系统?

在现代教育中&#xff0c;建立一个高效的班级查分系统对于老师和家长们来说至关重要。物种草作为一款功能强大的在线教育工具&#xff0c;为教师们提供了一个便捷的方式来管理和分享学生成绩。本文将以物种草的口吻&#xff0c;为你介绍如何建立一个高效的班级查分系统&#xf…

javascript二维数组按指定要求进行对象合并遍历的算法开发

javascript二维数组按指定要求进行对象合并遍历的算法开发 项目原数据项目需求数据格式算法开发 项目原数据 从第三方API获取如何格式的数据&#xff1a; “device”: “二(1)班”,不同班级名称&#xff0c;会重复“name”: “二(1)班-电量”,不同班级的数据标准“value”: 1…

如何初始化静态成员在类中

c - How do I initialize a const data member? - Stack Overflow

安卓逆向 - 某东sign(基于unidbg主动调用)

本文仅供学习交流&#xff0c;只提供关键思路不会给出完整代码&#xff0c;严禁用于非法用途&#xff0c;拒绝转载&#xff0c;若有侵权请联系我删除&#xff01; 目标app&#xff1a;5Lqs5LicYXBwMTEuMy4y 目标接口&#xff1a;aHR0cHM6Ly9hcGkubS5qZC5jb20vY2xpZW50LmFjdGl…

9月1日作业

思维导图 服务器代码 #include<myhead.h>#define PORT 4567 #define IP "192.168.6.225"struct msg //接收到的客户端信息结构体 {char type;char name[20];char txt[128]; };//定义节点类型 typedef struct Node {union{struct sockaddr_in cin; //数据…

springboot+springSecurity+jwt实现登录认证后令牌授权

springbootspringSecurityjwt实现登录认证后令牌授权&#xff08;已绑定整个项目的源码&#xff09; 目录 springbootspringSecurityjwt实现登录认证后令牌授权&#xff08;已绑定整个项目的源码&#xff09;一、自定义数据源登录认证1、实现spring security中UserDetails类2、…

22.1 JavaScript 基础

1. JavaScript 1.1 简介 JavaScript(简称js): 是一种广泛应用于网页开发的脚本语言. 它被用于增强网页的交互性和动态性, 可以让开发者对网页进行操作和控制. JavaScript可用于处理用户输入, 改变网页的内容, 动态加载数据, 创建动画效果等. 它在现代的Web开发中扮演着至关重…

【GIS】栅格转面报错:ERROR 000864输入栅格: 输入不在定义的属性域内。 ERROR 000863: 无效的 GP 数据类型

问题: 栅格转面(矢量)时,ArcGIS窗口显示:ERROR 000864输入栅格: 输入不在定义的属性域内。 ERROR 000863: 无效的 GP 数据类型. 原因: 栅格转面时输入的栅格数据集的字段必须是整型. 解决办法: 使用Spatial Analyst中的转为整型工具,将栅格数据转为整型后再进行栅格转面的操作…

pycharm增加新的编译器

安装了python2和3的电脑上&#xff0c;使用pycharm时候&#xff0c;最好将2和3都加入其编译器。 方法&#xff1a; 1、File-settings... 2、如图选择&#xff0c;然后点击加号&#xff0c;添加python2或者3的exe