算法第九期——DFS(深度优先搜索)对树的应用

news2025/4/10 10:06:49

树是一种特殊的图 。

特点:

  • 若树有n个点,则有n-1条边。
  • 树有连通性但没有回路。
  • 从一个点出发可以到达任意一个,而且路径是唯一的。

 树的重心u(最平衡的点):

  • 以树上任意一个结点为根计算它的子树的结点数,如果结点u的最大的子树的结点数最少,那么u就是树的重心。
  • 删除点u后得到两棵或更多棵互不连通的子树,其中最大子树的结点数最小。u是树上最平衡的点。

若删除① ,得到两棵子树,节点数分别为7和3,最大的子树的结点数为7,比4大,不是重心。

如何计算以结点i为根的子树的结点数量? 

  • 对i做DFS:从i出发,递归到最底层后返回,每返回一个结点,结点数加1(因为每个结点只返回1次),直到所有结点都返回,就得到了子树上结点总数。

如何寻找重心u?
暴力法(O(n^2)):

  1. 删除树上的一个结点u,得到几个孤立的连通块,可以对每个连通块做一次DFS,分别计算结点数量。
  2. 对整棵树逐一删除每个结点,重复上述计算过程,就得到了每个结点的最大连通块

优化(O(n)):只需要一次DFS,就能得到每个结点的最大连通块 

        1、删除u得到三个连通块: (1)包含1的连通块; (2) 包含2 的连通块, (3) 包含3的连通块。 

        2、这三个连通块的数量如何计算?
从任意一个点开始DFS,假设从1开始,1是u的父结点。DFS到结点u后,从u开始继续DFS,得到它的子树2和3的结点数量(2) 和(3),设u为根的子树的结点数量是d[u],则d[u]=(2)+(3)+1。那么 (1)的数量等于n-d[u],n是结点总数。记录(1)、 (2)、 (3)的最大值,就得到了u的最大连通块。

这样通过一次DFS,每个结点的最大连通块都得到了计算,总复杂度O(n)。 

例题 

【问题描述】城里有一个黑手党组织。把黑手党的人员关系用一棵树来描述,教父是树的根,每个结点是一个黑手党徒。为了保密,每人只和他的父结点和他的子结点联系。警察知道哪些人互相来往,但是不知他们的关系。警察想找出谁是教父。
警察假设教父是一个聪明人:教父懂得制衡手下的权力,所以他直属的几个小头目,每个小头目属下的人数差不多。也就是说,删除根之后,剩下的几个互不连通的子树(连通块),其中最大的连通块应该尽可能小。帮助警察找到哪些人可能是教父。
【输入】第一行是n,表示黑手党的人数,2≤n ≤50000。黑手党徒的编号是1到n。下面有n-1行,每行有2个整数,即有联系的2个人的编号。
【输出】输出疑似教父的结点编号,从小到大输出。

 复杂度分析:n最大是50000,最大复杂度可以O(nlogn),DFS复杂度为O(n),没有问题。

import sys
sys.setrecursionlimit(300000)

def dfs(u,fa):
    global num,maxnum   # num:教父的数量;
    d[u] = 1            # 递归到最底层时,结点数加1
    tmp = 0
    # 处理u的所有子树:计算把u中所有子树的结点数量,保存在d[u],记录u中最大子树的结点数量
    for v in edges[u]:          # 遍历u的子结点
        if v == fa:  continue   # 不递归父亲,因为可以用n - d[u]直接算出来
        dfs(v,u)                # 递归子结点,计算v这个子树的结点数量
        d[u] += d[v]            # 计算以u为根的结点数量
        tmp = max (tmp,d[v])    # 记录u的最大子树的结点数量
    tmp = max (tmp,n - d[u])    # tmp = u的最大连通块的结点数。(n - d[u]:父结点所在的子树的结点数)
    # 以上计算出了u的最大连通块
    # 下面统计疑似教父。如果一个结点的最大连通块比其他结点的都小,它是疑似教父
    if tmp < maxnum:        # 如果发现一个疑似教父比之前的教父的最大连通块要小
        maxnum = tmp        # 更新“最小的”最大连通块
        num = 1             # 之前的都不要了,num=1重新统计
        ans[1] = u          # 把教父记录在第1个位置(不用ans[0])
    elif tmp == maxnum:     # 和“最小的”最大连通块结点数相等,也是疑似教父
        num += 1
        ans[num] = u        # 疑似教父有多个,记录在后面


