DFS的树上应用

news2024/12/26 9:30:19

目录

一、前言

二、树上的DFS

1、树的重心

2、树的重心例题

3、树的直径

4、树的直径例题

(1)做两次DFS

三、拓扑排序与DFS

1、用DFS解拓扑排序

2、欧拉路与DFS

3、用DFS输出一个欧拉回路


一、前言

本文主要讲了树上的DFS、树的重心、树的直径、拓扑排序与DFS、欧拉路等理论内容。

二、树上的DFS

1、树的重心

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

  • 如何计算以结点 i 为根的子树的结点数量?
  • 对 i 做 DFS:从 i 出发,递归到最底层后返回,每返回一个结点,结点数加 1,直到所有结点都返回,就得到了子树上结点总数。
  • 因为每个结点只返回 1 次,所以这个方法是对的。

那么如何寻找树的重心呢?

暴力法:

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

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

删除 u 得到三个连通块:

(1)包含 1 的连通块; (2)包含 2 的连通块; (3)包含 3 的连通块。

这三个连通块的数量如何计算?

从任意一个点开始 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)。 

2、树的重心例题

【问题描述】

城里有一个黑手党组织。把黑手党的人员关系用一棵树来描述,教父是树的根,每个结点是一个黑手党徒。为了保密,每人只和他的父结点和他的子结点联系。警察知道哪些人互相来往,但是不知他们的关系。警察想找出谁是教父。

警察假设教父是一个聪明人:教父懂得制衡手下的权力,所以他直属的几个小头目,每个小头目属下的人数差不多。也就是说,删除根之后,剩下的几个互不连通的子树(连通块),其中最大的连通块应该尽可能小。帮助警察找到哪些人可能是教父。

【输入】

第一行是 n,表示黑手党的人数,2 <= n <= 50000。黑手党徒的编号是 1 到 n。下面有 n-1 行,每行有 2 个整数,即有联系的 2 个人的编号。

【输出】

输出疑似教父的结点编号,从小到大输出。

【输入样例】

6

1 2

2 3

2 4

4 5

3 6

【输出样例】

2 3

import sys
sys.setrecursionlimit(300000)    #设置递归深度,否则不能通过100%的测试

def dfs(u,fa):
    global num,maxnum   #num:教父的数量
    d[u]=1              #递归到最底层时,结点数加1
    tmp=0
    for v in edges[u]:  #遍历u的子节点
        if v==fa:
            continue
        dfs(v,u)        #递归子节点,计算v这个子树的结点数量
        d[u]+=d[v]      #计算以u为根的节点数量
        tmp=max(tmp,d[v])   #记录u的最大子树的节点数量
    tmp=max(tmp,n-d[u]) #tmp=u 的最大连通块的结点数
        #以上计算出了u的最大连通块
        #下面统计疑似教父。如果一个结点的最大连通块比其他结点的都小,它是疑似教父
    if tmp<maxnum:      #一个疑似教父
        maxnum=tmp      #更新"最小的"最大连通块
        num=1
        ans[1]=u        #把教父记录在第1个位置
    elif tmp==maxnum:
        num+=1
        ans[num]=u      #疑似教父有多个,记录在后面

maxnum=int(1e9)
n=int(input())
d=[0]*(n+1)     #以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)
    edges[b].append(a)
dfs(1,0)
s=sorted(ans[1:num+1])      #对教父排序
for i in range(num):
    print(s[i],end=' ')     #按顺序打印所有教父

3、树的直径

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

有两种方法求树的直径:

(1)做两次DFS(或BFS);

(2)树形DP。

复杂度都是 O(n)。

-----------------------------------------------------------------------------------------------

优点和缺点:

(1)做两次DFS(或BFS)

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

缺点:不能用于有负权边的树。

(2)树形DP

优点:允许树上有负权边。

缺点:只能求直径的长度,无法得到这条直径的完整路径。

4、树的直径例题

【问题描述】

求树的直径。

【输入描述】

第一行是整数 n,表示树的 n 个点。点的编号从 1 开始。后面 n-1 行,每行 3 个整数 a、b、w,表示点 a、b 之间有一条边,边长为 w。

【输出描述】

一个整数,表示树的直径。

【输入样例】

5

1 2 5

1 3 19

1 4 23

4 5 12

【输出样例】

54

(1)做两次DFS

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

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

s、t 就是距离最远的两个点,即树的直径的两个端点。

证明:

  • 把这棵树所有的边想象成不同长度的柔性绳子,并假设已经找到了直径的两个端点 s 和 t。
  • 双手抓住 s 和 t,然后水平拉直成一条长线,这是这棵树能拉出来的最长线。
  • 这时,其他的绳子和点会下垂。
  • 任选一个除 s 和 t 以外的点 r,它到 s(或t)的距离肯定是最远的。
  • 如果不是最远的,那么下垂的某个点就能替代s,这跟假设 s 是直径的端点矛盾。

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

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

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

