数据结构--树存储结构 & 深度优先遍历 & 广度优先遍历 通俗易懂

2023-11-20

树的概念

​ 首先,树是一种常用的非线性数据结构,是以边(Edge)相连的节点(Node)的集合,每个节点存储对应的值,当存在子节点时与之相连。

  • 根节点:是树的首个节点
  • 边:所有节点都由边相连,用于标识节点间的关系
  • 叶子结点:树的末端节点,它们没有子节点
  • 树的高度:由根节点出发,到子节点的最长路径长度(从下往上)
  • 树的深度:指对应节点到根节点路径长度(从上往下)
  • 空树:如果树节点个数为零,那么构成的树成为空树
  • 树的层:从一棵树的树根开始,树根所在层为第一层,树的孩子节点所在的层为第二层,以此类推。
  • 节点的度:对于一个节点,拥有的子树树成为度
  • 节点深度:是指对应节点到根节点路径长度
树的存储结构

​ 树的存储可以分为顺序存储和链式存储结构,但为了满足多子树的场景,树的存储方式利用了顺序存储和链式存储的,其方法有双亲表示法、孩子表示法、孩子兄弟表示法。

1、双亲表示法
  • 原理
    • 采用一段连续的存储空间存储每个节点
    • 根节点没有双亲,所以对应的父节点对应数组下标为-1
    • 其余的节点,存储其父节点对应数组下标即可
  • 数据结构
// 双亲表示法
#define MAX_SIZE 100
typedef int ElemType;

typedef struct PTNode{
    ElemType data;
    int parent;
}PTNode;

typedef struct PTree
{
    PTNode nodes[MAX_SIZE];
    int n;
}PTree;
- 特点 - 优点:根据节点的parent指针很容易找到它的双亲节点,所用时间复杂度为O(1),直到parent为-1时,表示找到了树的根节点。 - 缺点:如果要找到孩子结点,需要遍历整个结构才行。
2、孩子表示法
  • 原理:将每个节点的孩子节点都用单链表连接起来形成一个现行结构,n个节点具有n个孩子链表
  • 数据结构
// 孩子表示法
typedef int ElemType;
#define MAX_SIZE 100
typedef struct CNode
{
    int child;
    struct CNode *next;
}CNode;

typedef struct PNode
{
    ElemType data;
    struct CNode *child;
}PNode;

typedef struct CTree
{
    PNode nodes[MAX_SIZE];
    int n;
}CTree;
- 特点 - 优点:查找某个结点的某个孩子,或者找某个结点的兄弟,只需要查找这个结点的孩子单链表即可。 - 缺点:当要寻找某个结点的双亲时,就不是那么方便了。
3、孩子兄弟表示法
  • 原理:任意一棵树,它的节点的第一个孩子如果存在就是唯一的,它的兄弟如果存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
  • 数据结构
typedef int ElemType;
typedef struct Node
{
    ElemType data;
    struct Node *FirstChild;
    struct Node *NextBrother;
    
}Node,TREE;

​ 其实这个表示法的最大好处是它把一棵复杂的树变成了一棵二叉树,可以将上图转换成下图二叉树表示法,这样就可以充分利用二叉树的特性和算法来处理这棵树了。

  • 特点
    • 优点:查找某个结点的某个孩子带来了方便,只需要通过firstchild找到此结点的长子,然后再通过长子结点的rightsib找到它的二弟,接着一直下去,直到找到具体的孩子。
    • 缺点:如果想找某个结点的双亲,这个表示法就不方便了。
树的分类及特征
1、二叉树
  • 定义:二叉树(binary)是一种特殊的树,它是每个节点最多有两个子树的树结构,通常子树被称作是 “左子树” 和 “右子树”,二叉树常用于实现二叉搜索树和二叉堆。
  • 常见的二叉树有:完全二叉树,满二叉树,二叉搜索树,二叉堆,AVL 树,红黑树,哈夫曼树。
2、完全二叉树
  • 定义:若设二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边。