maxnum = int (1e9)  # 无穷大,用来作比较最大子树的结点
n = int (input())
d = [0]*(n+1)       # d[u]:以u为根的子树的结点数量
ans = [0]*(n+1)     # 记录教父
num = 0             # 教父的数量
edges = [[] for i in range(n+1)]
for i in range(n-1):
    a, b = map(int,input().split())
    edges[a].append(b)      # a行加入邻居点(与a相连的点)
    edges[b].append(a)      # b行加入邻居点
dfs(1,0)  # 做一次DFS求出所有教父。从任意一点开始都可以,如果不清楚树结点之间的关系,默认选择1开始,父亲为0(不存在).
s = sorted(ans[1 :num+1])                   # 对教父排序。sorted返回一个列表
for i in range(num): print(s[i], end=' ')   # 按顺序打印所有教文

树的直径

树的直径是指树上最远的两点间的距离,又称为树的最远点对
 

从上图可知,最远的两点为2和6,距离为114。

有两种方法求树的直径:

  1. 做两次DFS (或BFS)
  2. 树形DP

复杂度都是O(n)

优点和缺点:
(1)做两次DFS(或BFS)

  • 优点:能得到完整的路径。它用搜索的原理,从起点u出发一步一步求u到其他所有点的距离,能记录路径经过了哪些点。
  • 缺点:不能用于有负权边的树。

(2)树形DP

  • 优点:允许树上有负权边
  • 缺点:只能求直径的长度,无法得到这条直径的完整路径。 

树的直径例题

【问题描述】求树的直径。
【输入描述】第一行是整数n,表示树的n个点。点的编号从1开始。后面n-1行,每行3个整数a、b、w,表示点a、b之间有一条边,边长为w。
【输出描述】一个整数,表示树的直径。

方法一:做两次DFS

当边权没有负值时,计算树的直径可以通过做两次DFS解决,步骤是:

  1. 从树上的任意一个点r出发,用DFS求距离它最远的点s。s肯定是直径的两个端点之一
  2. 从s出发,用DFS求距离s最远的点t。t是直径的另一个端点。s、t就是距离最远的两个点,即树的直径的两个端点。

这个方法不能用于有负权边的树。例:

  • 第一次DFS,若从点1出发,得到的最远端点s为点2;
  • 第二次DFS从点2出发,得t为点4。
  • 但是,实际上这棵树的直径的两个端点应该是3、4。

总结:以贪心原理进行路径长度搜索的DFS,当树上有负权边时,只能在局部获得最优,而无法在全局获得最优。

import sys
sys.setrecursionlimit(300000)

def dfs(u,father,d):     # 用dfs计算从u到每个子结点的距离
    dist[u] = d          # u到该点的距离d
    for v,w in edges[u]: # 遍历u的所有邻居
        if v != father:  # 很关键,不回头搜父结点
            dfs(v,u,d+w) 

n = int(input())
dist = [0]*(n+1)                 # 一维数组:记录距离
edges = [[] for i in range(n+1)] # 存储树:结点的关系
for i in range(n-1):
    a, b,w = map(int,input().split())
    edges[a].append((b,w))       # a行加入邻居点b和对应的距离w
    edges[b].append((a, w))
# 从任意一点出发,默认从1出发,父亲为-1,距离为0
dfs(1,-1,0) # 求出1到其他点的距离,记录在dist。
s = 1
for i in range (1, n+1):    # 找最远的结点s,s是直径的一个端点
    if dist[i]>dist[s]: s = i
