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

news2024/11/19 5:27:39

树是一种特殊的图 。

特点:

  • 若树有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/159773.html

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

相关文章

二部图和匈牙利算法

1.二分图最大匹配 设G为二分图,若在G的子图M中,任意两条边都没有公共节点,那么称M为二分图G的一组匹配。在二分图中,包含边数最多的一组匹配称为二分图的最大匹配。 交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路。 …

【算法】深度优先搜索 (DFS)

目录1.概述2.代码实现3.应用1.概述 &#xff08;1&#xff09;深度优先遍历 (Depth First Search, DFS)&#xff0c;是图的搜索算法之一&#xff0c;本质其实就是一个递归的过程&#xff0c;它就像是一棵树的前序遍历。 &#xff08;2&#xff09;DFS 从图中某个顶点 start 出…

游戏如何解决注入挂难题

游戏黑灰产的攻击角度除了常见的内存修改、模拟点击、破解等作弊手段&#xff0c;还有门槛相对较高的「专用插件类」。 专用插件类外挂是指针对特定游戏定制的外挂&#xff0c;其在实现方式上&#xff0c;类似插件&#xff0c;也称“定制挂”、“注入挂”。 游戏面临多样化的安…

招聘老师的最佳实践及工作交流坊

香港 — 如果你在招聘外籍老师途中遇到极大的挑战&#xff0c;你并不是孤独的。由于新冠肺炎的限制及对于外籍老师的需求增加&#xff0c;招聘及留住高质量的外籍老师对于学校来说已经越来越困难。在疫情之下&#xff0c; 许多学校展现了非凡的韧性来确保他们的教学质量及学习供…

【HCIA-openEuler】实验手册—04【openEuler用户及权限管理】

文章目录一、实验介绍1、关于本实验2、实验目的二、实验任务配置1、配置步骤&#xff08;1&#xff09;用户和用户组的管理步骤1&#xff1a;who命令是显示目前登录系统的用户信息步骤2&#xff1a;id命令用于显示用户的ID&#xff0c;以及所属群组的ID步骤3&#xff1a;以root…

Vivado 错误代码 [Place 30-574]解决思路

问题描述 最近利用手头的开发板作UDP通信的设计。准备生成比特流时&#xff0c;出现这个错误&#xff1a; 具体信息&#xff1a; [Place 30-574] Poor placement for routing between an IO pin and BUFG. If this sub optimal condition is acceptable for this design, you …

Java(105):Java通过键盘(Scanner)输入数据

Java通过键盘(Scanner)输入数据 在Java中&#xff0c;我们可以使用Scanner 类来获取用户的输入。 Java 中添加了java.util.Scanner类&#xff0c;这是一个用于扫描输入文本的新的实用程序。相比于其他获取用户输入的方式&#xff0c;Scanner是非常方便的。 如果使用Scanner&…

如何理解鲁棒性?为什么robustness会翻译为鲁棒性?

鲁棒性&#xff0c;英文为Robustness&#xff08;承受故障和干扰的能力&#xff09;&#xff0c;是许多复杂系统&#xff08;包括复杂网络&#xff09;的关键属性。复杂网络的鲁棒性研究对许多领域都非常重要。本文着重介绍了鲁棒性的基本定义、命名起源、分类区别、提升方法和…

一图读懂mybatis插件plugin原理

插件是用来改变或者扩展mybatis的原有的功能&#xff0c;mybaits的插件就是通过继承Interceptor拦截器实现的;mybatis中能使用插件进行拦截的 可以进行拦截的 接口和方法如下: Executor (update、query 、 flushStatment 、 commit 、 rollback 、 getTransaction 、 close 、…

机试_1_暴力求解_习题

暴力求解——习题 学习完第一章–暴力求解之后&#xff0c;当然要做相应地练习啦~ https://blog.csdn.net/Window_mouse/article/details/128632426 注&#xff1a;上述习题都可以在牛客进行测试。 例如&#xff0c;第9题链接&#xff1a;xxx定律_牛客题霸_牛客网 (nowcode…

近几年美赛B题分析