3、满二叉树
  • 定义:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树被称之为满二叉树。满二叉树一定是完全二叉树,完全二叉树不一定满二叉树。
    在这里插入图片描述
4、二叉搜索树
  • 定义:二叉搜索树是一种特殊的二叉树,也可以称为二叉排序树,二叉查找树,除了具有二叉树的基本性质外,它还具备
    • 树中每个节点最多有两个子树,通常称为左子树和右子树
    • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值
    • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
    • 它的左右子树仍然是一棵二叉搜索树 (recursive)
5、平衡树
树的遍历

​ 左右子节点的访问顺序通常不重要,极个别情况下会有微妙区别,比如说我们想要访问一棵树的最左下角节点,那么顺序就会产生影响,但这种题目会比较少一点。

​ 通常遍历不是目的,遍历时为了更好的做处理,这里处理包括搜索、修改树等。树虽然只能从根节点开始访问,但是我们可以选择在访问完毕回来的时候处理,还是在访问回来之前处理,这两种不同的方式就是后序遍历和先序遍历。

​ 树的遍历可以分为两种类型:深度优先遍历和广度优先遍历。

  • DFS可以细分为前中后序遍历;DFS适合做一些暴力枚举的问题,DFS如果借助函数调用栈,则可以轻松的使用递归来实现。
  • BFS可以细分为带层和不带层遍历。BFS适合求最短路径。BFS的核心在于求最短路径问题时候可以提前终止,这是其核心价值,层次遍历是一种不需要提前终止的BFS的副产物。这个提前终止不同于 DFS 的剪枝的提前终止,而是找到最近目标的提前终止。比如我要找距离最近的目标节点,BFS 找到目标节点就可以直接返回。而 DFS 要穷举所有可能才能找到最近的,这才是 BFS 的核心价值。实际上,我们也可以使用 DFS 实现层次遍历的效果,借助于递归,代码甚至会更简单。如果找到任意一个满足条件的节点就好了,不必最近的,那么 DFS 和 BFS 没有太大差别。同时为了书写简单,我通常会选择 DFS。
1、深度优先遍历DFS

​ 深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点 v 的所在边都己被探寻过,搜索将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止,属于盲目搜索。

​ 深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题

  • 算法流程

    1、首先将根节点放入栈stack中

    2、从stack中取出第一个节点,并检验它是否为目标。如果找到所有的节点,则结束搜寻并回传结果。否则将它某一个尚未检验过的直接子节点加入stack中。

    3、重复步骤2

    4、如果不存在未检测过的直接子节点。将上一级节点加入stack中。重复步骤2。

    5、重复步骤4

    6、若stack为空,表示整张图都检查过了——亦即图中没有欲搜寻的目标。结束搜寻并回传“找不到目标”。

    这里的栈可以理解为自己实现的栈,也可以理解为调用栈。如果是调用栈的时候就是递归,如果是自己实现的栈的话就是迭代。

  • 算法模版

const visited = {}
function dfs(i) {
	if (满足特定条件){
		// 返回结果 or 退出搜索空间
	}

	visited[i] = true // 将当前状态标为已搜索
	for (根据i能到达的下个状态j) {
		if (!visited[j]) { // 如果状态j没有被搜索过
			dfs(j)
		}
	}
}

​ 上面的 visited 是为了防止由于环的存在造成的死循环的。 而我们知道树是不存在环的,因此树的题目大多数不需要 visited,除非你对树的结构做了修改,比如就左子树的 left 指针指向自身,此时会有环。

function dfs(root) {
	if (满足特定条件){
		// 返回结果 or 退出搜索空间
	}
	for (const child of root.children) {
        dfs(child)
	}
}

而几乎所有的题目几乎都是二叉树,因此下面这个模板更常见。

