数据结构之树(一)

news2024/11/28 4:52:47

一.概念

边:一棵n个结点树有n-1条边

结点深度:从根到当前结点的路径的深度。

结点高度:从当前结点到叶子结点最长路径的长度。

树的性质

  • 树中的结点总数等于所有结点的度+1;
  • m叉树中第i(i>=1)层上至多可以有m^(i-1)个节点;

  • 高度为h(h>=1)的m叉树至多有(m^h  -1)/(m-1)
  • 具有n个结点的m叉树的最小高度为[logm(n(m-1)+1)]。取上限,向上取整。

 二叉树

二叉树每个结点只能有两棵子树,分别被称为左子树和右子树。

满二叉树:一棵高度为h,且含有2^h-1个结点的二叉树被称之为满二叉树

对于编号为i的结点,左子节点的编号为2i,右子节点的编号为2i+1,双亲结点的编号为[i/2](向下取整)

完全二叉树:一颗高度为h,有n个结点的完全二叉树,它的每个结点都与高度相同的满二叉树中的结点编号一一对应。

二叉排序树:(二叉查找树)树上任意结点如果存在左子树和右子树,则左子树上所有结点元素的值都小于该结点,右子树上所有结点元素的值都大于该结点。

二叉树的性质:

  • 一棵非空的二叉树,n表示结点总数,n0表示度为0的结点树,n1表示度为1的结点树,n2表示度为2 的节点数
    n=n0+n1+n2
    n=n1+2n2+1
    n0=n2+1
  • 一棵非空的二叉树的第k层最多只能有2^(k-1)个结点
  • 高度为h的二叉树最多只能有2^h-1个结点

 二.完全二叉树的存储

完全二叉树的顺序存储

 普通二叉树的顺序存储,添加空结点,补全成为完全二叉树,数组中用0或者其他特殊值填充。可能造成浪费。

 二叉树的遍历

层次遍历:利用队列先进先出的性质

// 二叉树层次遍历。
void LevelOrder(BiTree TT)
{
    SeqQueue QQ;     // 创建循环队列。

    InitQueue(&QQ);  // 初始化循环队列。

    ElemType ee = TT;  // 队列的元素是二叉树。

    InQueue(&QQ, &ee);  // 把根结点当成队列的元素入队。

    while (IsEmpty(&QQ) != 1)  // 队列不为空。
    {
        OutQueue(&QQ, &ee);  // 队头元素出队。

        visit(ee);  // 访问出队元素。

        if (ee->lchild != NULL) InQueue(&QQ, &ee->lchild);  // 如果出队元素有左结点,左结点入队。

        if (ee->rchild != NULL) InQueue(&QQ, &ee->rchild);  // 如果出队元素有右结点,右结点入队。
    }
}

先序遍历:根左右

中序遍历:左根右

后序遍历:左右根

最深的子树开始向上遍历

二叉树的实现

递归实现

// 二叉树的先序遍历。
void PreOrder(BiTree TT)
{
    if (TT == NULL) return;

    visit(TT);               // 访问子树TT的根结点。
    PreOrder(TT->lchild);    // 遍历左子树。
    PreOrder(TT->rchild);    // 遍历右子树。
}

非递归实现:利用栈的性质

前序遍历:

 由遍历序列构造二叉树

由一种遍历序列不能还原出来唯一的二叉树。

 线索二叉树:

在含有n个结点的二叉链表中,有n+1个空指针,能否利用这些空指针来存放前驱和后继结点的地址?然后像遍历链表一样方便的遍历二叉树的序列?

线索二叉树可以部分解决上述问题,加快了在序列中查找前驱和后继结点的速度,但增加了在树中插入和删除结点的难度。

typedef struct TBTNode
{
    char   data;               // 存放结点的数据元素。
    struct TBTNode* lchild;    // 指向左子结点地址的指针。
    struct TBTNode* rchild;    // 指向右子结点地址的指针。
    unsigned char ltag, rtag;   // 左右指针的类型,0-非线索指针,1-线索指针。
}TBTNode, * TBTTree;

先序线索树求后继

 

 后续线索树求前驱?????
 

二叉排序树

 二叉排序树(二叉搜索树,二叉查找树BST)一棵非空的二叉排序树具有如下性质:

  • 1.如果左子树不空,则左子树上所有结点的值都小于根节点的值
  • 2.如果右子树不为空,右子树上所有节点的值都大于根节点的值
  • 3.左右子树也分别是二叉排序树

中序排列的结果:结点时升序

 创建二叉排序树

左子树<根<右子树

1.相同的序列创建的二叉排序树是唯一的

2.同一集合创建的二叉排序树是不同的

3.用二叉树的先序遍历序列创建的二叉排序树与原数相同

删除二叉树的结点

 ASL (average Search Length)平均查找长度:

平衡二叉树:任意结点的左右高度不超过1.

 平衡因子为左子树高减去右子树高

AVL树规定每个结点的平衡因子的绝对值不大于1

LL型状态:右旋

 

 LR双旋:先左转B结点,再右旋A结点

节点的度:子树的个数

树的度:所有节点度中的最大值

叶子节点:度为0的节点

非叶子节点:度不为0的节点

层数:根节点在第一层

节点的深度:从根节点到当前节点的唯一路径上的节点总数

节点的高度:从当前节点到最远叶子节点的路径上的节点总数

有序树:树中任意节点的子节点之间有顺序关系

二叉树的特点:

  • 每个节点的度最大为2
  • 左子树和右子树是有顺序的

二叉树是有序树还是无序树?有序树

非空二叉树的性质:

 

 

 

 

 n0=floor((n+1)/2);

 

 

 

 层序遍历:利用队列的性质


    层序遍历
    1.将根节点入队
    2.循环执行以下操作,直到队列为空
        a.将队头节点A出队,访问
        b.将A的左子节点入队
        c.将A的右子节点入队

求二叉树的高度:

法一:利用递归

	int heightByRecursion(Node<T>* node)
	{
		if (node == nullptr) return 0;
		return 1 + std::max(height(node->m_left), height(node->m_right));
	}

法二:迭代版本  每一层访问完后,下一层的长度就是队列的长度

int heightByIterator(Node<T>* node)
	{
		if (node == nullptr) return 0;
		int iHeight = 0;
		int iLaySize = 1;// 每一层的元素数量  初始化第一层的长度为1

		//先将头节点入队
		std::queue<T> q;
		q.push(node);
		//循环操作,直到队列为空
		while (!q.empty()) {
			Node<T>* head = q.front();
			std::cout << head->element;
			q.pop();
			iLaySize--;

			if (head->m_left != nullptr)
				q.push(head->m_left);
			if (head->m_right != nullptr)
				q.push(head->m_right);
			if (0 == iLaySize)//意味着即将要访问下一层
			{
				iLaySize = q.size();
				iHeight++;
			}
		}
		return iHeight;
	}

如何判断一棵树是否为完全二叉树

bool isCompleteBT()
	{
		std::queue<Node<T>*> q;
		q.push(m_root);
		bool bLeaf = false;
		while (!q.empty()) {
			Node<T>* head = q.front();
			q.pop();
			if (bLeaf && head->isLeaf())
			{
				return false;
			}
			/*************************/
			if (head->m_left != nullptr && head->m_right != nullptr)
			{
				q.push(head->m_left);
				q.push(head->m_right);
			}
			else if (head->m_left == nullptr && head->m_right != nullptr)
			{
				return false;
			}
			else if 
				(
					(head->m_left != nullptr && head->m_right == nullptr) ||
					(head->m_left == nullptr && head->m_right == nullptr)
				)//后面遍历的节点必须是叶子节点
			{
				if(head->m_left != nullptr) q.push(head->m_left);
				bLeaf = true;
			}
			/*************************/
		}
		return true;
	}

反转二叉树(所有左右子节点交换)

	//利用前序遍历访问每一个节点  递归方式 中序方式有问题
	Node<T>* inverTree(Node<T>* root)
	{
		if (root == nullptr) return root;

		Node<T>* node = root->m_left;
		root->m_left = root->m_right;
		root->m_right = node;

		inverTree(root->m_left);
		inverTree(root->m_right);
	}

	//利用中序遍历访问每一个节点  递归方式 
	Node<T>* inverTreePreOrder(Node<T>* root)
	{
		if (root == nullptr) return root;
		inverTree(root->m_left);

		Node<T>* node = root->m_left;
		root->m_left = root->m_right;
		root->m_right = node;

		inverTree(root->m_left);//这里仍然要传左子节点,因为左右已经交换
	}

	//利用迭代方式 层序遍历 反转二叉树
	bool inverTreeLayOrder(Node<T>* root)
	{
		Node<T>* head = root;
		std::queue<T> q;
		q.emplace(head);
		while (!q.empty())
		{
			head = q.front();
			q.pop();
			Node<T>* tempNode = head->m_left;
			head->m_left = head->m_right;
			head->m_right = tempNode;
			if (head->m_left != nullptr)
				q.emplace(head->m_left);
			if (head->m_right != nullptr)
				q.emplace(head->m_right);

		}
	}

根据遍历结果复原唯一的一棵二叉树

1.前序遍历+中序遍历

2.后序遍历+中序遍历

前序遍历+后序遍历,如果它是一棵真二叉树,结果是唯一的。不然,结果不唯一。