美赛B题概述&#xff1a; 美赛赛题类型美国大学生数学建模竞赛目前分为两种类型&#xff0c;MCM&#xff08;Mathematical Contest In Modeling&#xff09;和 ICM&#xff08;Interdisciplinary Contest In Modeling)&#xff0c;两种类型竞赛采用统一标准进行&#xff0c;竞…

金融风控04

特征工程 Filter 1&#xff09;移除低方差特征 假设某特征的特征值只有0和1&#xff0c;并且在所有输入样本中&#xff0c;95%的实例的该特征取值都是1&#xff0c;那就可以认为这个特征作用不大。如果100%都是1&#xff0c;那这个特征就没意义了。当特征值都是离散型变量的…

攻克强化学习技术难题记录

一共经过了5次迭代。 第1次迭代的设计思路&#xff1a; 强化学习demo游戏“cartpole”重述 游戏目标&#xff1a;向左/右移动小车cart&#xff0c;保证杆pole始终在小车上方&#xff0c;是大多数强化学习入门教材都会介绍的一个经典案例。 强化学习要素分析&#xff1a; 智…

【项目实战】Nacos下发路由配置实现Spring Cloud Gateway的动态路由

Spring Cloud Gateway网关的使用和Nacos下发路由配置实现Spring Cloud Gateway的动态路由 一、微服务网关概述 1.1 微服务网关诞生背景 不同的微服务一般会有不同的网络地址&#xff0c;而外部客户端可能需要调用多个服务的接口才能完成一个业务需求&#xff0c;如果让客户端…

泰凌微被暂缓审议:利润下滑遭关注,实控人王维航存在大额负债

1月12日&#xff0c;上海证券交易所披露的信息显示&#xff0c;泰凌微电子&#xff08;上海&#xff09;股份有限公司&#xff08;下称“泰凌微”&#xff09;的首发申请被科创板上市委暂缓审议。据贝多财经了解&#xff0c;上市委现场问题对该公司提出多个问题。 根据申请文件…

用详细实例说明和典型案例实现对分治法进行全面分析 | C++

第一篇 分治法 目录 第一篇 分治法 ●前言 ●一、分治法是什么&#xff1f; 1.简要介绍 2.生活实例 ●二、分治法的典型案例——硬币问题 1.具体问题 2.代码展示&#xff08;C&#xff09; 3.程序代码结果展示 ●总结 前言 简单的来说&#xff0c;算法就是用计算机程序代…

菲中工商贸投资合作签约活动在京举办

2023年1月3日至5日&#xff0c;中菲两国元首亲切会谈后&#xff0c;共同发布了成果丰硕的二十八条内容的联合声明。1月3日至9日&#xff0c;由菲律宾菲中人民友好促进会与中国联合国采购促进会在京联合举办了“菲中工商贸投资合作签约仪式”及“中菲合作项目对接洽谈周”活动。…

FPGA:逻辑函数的代数法化简

文章目录逻辑函数的最简形式逻辑函数的代数化简法并项法吸收法消去法配项法示例1示例2逻辑函数的最简形式 1&#xff0e;化简逻辑函数的意义 LABAˉBAˉBˉ(AAˉ)BAˉBˉ1⋅BAˉBˉBAˉ\begin{aligned} L & A B\bar{A} B\bar{A} \bar{B} \\ & (A\bar{A}) B\bar{A} \ba…

PELT——Per Entity Load Tracking

0. 前言&#xff1a; 今天写第一篇Linux内核调度子系统的文章&#xff0c;首先整理PELT负载追踪方法&#xff0c;之前的基础知识在后续的文章中share出来。文章的写成基本上是在几位内核大佬的文章基础之上完成的&#xff0c;有些地方的文字是直接引用的&#xff0c;但本文只用…

SpringBoot上传文件到Minio服务器

前言 本文主要介绍如何使用SpringBoot上传到minio服务器。 没什么可多说的&#xff0c;公司用什么咱们开发研究什么就完事了。直接分享核心代码。 核心代码 minio依赖 <!-- minio依赖 --><dependency><groupId>io.minio</groupId><artifactI…