function dfs(root) {
	if (满足特定条件){
		// 返回结果 or 退出搜索空间
	}
    dfs(root.left)
    dfs(root.right)
}
  • 两种常见分类

    前序遍历和后序遍历是最常见的两种 DFS 方式。而另外一种遍历方式 (中序遍历)一般用于平衡二叉树。

    • 前序遍历
    function dfs(root) {
    	if (满足特定条件){
    		// 返回结果 or 退出搜索空间
        }
        // 主要逻辑
        dfs(root.left)
        dfs(root.right)
    }
    
    • 后续遍历
    function dfs(root) {
    	if (满足特定条件){
    		// 返回结果 or 退出搜索空间
        }
        dfs(root.left)
        dfs(root.right)
        // 主要逻辑
    }
    
    • 混合遍历

    如上代码,我们在进入和退出左右子树的时候分别执行了一些代码。那么这个时候,是前序遍历还是后续遍历呢?实际上,这属于混合遍历了。不过我们这里只考虑主逻辑的位置,关键词是主逻辑。

    如果代码主逻辑在左右子树之前执行,那么就是前序遍历。如果代码主逻辑在左右子树之后执行,那么就是后序遍历。

    function dfs(root) {
    	if (满足特定条件){
    		// 返回结果 or 退出搜索空间
        }
        // 做一些事
        dfs(root.left)
        dfs(root.right)
        // 做另外的事
    }
    
2、广度优先遍历BFS

​ BFS 也是图论中算法的一种,不同于 DFS, BFS 采用横向搜索的方式,在数据结构上通常采用队列结构。 注意,DFS 我们借助的是栈来完成,而这里借助的是队列。

​ BFS 比较适合找最短距离/路径和某一个距离的目标。比如给定一个二叉树,在树的最后一行找到最左边的值。 ,此题是力扣 513 的原题。这不就是求距离根节点最远距离的目标么? 一个 BFS 模板就解决了。

  • 算法流程

    1、首先将根节点放入队列中

    2、从队列中取出第一个节点,并检查它是否为目标。

    • 如果找到目标,则结束搜索并返回结果
    • 如果不是目标,将它所有尚未检验过的直接子节点加入队列中

    3、若队列为空,表示整张图都检查过了——亦即图中没有欲搜索的目标。结束搜索并回传“找不到目标”。

    4、重复步骤2

  • 算法模版

const visited = {}
function bfs() {
	let q = new Queue()
	q.push(初始状态)
	while(q.length) {
		let i = q.pop()
        if (visited[i]) continue
        if (i 是我们要找的目标) return 结果
		for (i的可抵达状态j) {
			if (j 合法) {
				q.push(j)
			}
		}
    }
    return 没找到
}
  • 两种常见分类

    前面我提到了“BFS 比较适合找最短距离/路径和某一个距离的目标”。 如果我需要求的是最短距离/路径,我是不关心我走到第几步的,这个时候可是用不标记层的目标。而如果我需要求距离某个节点距离等于 k 的所有节点,这个时候第几步这个信息就值得被记录了。

    • 标记层
    class Solution:
        def bfs(k):
            # 使用双端队列,而不是数组。因为数组从头部删除元素的时间复杂度为 N,双端队列的底层实现其实是链表。
            queue = collections.deque([root])
            # 记录层数
            steps = 0
            # 需要返回的节点
            ans = []
            # 队列不空,生命不止!
            while queue:
                size = len(queue)
                # 遍历当前层的所有节点
                for _ in range(size):
                    node = queue.popleft()
                    if (step == k) ans.append(node)
                    if node.right:
                        queue.append(node.right)
                    if node.left:
                        queue.append(node.left)
                # 遍历完当前层所有的节点后 steps + 1
                steps += 1
            return ans
    
    • 不标记层
    class Solution:
        def bfs(k):
            # 使用双端队列,而不是数组。因为数组从头部删除元素的时间复杂度为 N,双端队列的底层实现其实是链表。
            queue = collections.deque([root])
            # 队列不空,生命不止!
            while queue:
                node = queue.popleft()
                # 由于没有记录 steps,因此我们肯定是不需要根据层的信息去判断的。否则就用带层的模板了。
                if (node 是我们要找到的) return node
                if node.right:
                    queue.append(node.right)
                if node.left:
                    queue.append(node.left)
            return -1
    