dfs(s,-1,0)                 # 从s出发,计算以s为起点,到树上每个结点的距离
t = 1
for i in range(1, n+1):     # 找距离s最远的点t,t就是直径的另一个端点
    if dist[i]>dist[t]: t = i
print(dist[t])              # 打印树的直径的长度(s到t的距离)

拓扑排序与DFS

  • 设有a、b、c、d等事情,其中a有最高优先级,b、c优先级相同,d是最低优先级,表示为a→(b, c)→ d,那么abcd或者acbd都是可行的排序。
  • 把事情看成图的点,先后关系看成有向边,问题转化为在图中求一个有先后关系的排序,这就是拓扑排序。

 

  • 出度:以点u为起点的边的数量,称为u的出度。例如上图点a的出度为2
  • 入度:以点v为终点的边的数量,称为v的入度。例如上图点d的入度为2
  • 一个点的入度和出度,体现了这个点的先后关系。如果一个点的入度等于0,说明它是起点,是排在最前面的;如果它的出度等于0,说明它是终点
  • 图中,点a的入度为0,它们都是优先级最高的事情;d的出度为0,它的优先级最低。

 用DFS解拓扑排序

  • DFS天然适合拓扑排序。
  • DFS深度搜索的原理,是沿着一条路径一直搜索到最底层,然后逐层回退。
  • 这个过程正好体现了点和点的先后关系,天然符合拓扑排序的原理。

操作 

①如果只有一个点u是0入度的 

  • 那么从u开始DFS,DFS递归返回的顺序就是拓扑排序(是一个逆序)。
  • DFS递归返回的首先是最底层的点,它一定是0出度点,没有后续点,是拓扑排序的最后一个点;然后逐步回退,最后输出的是起点u;输出的顺序是一个逆序
  • 从a开始,递归返回的顺序见点旁边的划线数字: cdba,是拓扑排序的逆序。

 

②如果有多个入度为0的点

  • 想象有一个虚拟的点v,它单向连接到所有其他点。这个点就是图中唯一的0入度点,图中所有其他的点都是它的下一层递归;而且它不会把原图变成环路。从这个虚拟点开始DFS(可以从任意一个点出发,不需要找入度为0的点),就完成了拓扑排序。
  • 图(1)有2个0入度点a和f
  • 图(2)想象有个虚拟点v,递归返回的顺序见点旁边划线数字,返回的是拓扑排序的逆序。

图(2)可以从任意一个点出发返回的顺序都是一样的,例如从a出发,a-b-d-c,然后从c开始返回到a,顺序为1c-2d-3b-4d。然后下次再从e出发,发现下一个点b已经访问过,结束返回5e,最后从f出发,发现下一个点e已经访问过,结束返回6f,所以递归返回的顺序是cdbaef(拓扑排序的逆序)。从b开始,b-d-c,再返回cdb,再从a开始,b访问过,返回a,再从e开始,b访问过,返回e,最后从f开始,e访问过返回f,顺序也是cdbaef。

欧拉路与DFS

欧拉路:从图中某个点出发,遍历整个图,图中每条边通过且只通过一次。(一笔画游戏)

欧拉回路:起点和终点相同的欧拉路。
欧拉路问题:①是否存在欧拉路、②打印出欧拉路。

欧拉路的两种存在形式:①每个结点都是偶数边(欧拉回路)  ②只有两个奇数边的点,一个作为起点,一个作为终点。

  

用DFS输出一个欧拉回路

对一个无向连通图做DFS,就输出了一个欧拉回路。
从图(1)中a点开始DFS,DFS的对象是边。图(2)边上的数字,是DFS访问的顺序。DFS从a点开始,a-b-d-c-a,发现a访问过了,结束返回ac。回到c后再从c开始,c-d-f-e-c,发现c访问过,结束。返回ce-ef-fd-dccd-db-ba。访问顺序为ab-bd-dc-ca-cd-df-fe-ec。图(3)边上的数字是回溯的顺序。顺序为ca-ce-ef-fd-dc-cd-db-ba

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

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