import sys
sys.setrecursionlimit(300000)

def dfs(u,fa,d):        #用dfs计算从u到每个子结点的距离
    dist[u]=d
    for v,w in edges[u]:
        if v!=fa:       #很关键,不回头搜父结点
            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))
    edges[b].append((a,w))
dfs(1,-1,0)
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):      #找直径的另一个端点t
    if dist[i]>dist[t]:
        t=i
print(dist[t])      #打印树的直径的长度

三、拓扑排序与DFS

设有 a、b、c、d 等事情,其中 a 有最高优先级,b、c 优先级相同,d 是最低优先级,表示为a → (b,c) → d,那么 abcd 或者 acbd 都是可行的排序。

把事情看成图的点,先后关系看成有向边,问题转化为在图中求一个有先后关系的排序,这就是拓扑排序。

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

1、用DFS解拓扑排序

  • DFS 天然适合拓扑排序。
  • DFS 深度搜索的原理,是沿着一条路径一直搜索到最底层,然后逐层回退。
  • 这个过程正好体现了点和点的先后关系,天然符合拓扑排序的原理。
  • 如果只有一个点 u 是 0 入度的,那么从 u 开始 DFS, DFS 递归返回的顺序就是拓扑排序(是一个逆序)。
  • DFS 递归返回的首先是最底层的点,它一定是 0 出度点,没有后续点,是拓扑排序的最后一个点;然后逐步回退,最后输出的是起点 u;输出的顺序是一个逆序。
  • 从 a 开始,递归返回的顺序见点旁边的划线数字:cdba,是拓扑排序的逆序。

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

2、欧拉路与DFS

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

欧拉回路:起点和终点相同的欧拉路。

欧拉路问题:是否存在欧拉路、打印出欧拉路。

3、用DFS输出一个欧拉回路

对一个无向连通图做 DFS,就输出了一个欧拉回路。

从图 (1) 中 a 点开始 DFS,DFS 的对象是边。图 (2) 边上的数字,是 DFS 访问的顺序。

以上,DFS的树上应用

祝好

 

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

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

相关文章

538. 把二叉搜索树转换为累加树

538. 把二叉搜索树转换为累加树 难度中等 给出二叉 搜索 树的根节点&#xff0c;该树的节点值各不相同&#xff0c;请你将其转换为累加树&#xff08;Greater Sum Tree&#xff09;&#xff0c;使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。 提醒一下&am…

关于浮点数使用的两个注意事项(C/C++)

目录 一.回顾浮点数的存储与读取 二.浮点数使用的第一个注意事项 三.浮点数使用的第二个注意事项 附&#xff1a; 观察内存中的FLT_MAX和FLT_MIN 一.回顾浮点数的存储与读取 http://t.csdn.cn/oVwte 浮点数的存入与读取流程总览&#xff1a; 二.浮点数使用的第一个注意事…

理解实现八大排序

目录 一、初步认识 二、直接插入排序 三、希尔排序 四、直接选择排序 五、堆排序 六、冒泡排序 七、快速排序 7.1 Hoare版本 7.2 挖坑法 7.3 前后指针法 7.4 非递归 7.5 优化方法 7.5.1 三数取中 7.5.2 小区间优化 八、归并排序 九、计数排序 一、初步认识 排…

Netty入门

二. Netty 入门 1. 概述 1.1 Netty 是什么&#xff1f; Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.Netty 是一个异步的、基于事件驱动的网络应用框架&…

简单开发网站+HTML标签

目录 一、学习路线 二、快速开发网站 1、简单demo 2、浏览器能识别的标签 ① 编码② Title ③ 标题 ④ div和span ⑤ 超链接 ⑥ 图片⑦ 列表 ⑧ 表格 ⑨ input系列 ⑩ 下拉框 ⑪ 多行文本 三、网络请求 四、案例 1、用户注册 2、用户登录 五、小结 1、学习标签的总…

网易互客CRM 微盟系统 管易系统 金蝶系统对接集成整体解决方案

前言&#xff1a;大部分的企业都可能只用一套系统组织架构复杂&#xff0c;业务流程繁琐&#xff0c;内部同时有CRM系统、OMS系统、ERP系统......且各个系统都需要独立登陆&#xff0c;造成IT部门数据监管困难&#xff01;如何在同一套中台系统上关联多管理系统呢&#xff1f;系…

【GD32F427开发板试用】-03-定时器1 的不算坑的坑和时钟设置

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;申小林 先说一下我使用定时器1 的时候吧&#xff0c;最开始我以为定时器1是挂在APB1上的&#xff0c;随意按照惯性思维&#xff0c;定时器的时…

vue3使用svg图标多种方式

方式1使用在线链接访问 在iconfont找到自己的项目的图标选择Symbol获取在线链接 2&#xff1a;在vue3项目中找到public的index.html进行script进行引入 打开浏览器看&#xff1a;这样就会自动注入到body下 在项目直接使用 //控制图标的大小<svg style"width: 10px; …