公众号:编程之蝉 专注后台开发、CDN、算法、大数据,欢迎关注,阅读最新更新
公众号:编程之蝉
Leetcode树专栏讲解
数据结构树讲解

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构--树存储结构 & 深度优先遍历 & 广度优先遍历 通俗易懂 的相关文章

  • 搜索学习心得

    在学习了众多搜索的方式后 不由感慨 啊 太巨了 今天huayucaiji我就给大家讲一讲C 搜索的心得吧 深度优先搜索 广度优先搜索 迭代加深搜索 一个一个讲吧 深度优先搜索 深度优先搜索 下简称 深搜 简称DFS 是简洁明了的搜索方式 以
  • 路径搜索问题

    之前碰到的很多问题都可以归结为路径搜索问题 就是求两点之间的路经 1 是否存在路径 2 求任意一条路径 3 求所有路径 求是否有路径和任意一条路径的时候 和正常遍历一样 一个点被mark之后不再访问 因为如果这个结点到终点有路径 之前就应该
  • UVA12166 Equilibrium Mobile

    VJ传送门 一道思维题 刚开始看的时候没什么思路 在博客园上参考了大佬的解析 在这里总结一下 一 分析 这道题要求让天平平衡所需要的最小改动次数 至少有一个不变 我们可以先选定一个不变的基准 然后改变其他的秤砣 得到以此为基准的天平的总重量
  • 【100%通过率 】【华为OD机试 c++/python】查找单入口空闲区域【 2023 Q1

    华为OD机试 题目列表 2023Q1 点这里 2023华为OD机试 刷题指南 点这里 题目描述 给定一个 m x n 的矩阵 由若干字符 X 和 O 构成 X 表示该处已被占据 O 表示该处空闲 请找到最大的单入口空闲区域 解释 空闲区域是
  • Catowice City【Codeforces 1248 F】【BFS】

    Codeforces Round 594 Div 2 F 一开始是听闻有人说这是一道Tarjan好题 然后就点进来做了 但是想来想去 却想了个另类的法子 我们可以看到 如果N个人都要选择的话 那么每个人都只能是审判者 或者是参赛者 所以 我
  • poj 3074 Sudoku

    Time Limit 1000MS Memory Limit 65536K Total Submissions 7613 Accepted 2696 Description In the game of Sudoku you are giv
  • 关于multipartFile.transferTo方法报错java.nio.file.FileAlreadyExistsException

    之前老项目用的spring4版本 现在升级成spring5版本 重新把文件中心搬过来 发现原先有一段 MultipartFile multiFile XXX File file File createTempFile System curr
  • LeetCode-116.填充每个节点的下一个右侧节点指针、深度优先搜索

    题目分析 广度优先搜索 题目要求把二叉树中每一层的的节点连起来 最简单的方法即 BFS 按层的顺序的对树进行遍历 但需要使用 queue 数据结构 空间复杂度为 O N 不符合题目要求 深度优先搜索 由于 next 指针的存在 可以实现对二
  • python 实现二叉树

    本文不涉及二叉树概念的详细讲解 而着重利用 python 实现二叉树 其中穿插代码讲解 其它数据结构 链表 栈和队列 目录 节点 构造树 层遍历 添加节点 先序遍历 中序遍历 后序遍历 测试 在链表中 一个节点只能有一个后继节点和前驱节点
  • LeetCode0752-打开转盘锁

    LeetCode0752 打开转盘锁 题目 你有一个带有四个圆形拨轮的转盘锁 每个拨轮都有10个数字 0 1 2 3 4 5 6 7 8 9 每个拨轮可以自由旋转 例如把 9 变为 0 0 变为 9 每次旋转都只能旋转一个拨轮的一位数字 锁
  • 第十届蓝桥杯省赛C++B组 迷宫

    试题 E 迷宫 本题总分 15 分 问题描述 下图给出了一个迷宫的平面图 其中标记为 1 的为障碍 标记为 0 的为可 以通行的地方 010000 000100 001001 110000 迷宫的入口为左上角 出口为右下角 在迷宫中 只能从
  • CentOS 7.9 64位 SCC版安装FastDfs和配置Nginx

    最近练习的项目中需要用到FastDfs 和Nginx 这里记录一下安装和配置过程 个人使用部署过程遇到了很多的坑 准备把过程记下来不然忘了 首先 购买 试用阿里云 CentOS 7 9 64位Scc版系统 进入远程桌面 由于项目较老 所以我
  • 奶酪【BFS】

    题目链接 点从z 0为起点 想跑到z h 只能在球内 或者是球表层上跑 问能否从起点跑到终点 直接暴力bfs判断即可 include
  • 不同岛屿的数量

    694 不同岛屿的数量 这道题要给出不同岛屿的数量 最直观的想法就是对发现的不同岛屿进行序列化 然后把序列化结果存到HashSet 那怎么序列化呢 其实比较类似于二叉树的序列化 记录遍历顺序 方向 这里用 1 2 3 4 代表上下左右 1
  • [ACM] 1016 Prime Ring Problem (深度优先搜索)

    Prime Ring Problem Problem Description A ring is compose of n circles as shown in diagram Put natural number 1 2 n into
  • Fix a Tree【Codeforces 699 D】【dfs + 树的性质】

    Codeforces Round 363 Div 2 D 题意 有N个点 每个点i都有一个父节点p i 如果 i p i 则是说明i结点是根结点 现在我们给出这样的1 N的p i 这可能是不合法的 问 我们应该最少改变多少个使它变成一棵合法
  • LeetCode——1302. 层数最深叶子节点的和

    题目描述 给你一棵二叉树的根节点 root 请你返回层数最深的叶子节点的和 示例 1 输入 root 1 2 3 4 5 null 6 7 null null null null 8 输出 15 示例 2 输入 root 6 7 8 2 7
  • POJ 2676 Sudoku 数独(dfs)

    POJ 2676 Sudoku 数独 dfs Sudoku is a very simple task A square table with 9 rows and 9 columns is divided to 9 smaller squ
  • [SDOI2012]拯救小云公主【bfs+二分答案】

    题目链接 正难则反 要直接求从起点到终点的最大距离 不妨反过来求最小的可以阻止骑士从起点到终点的对于全体圆的最小半径 那么 就是阻止从左上角到右下角的所有相交圆 于是 就是要变成没有从左上角到右下角的相交圆才可以 那么不妨跑一个bfs来判断
  • 数据结构--树存储结构 & 深度优先遍历 & 广度优先遍历 通俗易懂

    树的概念 首先 树是一种常用的非线性数据结构 是以边 Edge 相连的节点 Node 的集合 每个节点存储对应的值 当存在子节点时与之相连 根节点 是树的首个节点 边 所有节点都由边相连 用于标识节点间的关系 叶子结点 树的末端节点 它们没