原因:如果左右子树有一个为空,通过前序遍历和后序遍历不能确定是左子树为空还是右子树为空。如果是一棵真二叉树,则左右子树要么都存在,要么为空。

前驱节点

前驱节点指   中序遍历时的前一个结点

对于二叉排序树,前驱结点是该结点的左子树的最大结点。

	/*查找前驱结点*/
	Node<T>* preDecessor(Node<T>* node)
	{
		if (node == nullptr) return nullptr;
		Node<T>* tempNode = nullptr;
		
		if (node->m_left != nullptr)
		{
			Node<T>* p = node->m_left;
			while (p != nullptr)
			{
				p = p->m_right;
			}
			return p;
		}
		//从父节点 祖父结点中寻找前驱结点
		while(node->m_parent != nullptr && node == node->m_parent->m_left)
			//父节点不为空并且该节点是父节点的左子树
		{
			node = node->m_parent;
		}
		//
		return node->m_parent;
	}

后继结点:中序遍历时的后一个结点,如果是二叉搜索树,后继结点就是后一个比它大的结点 

删除结点

 a.如果是叶子结点,直接删除

 b.度为1的结点,用子结点替代原结点的位置。

        1.如果node是左子结点

                child.parent = node.parent;

                node->parent->left = child;

        2.如果node是右子结点

                child.parent = node.parent;

                node->parent->right = child;

        3.如果node是root

                root = child;child.parent=nullptr;

c.度为2的结点

        找前驱或者后继结点覆盖该节点,删除前驱或者后继结点。

        注意:如果一个结点的度为2,那么它的前驱,后继节点的度只能是1和0。

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

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

相关文章

互联网大厂手把手教你搭建数据服务中台(附下载链接)

摘要: 随着公司业务的发展&#xff0c;对于数据的需求会越来越多。怎么在业务系统中高效的使用数据&#xff0c;让业务系统处理大数据时化繁为简&#xff0c;数据服务化基本是必经之路。那么什么是数据服务化&#xff0c;简单理解就是数据SaaS&#xff0c;通过一些数据库语言把…

true or false?

有同学在星球问了这样一个问题。 代码是这样的&#xff1a; public class Main {private static final Main instance new Main();private boolean b a;private static boolean a initA();private static boolean c a;private static boolean initA() {return true;}priva…

洛谷B2099 矩阵交换行