相关文章

1578_AURIX_TC275_MTU中的ECC检测、错误追踪以及运行模式

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) EOV其实是体现了一个错误递增的概念&#xff0c;而且这个是积累到了一定的度。至于具体的规则&#xff0c;其实后面还有更加详细的信息。关于ECC错误纠正使能&#xff0c;相应的处理可能跟…

产品更新!数维图编辑器超10项功能升级

新的一年我们加紧了更新迭代的速度&#xff0c;覆盖数维图三大可视化编辑器产品&#xff0c;超10项功能升级优化。我们将继续保持每天更新的产品升级节奏&#xff0c;满足不同行业用户的更多需求&#xff0c;为用户带来极致的产品使用体验。以下是主要的亮点功能更新汇总&#…

用R语言理解连续性和导数

文章目录微分1 连续性2 求导微分 1 连续性 众所周知微分的几何意义是斜率&#xff0c;然而斜率最初的定义只涉及直线&#xff0c;指的是ykxbykxbykxb中的kkk&#xff0c;而对任意曲线yf(x)yf(x)yf(x)而言&#xff0c;若想谈其斜率&#xff0c;就必须先做出其切线&#xff0c;…

#A. 毛毛虫树

Description给你一棵树希望你找出一条链来&#xff0c;这条链上的点&#xff0c;及这些点直接相连的点&#xff0c;加起来点数尽可能的多FormatInput第一行两个整数N&#xff0c;M&#xff0c;分别表示树中结点个数和树的边数。接下来M行&#xff0c;每行两个整数a, b表示点a和…

lombok快速入门

lombok快速入门 1.安装idea lombok插件 2.加入 maven 依赖 lombok常用注解 1. Getter/Setter 自动产生 getter/setter 2. ToString 自动重写 toString() 方法&#xff0c;会打印出所有变量 3. EqualsAndHashCode 自动生成 equals(Object other) 和 hashcode() 方法&#x…

MATLAB实验四

1.求方程2x5−3x371x2−9x1302x^5-3x^371x^2-9x1302x5−3x371x2−9x130 的全部根 >> p[2 0 -3 71 -9 13]; >> pkg load symbolic >> poly2sym(p) Symbolic pkg v3.0.0: Python communication link active, SymPy v1.10.1. ans (sym)5 3 22*x -…

FPGA图像处理HLS实现sobel边沿检测,提供HLS工程和vivado工程源码

目录一、sobel边沿检测原理二、HLS方案实现sobel边沿检测三、HLS在线仿真并导出IP四、Kintex7开发板vivado工程验证五、zynq7100开发板vivado工程验证六、板级调试验证七、福利&#xff1a;工程源码获取一、sobel边沿检测原理 所谓边缘是指其周围像素灰度急剧变化的那些象素的…

CS架构 企业ERP系统源码 Winform财务设备生产采购进销存源码

淘源码&#xff1a;国内知名的源码免费下载平台 推荐环境&#xff1a;vs2019 sql server 2008 r2以上 源码简介 本系统属于中小型企业ERP管理系统&#xff0c;可以对中小型生产企业或商业企业进行有效管理。 系统包含进销存、财务、生产、委外、财务、设备管理、权限管理等模…

【可解释性机器学习】基于ELI5使用解读LIME算法以及实战案例

LIME算法解读与实战案例LIME论文简介LIME算法原理LIME算法要点LIME的注意事项LIME的代码实现对Pytorch搭建的模型进行解释使用LIME解释Pytorch构建的模型参考资料LIME论文简介 LIME的全称为Local Interpretable Model-agnostic Explanations. 尽管被广泛采用&#xff0c;机器…

模板进阶篇

一、非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。如图&#xff1a; 非类型形参&#xff1a;就是用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模板中可…

Mybatis 原理之启动阶段