SAP ABAP 函数组组件缺失检查

有没有遇到如下几个场景 场景1 开发1&#xff0c;新建函数组1&#xff0c;创建函数1 开发2&#xff0c;在函数组1里&#xff0c;创建函数2 两者都传Q测试&#xff0c;开发2的先QAT完后发布生产&#xff0c;请求dump&#xff0c;找不到函数2 场景2 函数组1已传生产 开发1&#x…

编解码-性能优化-SIMD

文章目录前言MMXSSEAVX使用内置函数使用SSE/AVX命名规则SSE/AVX操作类别实战汇编使用优化前代码详解优化后代码详解引用文章#mermaid-svg-cWLDz5Rki1i4TgZ1 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#mermaid-svg-cWLDz5Rki…

【JavaGuide面试总结】MySQL篇·中

【JavaGuide面试总结】MySQL篇中1.MySQL 的隔离级别是基于锁实现的吗&#xff1f;2.表级锁和行级锁了解吗&#xff1f;有什么区别&#xff1f;3.共享锁和排他锁简单说说4.意向锁有什么作用&#xff1f;5.InnoDB 有哪几类行锁&#xff1f;6.当前读和快照读有什么区别&#xff1f…

Go语言循环语句

Go语言循环语句 资料参考至菜鸟教程。 在不少实际问题中有许多具有规律性的重复操作&#xff0c;因此在程序中就需要重复执行某些语句。 以下为大多编程语言循环程序的流程图&#xff1a; Go语言提供了以下几种类型循环处理语句&#xff1a; 循环类型描述for循环重复执行语句块…

Base64

概述 Base64是一种基于64个字符的编码算法,经过Base64编码后的数据会比原始数据略长,为原来的4/3倍。经Base64编码后的字符串的字符数是以4为单位的整数倍。 编码表 即64个字符分别是: 字符个数A-Z26a-z260-910+1/1=用于补位 在电子邮件中,每行为76个字符,每行末需添加一…

【青训营】Go的依赖管理

Go的依赖管理 本节内容来自于&#xff1a;字节跳动青年训练营第五届 后端组 1.什么是依赖 实际开发的工程需要使用许多第三方库&#xff0c;这能够使得我们站在巨人的肩膀上&#xff0c;使用第三方库中封装好的函数&#xff0c;可以大大方便我们的程序开发&#xff0c;第三方…

Microsoft Teams上的编程教育

内容提示&#xff1a;Microsoft Teams上的 MakeCode Arcade 使用形式&#xff1a;Microsoft Teams中的 “作业” 服务 应用场景&#xff1a;编程教育 社团活动 个人经验&#xff1a;在校期间&#xff0c;每周学校都会有社团活动&#xff0c;学生们根据自己的兴趣爱好来选择社…

struct 结构体的内存对齐

&#x1f499;作者&#xff1a;阿润菜菜 &#x1f4d6;专栏&#xff1a;一起学习C言 本文目录 在内存中观察 结构体内存对齐的规则&#xff1a; 为什么存在内存对齐&#xff1f; 编程中我们该如何设计结构体&#xff1f; 修改默认对齐数 相关笔试题 在内存中观察 首先…

el-date-picker 目前只能通过点击input输入框触发日期选择器,怎么通过其他方式触发日期选择器同时把input输入框去掉,如点击按钮

依然是该模块由于后端接口数据传输限制 在前面文章里做了些许改动。 需求左右切换 可以快速找到年份&#xff0c;于是添加了element选择年份的日期组件。 图中隐藏了el-data-picker日期组件&#xff0c;手动添加样式展示时间栏选择的数据进行 0 回显。 点击时间时&#xff0c;…

想看看一个影片评论怎么样?python带你采集数据做词云

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 目录前言环境使用:模块使用安装python第三方模块:思路分析代码展示数据采集词云图尾语 &#x1f49d;环境使用: Python 3.8 解释器 Pycharm 编辑器 模块使用 import parsel >>> pip install parsel import req…

《玩转 Spring 全家桶》学习笔记Day1

Spring 诞生于2002年&#xff0c;成型于2003年。Spring Boot 负责构建、Spring Cloud 协调、Spring Cloud Data Flow 负责连接一切。Spring Framework理念&#xff0c;向后兼容&#xff0c;专注API设计&#xff0c;让选择无处不在&#xff0c;海纳百川&#xff0c;严苛的代码质…

N+1终于等到了 但却放弃了

在公司呆了8年了&#xff0c;做梦都想被开除&#xff0c;年底等到了。但......2021年年底绩效B&#xff0c;可是公司年终奖泡沫了&#xff1b;估摸着2022年公司可能会发奖金&#xff0c;那我也悄悄的给自己定了目标&#xff0c;大干一场&#xff0c;争取过年拿个好结果。跟着公…