随机推荐

  • 服务器虚拟化的优势

    1 提高硬件资源使用效率 一个服务器上可以开多个虚拟机 给不同应用使用 打破一个应用一台服务器的限制 因为某具体用户使用的时间 资源有限 多个用户 应用 就可以大大提高服务器的使用效率 减少服务器数量 可以 降低购买服务器的投资 降低服务器
  • C++(四)——C++标准模板库

    文章目录 1 STL组件 Component 2 容器 Container 2 1 序列式容器 Sequence Container 2 2 关联式容器 Associative Container 2 3 无序容器 Unordered Co
  • 用matlab绘制系统函数的DTFT

    freqz函数 frequency response of digital filter 对于一个输入离散序列 输出离散序列的离散时间系统 我们可以用它的系统函数H Z 来描述这个系统 求这个系统函数的DTFT 可以得到这个系统的幅频响应和
  • logback-spring.xml中三种相对路径生成的日志文件的位置

    logback spring xml中关于路径配置的三种写法 写法1
  • 大屏图表,ECharts 从“熟练”到入门

    阅读本文 你将 了解 配置驱动 的思想 理解 Echarts 基本概念 了解 graphic 和 动画基本玩法 了解 Echarts 基底组件的封装的思路 一 不是标题党 Echarts 简历上人均 熟练 公司最近在招外包 而因为目前大屏的
  • java自动识别文件编码格式UTF-8,UTF-8无BOM,GBK

    背景 在解读properties配置文件时windows操作系统编辑过的内容上传后总是无法通过键获取文件中内容 讲过分析是文件的编码格式为UTF 8带BOM的 因此通过该程序获取文件编码格式 import java io BufferedI
  • ES6阮一峰入门教程

    地址为 https es6 ruanyifeng com
  • visual studio 一直显示正在准备解决方案

    首先重启电脑 无法解决的情况下执行以下步骤 Kill Visual Studio Open Visual Studio without loading a solution Disable AnkhSvn as Source Control
  • vue动态绑定video视频src问题解决

    做个项目 视频部分需要先后台上传 然后前端页面显示 然后就遇到了视频动态获取地址的问题 一开始想着很简单 使用v model双向绑定就行了 结果试了下并不行 后面开始度娘 尝试过很多人说的 refs解决 结果并不行 虽然浏览器中看地址确实绑
  • 设计模式(2)

    2 2 结构型模式 结构型模式一共有七种 其中 适配器模式和装饰模式统称为包装模式 装饰模式和代理模式的类图基本相同 但目的不同 这些有相似目的或者有相似结构的模式需要对其概念辨析清楚 才能较好地掌握 下面将对结构型模式分别进行介绍 2 2
  • C++启蒙笔记(八)---类继承、动态内存分配

    目录 一 基本概念 1 1派生类 1 2 继承关系 二 常规写法 2 1 头文件 2 2 类实现 2 3 主程序 2 4 编译及显示 三 多态公有继承 3 1 虚方法 3 2 抽象基类 3 3 多重继承MI 四 动态内存分配 4 1 头文件
  • PyTorch实现Logistic Regression

    1 PyTorch基础实现Logistic regression import torch from torch autograd import Variable torch manual seed 2 x data Variable to
  • Python in Visual Studio Code 2023年9月更新

    作者 Courtney Webster Program Manager Python Extension in Visual Studio Code 排版 Alan Wang 我们很高兴地宣布 Visual Studio Code 的 Py
  • 黑白图片上色算法

    效果图 Marked B W image Result Marked B W image Result Marked B W image Result Marked B W i
  • win10 系统锁屏壁纸的目录

    路径 C Users 你自己的用户名 AppData Local Packages Microsoft Windows ContentDeliveryManager cw5n1h2txyewy LocalState Assets 查看 需要
  • 使用php简单网页抓取和内容分析,PHP抓取及分析网页的方法详解

    本文实例讲述了PHP抓取及分析网页的方法 分享给大家供大家参考 具体如下 抓取和分析一个文件是非常简单的事 这个教程将通过一个例子带领你一步一步地去实现它 让我们开始吧 首先 我首必须决定我们将抓取的URL地址 可以通过在脚本中设定或通过
  • python 去除所有的中文 英文标点符号

    去除英文标点符号 python的string模块下的 punctuation 包含所有的英文标点符号 所以用replace 一下就可以去除 代码示例 import string stri today is friday so happy p
  • MacOS中清除原有ssh公钥方法

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 用ssh的跳转登录服务器后 ssh会把你每个你访问过计算机的公钥 public key 都记录在 ssh known hosts 当下次访问相同计算机时 SSH会核对公钥
  • smbms 获取角色操作,角色管理实现

    为了我们职责统一 可以把角色的操作单独放在一个包中 和pojo中的对应 RoleDao 接口 package com Li dao role import com Li pojo Role import java sql Connectio
  • 数据结构--树存储结构 & 深度优先遍历 & 广度优先遍历 通俗易懂

    树的概念 首先 树是一种常用的非线性数据结构 是以边 Edge 相连的节点 Node 的集合 每个节点存储对应的值 当存在子节点时与之相连 根节点 是树的首个节点 边 所有节点都由边相连 用于标识节点间的关系 叶子结点 树的末端节点 它们没