文章目录1.MyBatis 核心流程2.启动准备阶段流程3.创建 SQlSessionFactory4.创建XMLConfigBuilder5.创建 XPathParser6.解析并设置 configuration 中的属性7.解析Mappers标签1.MyBatis 核心流程 Mybatis的核心流程氛围两个阶段&#xff0c;启动准备阶段和执行SQL阶段。 加载配…

Day858.高性能网络应用框架Netty -Java 并发编程实战

高性能网络应用框架Netty Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于高性能网络应用框架Netty的内容。 Netty 是一个高性能网络应用框架&#xff0c;应用非常普遍&#xff0c;目前在 Java 领域里&#xff0c;Netty 基本上成为网络程序的标配了。 Netty 框架功…

win10录屏软件哪款比较好用?一款不限时长的录屏软件

现在大部分人的电脑都是win10系统的电脑&#xff0c;也有许多小伙伴会经常会问&#xff1a;“win10电脑怎么录屏&#xff1f;”录制电脑屏幕&#xff0c;需要使用到录屏软件&#xff0c;那win10录屏软件哪款比较好用&#xff1f;小编今天给大家分享一款试用版即可不限录制时长的…

【区间合并】洛谷 P1496 火烧赤壁

P1496 火烧赤壁 文章目录题目背景题目描述输入格式&#xff1a;输出格式&#xff1a;数据范围输入样例输出样例方法&#xff1a;区间合并解题思路代码复杂度分析&#xff1a;题目背景 曹操平定北方以后&#xff0c;公元 208 年&#xff0c;率领大军南下&#xff0c;进攻刘表。…

部分时变离散系统中的稳定性判据

部分时变离散系统中的稳定性判据 1.Lyapunov稳定性理论 下面先给出Lyapunov稳定性的一些基本理论&#xff08;网上资源较多这里不再过多赘述&#xff09;&#xff1a; 2.一类时变离散系统的稳定性 定理 ​ 对于离散时变系统x(k1)A(k)x(k)x(k1)A(k)x(k)x(k1)A(k)x(k)&#x…

Java EE|多线程代码实例之单例模式与阻塞队列

文章目录前言设计模式介绍&#x1f534;单例模式什么是单例模式单例模式实现方式饿汉模式懒汉模式基于上述单例模式实现线程安全问题讨论重点回顾&#x1f534;阻塞队列阻塞队列是什么标准库中的阻塞队列典型应用场景&#xff1a;生产者消费者模型利用系统提供的BlockingQueue实…

osg fbo(三),将颜色缓冲区图片通过shader变绿

这个其实很简单&#xff0c; 一&#xff0c;写顶点着色器和片元着色器 static const char * vertexShader { “void main(void)\n” “{\n” " gl_Position ftransform();\n" “}\n” }; static const char *psShader { “uniform float alpha;” “void main(vo…

12、ThingsBoard-如何配置发送邮件

1、概述 ThingsBoard提供了系统层设置邮件配置和租户层通过设置邮件规则节点,对规则引擎产生的告警进行分发这两种邮件配置,其中系统层设置邮件配置主要是针对用于向用户分发激活和密码重置电子邮件;租户层通过设置邮件规则节点是针对告警通知的;一定要区别开这两个邮件配…

SpringBoot整合SpringSecurity实现进行认证和授权。

目录 2.在子工程通过easyCode创建项目相关包和文件 3.子项目新建Controllter层&#xff0c;并建立BlogLoginController.java 4.在servic 层定义login 方法&#xff0c;并new UsernamePasswordAuthenticationToken对象&#xff0c;传入对应用户名&#xff0c;密码 5.自定义实…

Java集合(进阶)

Java集合Collection集合体系结构CollectionCollection系列集合三种遍历方式List泛型泛型类泛型方法泛型接口泛型的继承和通配符SetHashSetTreeSet总结&#xff1a;Map&#xff08;双列集合&#xff09;HashMapLinkedHashMapTreeMap可变参数集合工具类Collections集合嵌套案例不…