矩阵交换行 题目描述 给定一个 5 5 5 \times 5 55 的矩阵(数学上&#xff0c;一个 r c r \times c rc 的矩阵是一个由 r r r 行 c c c 列元素排列成的矩形阵列)&#xff0c;将第 n n n 行和第 m m m 行交换&#xff0c;输出交换后的结果。 输入格式 输入共 6 6 6 …

DataEase 本地源码启动详细教程

本教程将引导你通过本地源码部署的方式启动 DataEase&#xff0c;同时我还录制了相应的视频教程&#xff0c;你可以跟随视频进行操作&#xff1a; DataEase 本地源码启动_哔哩哔哩_bilibili 1、下载并安装IDEA开发工具 2、下载安装Mysql 5.7 以及 JDK 1.8 如果你使用的是wi…

使用pdf.js展示pdf文件(亲测可用)

简单的实现方式 如果只是电脑端&#xff0c;可通过 iframe 标签嵌套预览 ios手机端可通过 a 标签包裹点击跳转预览&#xff08;安卓端不行&#xff09; 安卓电脑ios的通用方法 资料 老版本github地址 全版本地址 获取当前客户端类型 judgeClient() {let client if (/(iPh…

鲁大师智能化评测新项目——鲁大师真续航测试1.0正式发布

随着社会的高速发展&#xff0c;人们出行的交通工具正变得多种多样&#xff0c;有电单车、摩托车、自行车、两轮平衡车、甚至滑板等。不过如我们所见&#xff0c;电动两轮车已经成为了大多数近途出行用户的选择&#xff0c;因电单车具有方便、快捷、灵活、小巧、易停放等优点而…

MySQL基础篇4

MySQL基础 1. 数据类型1.1 整数类型1.2 浮点类型1.3 定点数类型1.4 位类型1.5 日期时间类型1.6 文本字符串类型1.6.1 CHAR类型和VARCHAR类型1.6.2 TEXT类型 1.7 ENUM枚举类型1.8 SET类型1.9 二进制字符串类型1.10 JSON数据类型 2. 约束2.1 前置知识2.1.1 为什么需要约束2.1.2 约…

亚马逊云科技围绕需求发力,赋能医疗与生命科学行业数字化创新

2023年4月27日&#xff0c;亚马逊云科技医疗与生命科学行业峰会召开&#xff0c;会议汇聚了业界专家和思想领袖&#xff0c;共同探讨行业数字化转型和创新之道。作为全球医疗及生命科学行业云计算引领者&#xff0c;亚马逊云科技将围绕数据、算力和行业用户体验三大需求发力&am…

移远“5G+Wi-Fi 6”方案双重加速,为用户带来更具性价比的连网体验

近年来&#xff0c;人们对于高速宽带连接的需求猛增&#xff0c;这对网络传输速率、稳定性与时延都提出了更高的要求。在离开网络寸步难行的今天&#xff0c;插电即用、免拉宽带的5G CPE受到了广泛关注。 在地广人稀的部分海外市场&#xff0c;受制于光纤铺设成本、周期、路由规…

Day966.从组件团队到Spotify模型 -遗留系统现代化实战

从组件团队到Spotify模型 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于从组件团队到Spotify模型的内容。 团队结构现代化。这个方向跟管理有关&#xff0c;但无论掌控全局的 CTO、架构师&#xff0c;还是身处遗留系统一线战队的队员&#xff0c;都有必要了解现代…

JavaScript - 基础+WebAPI(笔记)

前言&#xff1a; 求关注&#x1f62d; 本篇文章主要记录以下几部分&#xff1a; 基础&#xff1a; 输入输出语法&#xff1b;数据类型&#xff1b;运算符&#xff1b;流程控制 - 分支语句&#xff1b;流程控制 - 循环语句&#xff1b;数组 - 基础&#xff1b;函数 - 基础&…

idm下载器2024官方最新中文版免费下载

哈喽大家好呀&#xff0c;coco玛奇朵发现我已经有一阵子没有给大家分享windows软件了&#xff0c;今天给大家分享一款暗藏惊喜的windows软件&#xff0c;用过之后真的很难拒绝&#xff01; 这是一个可以帮你提升下载速度的工具&#xff0c;有了它几秒就能帮你下载好各种资源。…

UNIAPP实战项目笔记64 当前用户修改收货地址的前端和后端

UNIAPP实战项目笔记64 当前用户修改收货地址的前端和后端 思路 默认地址的修改&#xff0c;先清空当前id下的所有默认地址&#xff0c;再将新地址设为默认 前端 将新值传给后端 后端接受值后更新处理 实例截图 代码 后端 index.js var express require(express); var route…

大数据Doris(十六):分桶Bucket和分区、分桶数量和数据量的建议

文章目录 分桶Bucket和分区、分桶数量和数据量的建议 一、分桶Bucket

机器学习随记(6)—K-means

1 K-means方案 K-means 算法是一种自动将相似数据点聚集在一起的方法。 具体来说&#xff0c;你得到了一个训练集{,...,}&#xff0c;并且您希望将数据分组为几个有凝聚力的“集群”。 K-means 是一个迭代过程 首先猜测初始质心&#xff0c;然后改进这个猜测 反复将样本分配到…

Seurat -- Cluster the cells --第一部分

文章目录 briefKNN&#xff08;k-nearest neighbor&#xff09;简介部分SNN&#xff08;shared nearest neighbor&#xff09;简介部分Annoy算法理解Jaccard indexSeurat进行聚类的步骤可视化部分subcluster之间的marker gene具体参数 brief seurat 官方教程的解释如下&#x…

Hash碰撞

Hash碰撞 什么是Hash碰撞 Hash碰撞是指两个不同的输入值&#xff0c;经过哈希函数的处理后&#xff0c;得到相同的输出值&#xff0c;这种情况被称之为哈希碰撞。 例如&#xff1a;两个不同的对象&#xff08;object1和object2的值&#xff09;经过Hash函数计算后的&#xf…

科思转债上市价格预测

科思转债 基本信息 转债名称&#xff1a;科思转债&#xff0c;评级&#xff1a;AA-&#xff0c;发行规模&#xff1a;7.249178亿元。 正股名称&#xff1a;科思股份&#xff0c;今日收盘价&#xff1a;67.1元&#xff0c;转股价格&#xff1a;53.03元。 当前转股价值 转债面值…

【进程间通信 之 通信的建立】

目录&#xff1a; 前言进程间通信的目的进程间通信的方式管道1.匿名管道简单示例1 - 消息传输五个特性四种场景简单示例2 - 进程控制对管道的深入理解 2.命名管道简单示例3 -- 不相关进程间通信 system V共享内存简单示例4 - 通知事件消息传输 总结 前言 打怪升级&#xff1a;…

后摩尔时代 , 从有源相控阵天线走向天线阵列微系统

本文围绕高分辨率对地微波成像雷达对天线高效率、低剖面和轻量化的迫切需求 , 分析研究了有源阵列天线的特点、现状、趋势和瓶颈技术 , 针对对集成电路后摩尔时代的发展预测 , 提出了天线阵列微系统概念、内涵和若干前沿科学技术问题 , 分析讨论了天线阵列微系统所涉及的微纳尺…