一篇文章讲透数据结构之树

一.树

1.1树的定义

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

在树中有一个特殊的结点,称为根结点根结点没有前驱结点

除根结点外,其余的结点被分成了M个互不相交的集合T1、T2、......、Tm,其中每一个集合Ti又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱结点,但可以有0个或多个后继结点。因此:树是递归定义的。

注意:树的结点之间是不可以有交集的,有交集的话就不是树了,至于是什么我们后期再说。

如下图:以A为根节点的就不是树,以D为结点的才是树。

  

1.2树的基本概念

  1. 结点的度:一个结点含有的子树的个数称为该结点的度
  2. 叶子结点:度为0的结点称为叶子结点
  3. 分支结点:度不为0的结点
  4. 双亲结点/父结点:若一个结点包含子节点,则这个结点称为其子结点的父节点
  5. 孩子结点/子节点:一个结点含有的子树的根结点称为该结点的子节点
  6. 兄弟结点:具有相同父节点的结点互称为兄弟结点
  7. 树的度:一棵树中,度最大的结点的度称为树的度
  8. 结点的层次:定义根为第一层,根的子节点为第二层,依次类推即可
  9. 树的高度或深度:树中结点的最大层次
  10. 堂兄弟结点:双亲在同一层的结点互为堂兄弟结点
  11. 结点的祖先:从根到该结点所经分支上的所有结点。
  12. 子孙:以某结点为根的子树的任一结点都称为该结点的子孙。
  13. 森林:由m棵互不相交的树组成的集合称为森林。

1.3树的表示方法

我们在这里学习三种树的表示方法,分别为双亲表示法、树的孩子表示法、左孩子右兄弟表示法

我们将用这三种方法来表示这一棵树:

1.3.1父节点表示法(双亲表示法)

树的父节点表示法,是利用顺序表来完成的,怎么做呢?

首先,我们创建顺序表结构体来存储结点的内容以及父节点的下标。我们将这个结构体称为结点结构体。

然后,我们创建树的结构体,其中一个是结点结构体数组,一个是数组内元素个数。

由此,我们可以写出如下代码:

//双亲表示法
//顺序表的方式存储
//顺序表存储结点的数据和双亲结点的下标
typedef int DataType;
typedef struct Node
{DataType data;//数据域int parent;//双亲结点下标
}Node;
//树-->结点组成的数组
typedef struct Tree
{Node NodeArr[10];//保存结点的数组,也可以动态申请int size;//结点个数
};

在这里需要我们大家注意的是:由于根节点没有前驱结点,我们将其的父节点特别记为-1. 

下面我们来表示一下这棵树。

1.3.2树的孩子表示法

树的孩子表示法是通过顺序表和链表结合的形式表示的

它的原理是:

  • 先定义一个单链表结构体表示一个父节点的所有孩子结点的下标,一个孩子指向另外一个孩子
  • 然后用一个顺序表结构体存储当前结点的数值以及这个结点的第一个孩子结点的下标
  • 最后定义一个树结构体

那么,我们就可以写出如下代码:

typedef int DataType;
//链表
struct ListNode
{int child;//当前孩子结点下标struct Listnode* next;//下一个孩子
};
//顺序表
struct Node
{DataType data;//结点的数据ListNode* FirstChild;//第一个孩子
};
struct Tree
{Node NodeArr[10];int size;
};

现在我们画图来理解一下这个方法

  • 绿色的是全部的链表结构体(每个单链表之间没有关系)
  • 绿色的每一块是一个单链表
  • 紫色中的每一块是一个顺序表
  • 由于图的篇幅有限,将一个树分开画了,紫色的其实是一个整体
  • 紫色每一块的右下角是元素的个数

 

这个方法的缺点就是:我们要在顺序表中插入或者删除数据需要移动大量的数据。 

1.3.3左孩子右兄弟表示法

这个是最常用的表示树的的方法,即定义两个指针,让左指针指向最左边的子节点,右指针指向兄弟节点。

如果没有节点,则都指向空。

typedef int DataType;
struct Node
{struct Node* leftChild;//孩子结点struct Node* rightBrother;//指向下一个兄弟结点DataType Data;//数据域
};

我们下面具体画一下图: 

1.4树的实际应用

在linux环境下目录结构就是有一颗树构成。

