【图论 深度优先搜索 反证法 分类讨论】树的直径

本文涉及知识点

深度优先搜索汇总
图论知识汇总
反证法 分类讨论

相关定义和有关名称

有根树的深度:根到叶子节点的距离。
有根数的搞定:叶子到根节点的距离。
两者本质是一样,习惯不同而已。
树的直径——就是树中距离最大的两点的距离。 以任意节点为根的最大深度。

暴力解法

令 直径所在路径为path。 将无根树转化成以0为根的有根树。path在此根树的最早公共节点的为pub,枚举pub。
DFS返回cur 节点到叶子的最大深度。
{ m a x S e l f ( m i R e t , 1 ) c u r 子节点为 0 m a x S e l f ( m i R e t , 1 + D F S ( n e x t ) c u r 子节点数为 1 m a x S e l f ( m i R e t 1 , 1 + m a x ( D F S ( n e x t ) ) + m a x 2 ( D F S ( n e x t ) ) ; o t h e r \begin{cases} maxSelf(&m_iRet,1) && cur子节点为0 \\ maxSelf(&m_iRet,1+ DFS(next) && cur子节点数为1 \\ maxSelf(&m_iRet1,1 + max(DFS(next)) + max2(DFS(next)); && other \\ \end{cases} maxSelf(maxSelf(maxSelf(miRet,1)miRet,1+DFS(next)miRet1,1+max(DFS(next))+max2(DFS(next));cur子节点为0cur子节点数为1other
max2(DFS(next)) 是 DFS(next) 的次大值。

教科书上的解法

以任何根节点(比如0 )DFS 获取最大深度的叶子节点n1,如果有多个获取任意一个。
以n1为根节点的有根树就是最大深度。下面来证明:
令数的直径为: n 2 → p u b → n 3 n2 \rightarrow pub \rightarrow n3 n2pubn3 pub是 以0为根的有根树的最近公共祖先。
分情况讨论:
一, n1是pub的子孙。
p u b → n 1 pub \rightarrow n1 pubn1 一定不小于 p u b → n 3 pub \rightarrow n3 pubn3 否则 n3的深度更大。
p u b → n 1 pub \rightarrow n1 pubn1 一定不大于 p u b → n 3 pub \rightarrow n3 pubn3 否则n1 → \rightarrow n2 才是直径。
二,n1不是pub的子孙。
令puc和n1的最近公共祖先是pub2。
p u c 2 → n 1 puc2 \rightarrow n1 puc2n1 一定不小于 p u b 2 → n 3 pub2 \rightarrow n3 pub2n3 否则 n3的深度更大。
p u c 2 → n 1 puc2 \rightarrow n1 puc2n1 一定不大于 p u b 2 → n 3 pub2 \rightarrow n3 pub2n3 否则n1 → \rightarrow n2 才是直径。
p u c 2 → n 1 puc2 \rightarrow n1 puc2n1 == p u b 2 → n 3 pub2 \rightarrow n3 pub2n3    ⟺    \iff pub1 == pub2 。 否则 n1到n3 比n2到n3长,即n2到n3不是直径,与假设矛盾。

例题:1617

题目及讲解见:【图论】【状态压缩】【树】【深度优先搜索】1617. 统计子树中城市之间最大距离

二次DFS深度求树直径的代码

auto it = std::max_element(dfs.m_vLeve.begin(), dfs.m_vLeve.end());
		dfs.DFS(it - dfs.m_vLeve.begin());
		it = std::max_element(dfs.m_vLeve.begin(), dfs.m_vLeve.end());

完整代码

class CBitCounts
{
public:
	CBitCounts(int iMaskCount)
	{
		for (int i = 0; i < iMaskCount; i++)
		{
			m_vCnt.emplace_back(bitcount(i));
		}
	}
	template<class T>
	static int bitcount(T x) {
		int countx = 0;
		while (x) {
			countx++;
			x &= (x - 1);
		}
		return countx;
	}
	vector<int> m_vCnt;
};

int EndZeroCount(unsigned x )
{
	for (int i = 0; i < 32; i++)
	{
		if ((1 << i) & x)
		{
			return i;
		}
	}
	return 32;
}

class CDFS
{
public:
	CDFS(const vector<vector<int>>& vNeiBo):m_vNeiBo(vNeiBo){

	}	
protected:
	vector<vector<int>> m_vNeiBo;
};
class CDFSLeve : public CDFS
{
public:
	using CDFS::CDFS;
	void DFS(int root) {
		m_vLeve.assign(m_vNeiBo.size(), -1);
		m_vLeve[root] = 0;
		DFS(root, -1);
	}
	vector<int> m_vLeve;
protected:
	void DFS(int cur, int par) {
		if (-1 != par) {m_vLeve[cur] = m_vLeve[par] + 1;}
		for (const auto& next : m_vNeiBo[cur]){
			if (next == par){continue;}
			DFS(next, cur);
		}
	}		
};
class Solution {
public:
	vector<int> countSubgraphsForEachDiameter(int n, vector<vector<int>>& edges) {
		vector<int> vRet(n - 1);
		for (auto& v : edges) { v[0]--; v[1]--; };
		for (int mask = 0; mask < (1 << n); mask++)
		{
			const int iNodeCnt = n - CBitCounts::bitcount(mask);
			if (iNodeCnt < 2) { continue; }//一个节点或0个节点忽略
			vector<vector<int>> vNeiBo(n);
			for (const auto& v : edges) {				
				if ((mask & (1 << (v[0]))) || (mask & (1 << v[1]))) { continue; }//有端点被删除,忽略
				vNeiBo[v[0]].emplace_back(v[1]);
				vNeiBo[v[1]].emplace_back(v[0]);
			}
			CDFSLeve dfs(vNeiBo);
			dfs.DFS(EndZeroCount((1 << n) -1 - mask));
			const int iVisitNode = n - std::count(dfs.m_vLeve.begin(), dfs.m_vLeve.end(), -1);
			if (iVisitNode < iNodeCnt) { continue; }//删除节点后,是森林不是树
			auto it = std::max_element(dfs.m_vLeve.begin(), dfs.m_vLeve.end());
			dfs.DFS(it - dfs.m_vLeve.begin());
			it = std::max_element(dfs.m_vLeve.begin(), dfs.m_vLeve.end());
			vRet[*it - 1]++;
		}
		return vRet;
	}
	vector<int> m_vDis1, m_vDis2;
};

暴力求直径

class CDFSForDiameter : public CDFS
{
public:
	using CDFS::CDFS;
	int DFS(int root) {
		DFS(root, -1);
		return m_iRet;
	}
protected:
	int m_iRet=1;//记录树的直径,0个节点的树会出错。
	int DFS(int cur, int par) {
		int left = 0;
		for (const auto& next : m_vNeiBo[cur]) {
			if (next == par) { continue; }
			auto right = DFS(next, cur);
			MaxSelf(&m_iRet, left + right + 1);
			MaxSelf(&left, right);
		}
		return left + 1;
	}
};

可以记录DFS节点的数量,这样可以直接判断是否是连通。

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
《喜缺全书算法册》以原理、正确性证明、总结为主。
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闻缺陷则喜何志丹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值