而在Windows环境下,目录许多内容并不交叉,所以是由森林构成。

二.二叉树

2.1二叉树的定义

一棵二叉树是结点的一个有限集合,该集合:

1.或者为空

2.由一个根节点加上两棵别称(别名/外号)为:左子树、右子树的二叉树组成

从上图可以看出

  • 二叉树不存在度大于2的结点
  • 二叉树的子树有左右之分,次序不能颠倒, 因此二叉树是有序树

我们可以将一棵二叉树拆分开来,我们会发现任何一棵二叉树都是由以下几种情况复合而来的:

下面给大家看一张现实生活中存在的二叉树: 

2.2特殊的二叉树

  • 满二叉树:

一个二叉树,如果每一层的节点数都达到了最大值,那么这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是2^k-1,则它就是满二叉树。

  • 完全二叉树:

完全二叉树是一种效率很高的数据结构。

完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1到n的结点都可以一一对应时,则可称之为完全二叉树。也就是说:满二叉树是一种特殊的完全二叉树。(文字表述蛮抽象的,其实就是倒数第二层满,最后一层的叶子结点要从左往右放

2.3二叉树的相关性质

1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点.
2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是2^h-1.
3. 对任何一棵二叉树, 如果度为0其叶结点个数为 , 度为2的分支结点个数为 ,则有 n0=n2+1

4.若规定根结点的层数为1,具有n个结点的满二叉树的深度,h=log(n+1)

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

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

结论3的推导过程:

 其余结论的推导过程可参考:堆的实现 

2.4二叉树的存储

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

2.4.1顺序结构

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树。

因为不是完全二叉树会有空间的浪费。

而现实中使用中只有堆才会使用数组来存储,关于堆的实现可以参考上面的链接。

二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

如下图,左边是完全二叉树,不会有空间浪费;

右边是不完全二叉树,有空间浪费。

2.4.2链式存储 

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址

如下图所示:

最左边是存储结构,中间是二叉树实例,右边是逻辑结构图。

在这里我们采用链式结构来存储二叉树:

2.5二叉树的遍历

二叉树的遍历方式有四种:前序遍历、中序遍历、后序遍历、层序遍历

二叉树的前、中、后序遍历都可以通过递归和用栈模拟递归实现,这里我们学习这两种方法.

大家可以先研究代码,理解困难的话可以根据视频学习:

视频1:

二叉树的前中后序遍历的递归实现

视频2:

二叉树前中后序遍历的非递归实现以及层序遍历

2.5.1前序遍历

前序遍历:先遍历根节点,再依次遍历左子树,右子树。而遍历左子树,又要先遍历根节点,再依次遍历左子树,右子树…...直到遍历完整棵树。

递归实现 

void PreOrder1(BTree* root)
{assert(root);if (!root){return;}printf("%d ", root->data);PreOrder(root->left);PreOrder(root->right);
}

 非递归实现

typedef BTree* STDataType;
void PreOrder2(BTree* root)
{//开辟一个栈Stack s;StackInit(&s);BTree* p = root;while (p || !IsEmpty(&s)){if (p != NULL){printf("%d ", p->data);StackPush(&s, p);p = p->left;}else{p = StackTop(&s);		    StackPop(&s);p = p->right;}}
}

2.5.2中序遍历

先遍历左子树,再依次遍历根节点,右子树。而遍历左子树,又要先遍历左子树,再依次遍历根节点,右子树…直至遍历完整棵树。

递归实现

void Inorder1(BTree*root)
{if (root == NULL){return;}PreOrder(root->left);//左子树printf("%d ", root->data);//根节点PreOrder(root->right);//右子树
}

非递归实现 

  typedef BTree* STDataType;void Inorder2(BTree* root){Stack s;InitStack(&s);BTree* p = root;while (p || !IsEmpty(&s))  {if (p != NULL)//入栈{StackPush(&s, p);p = p->left;}else{p = StackTop(&s);StackPop(&s);	  printf("%d ", p->data);p = p->right;}}}

2.5.3后序遍历

后序遍历:先遍历左子树,再依次遍历右子树,根节点。而遍历左子树,又要先遍历左子树,再依次遍历右子树,根节点…直至遍历完整棵树。

递归实现

void Postorder1(BTree*root)
{if (root == NULL){return;}PreOrder(root->left);//左子树PreOrder(root->right);//右子树printf("%d ", root->data);//根节点
}

 非递归实现

void Postorder2(BTree* root)
{Stack s;InitStack(&s);BTree* p = root;// p为遍历指针BTree* v = root;// v标记已访问节点while (p || !IsEmpty(&s))  // 栈不为空或p不为空时循环{while(p != NULL)//入栈{StackPush(&s, p);p = p->left;}p = StackTop(&s);if (p->right && p->right != v)//存在右子树,且没有被访问{p = p->right;//访问}else//没有右子树或者右子树已被访问{printf("%d ", p->data);v = p;//记录当前访问的节点p = NULL;//防止重复访问左子树StackPop(&s);// 栈顶元素出栈}}
}

2.5.4层序遍历

层序遍历,就是一层一层的遍历。

这里我们借助队列来实现。

void leverOrder(BTree* root, Queue* pq)
{if (root == NULL)//为空直接返回{return;}QueuePush(pq, root);//插入第一个节点while (!QueueEmpty(pq))//队列不为空{BTree* p = QueueFront(pq);printf("%d ", p->data);QueuePop(pq);if (p->left != NULL)//带入左孩子{QueuePush(pq, p->left);}if (p->right != NULL)//带入右孩子{QueuePush(pq, p->right);}}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL--主从复制

目录 一、主从复制原理 1.简要原理 2.涉及到的文件 3.涉及到的线程 4.主从复制执行步骤&#xff08;重点&#xff09; 二、主从复制搭建 1.准备两台以上的数据库实例&#xff0c;要求数据库版本一致 2.区分不同角色 3.主库开启二进制日志 4.主库创建专用复制用户&…

文件IO(三)

文件IO&#xff08;三&#xff09; 左移右移Linux的man 手册文件IO打开文件操作文件关闭文件 caps lock开灯关灯读取按键文件IO操作目录文件打开目录文件操作目录文件 库动态库和静态库的优缺点创建静态库创建动态库 按下右ctrl键 亮灭灯 左移右移 Linux的man 手册 文件IO 打开…

【计算机毕设】基于SpringBoot的教师工作量管理系统设计与实现 - 源码免费(私信领取)

免费领取源码 &#xff5c; 项目完整可运行 &#xff5c; v&#xff1a;chengn7890 诚招源码校园代理&#xff01; 1. 研究目的 随着高校规模的扩大和教学任务的增加&#xff0c;教师的工作量管理变得越来越复杂和重要。传统的教师工作量管理方式效率低下&#xff0c;容易出错&…

真机调试 Error:系统错误,xxx exceed max limit 2MB

我们在使用微信开发者工具开发小程序、小游戏等应用时&#xff0c;往往会点击“真机调试”&#xff0c;微信扫描查看真实情况。 但是会出现下面的报错提示&#xff0c;是因为主包体积超过了2MB。 小程序有体积和资源加载限制&#xff0c;在微信小程序中&#xff0c;每个包不能…

Java事务入门:从基础概念到初步实践

Java事务入门&#xff1a;从基础概念到初步实践 引言1. Java事务基础概念1.1 什么是事务&#xff1f;1.2 为什么需要事务&#xff1f; 2. Java事务管理2.1 JDBC 的事务管理2.2 Spring 事务管理2.2.1 Spring JDBC2.2.1.1 添加 Spring 配置2.2.1.2 添加业务代码并测试验证 2.2.2…

【图解IO与Netty系列】Reactor模型

Reactor模型 Reactor模型简介三类事件与三类角色Reactor模型整体流程 各种Reactor模型单Reactor单线程模型单Reactor多线程模型主从Reactor模型 Reactor模型简介 Reactor模型是服务器端用于处理高并发网络IO请求的编程模型&#xff0c;与传统的一请求一线程的同步式编程模型不…

ssm汉服文化平台网站

博主介绍&#xff1a;✌程序员徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

【NumPy】深入了解NumPy的multiply函数:高效矩阵和数组乘法指南

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

区块链合约开发流程

区块链合约开发&#xff0c;尤其是以太坊智能合约开发&#xff0c;是一个多步骤的过程&#xff0c;从需求分析到部署和维护&#xff0c;每一步都需要仔细规划和执行。以下是详细的开发流程。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合…

NextJs 数据篇 - 数据获取 | 缓存 | Server Actions

NextJs 数据篇 - 数据获取 | 缓存 | Server Actions 前言一. 数据获取 fetch1.1 缓存 caching① 服务端组件使用fetch② 路由处理器 GET 请求使用fetch 1.2 重新验证 revalidating① 基于时间的重新验证② 按需重新验证revalidatePathrevalidateTag 1.3 缓存的退出方式 二. Ser…

Window下VS2019编译WebRTC通关版

这段时间需要实现这样一个功能&#xff0c;使用WebRTC实现语音通话功能&#xff0c;第一步要做的事情就是编译WebRTC源码&#xff0c;也是很多码友会遇到的问题。 经过我很多天的踩坑终于踩出来一条通往胜利的大路&#xff0c;下面就为大家详细介绍&#xff0c;编译步骤以及踩…

【React篇】简述React-Router 的实现原理及工作方式

React Router 路由的基础实现原理分为两种&#xff0c;如果是切换 Hash 的方式&#xff0c;那么依靠浏览器 Hash 变化即可&#xff1b;如果是切换网址中的 Path&#xff0c;就要用到 HTML5 History API 中的 pushState、replaceState 等。在使用这个方式时&#xff0c;还需要在…

【基本数据结构】平衡二叉树

文章目录 前言平衡二叉树1 简介2 旋转2.1 左旋2.2 右旋2.3 何时旋转 3 插入节点4 删除节点5 代码 参考资料写在最后 前言 本系列专注更新基本数据结构&#xff0c;现有以下文章&#xff1a; 【算法与数据结构】数组. 【算法与数据结构】链表. 【算法与数据结构】哈希表. 【…

前端Vue小兔鲜儿电商项目实战Day06

一、本地购物车 - 列表购物车 1. 基础内容渲染 ①准备模板 - src/views/cartList/index.vue <script setup> const cartList [] </script><template><div class"xtx-cart-page"><div class"container m-top-20"><div…

mysql(数据库)可视化工具——Navicat Premium

Navicat Premium是一款功能强大的数据库管理工具&#xff0c;它支持多种数据库管理系统&#xff0c;包括MySQL、MariaDB、SQL Server、SQLite、Oracle和PostgreSQL等。Navicat Premium提供了直观的用户界面&#xff0c;使用户能够轻松地管理数据库结构、执行复杂的SQL查询、导入…

从零开始学React--环境搭建

React官网 快速入门 – React 中文文档 1.搭建环境 下载nodejs,双击安装 nodejs下载地址 更新npm npm install -g npm 设置npm源&#xff0c;加快下载速度 npm config set registry https://registry.npmmirror.com 创建一个react应用 npx create-react-app react-ba…

生态系统服务功能之碳储量

大家好&#xff0c;这期开始新生态系统服务功能即碳储量的计算&#xff0c;这部分较简单&#xff0c;下面让我们开始吧&#xff01;&#xff01;&#xff01; 碳储量的计算公式 生态系统通过从大气中释放和吸收二氧化碳等温室气体来调节地球气候&#xff0c;而森林、 草原和沼…

PVE虚拟机 安装 OpenWrt

1、创建虚拟机 2、操作系统 3、磁盘&#xff0c;先删除 4、网络 5、其它默认 6、在 local 分区上传镜像 7、登录PVE虚拟机 # 切换到镜像目录 cd /var/lib/vz/template/iso/# 把镜像导入磁盘 qm importdisk 102 openwrt-buddha-version-v7_2022_-x86-64-generic-squashfs-uefi…

精选免费在线工具与资源推荐20240531

精选免费在线工具与资源推荐 引言 在互联网高速发展的今天&#xff0c;我们身处一个信息爆炸的时代。为了更好地应对工作和学习中的挑战&#xff0c;我们时常需要借助各种工具和资源来提高效率。幸运的是&#xff0c;网络上存在着大量免费且高效的在线工具和资源&#xff0c;…

VALL-EX下载介绍:只需3秒录音,即可克隆你的声音

VALL-EX是一个强大和创新的多语言文本转语音模型&#xff0c;支持对中文、英文和日语的语音进行合成和克隆&#xff0c;使用者只需上传一段3-10秒的录音&#xff0c;就可以生成高质量的目标音频&#xff0c;同时保留了说话人的声音、情感和声学环境 VALL-EX的应用范围非常广泛&…