【C++图论 树】3203. 合并两棵树后的最小直径|2266

本文涉及的基础知识点

C++图论

LeetCode3203. 合并两棵树后的最小直径

给你两棵 无向 树,分别有 n 和 m 个节点,节点编号分别为 0 到 n - 1 和 0 到 m - 1 。给你两个二维整数数组 edges1 和 edges2 ,长度分别为 n - 1 和 m - 1 ,其中 edges1[i] = [ai, bi] 表示在第一棵树中节点 ai 和 bi 之间有一条边,edges2[i] = [ui, vi] 表示在第二棵树中节点 ui 和 vi 之间有一条边。
你必须在第一棵树和第二棵树中分别选一个节点,并用一条边连接它们。
请你返回添加边后得到的树中,最小直径 为多少。
一棵树的 直径 指的是树中任意两个节点之间的最长路径长度。
示例 1:
输入:edges1 = [[0,1],[0,2],[0,3]], edges2 = [[0,1]]
输出:3
解释:
将第一棵树中的节点 0 与第二棵树中的任意节点连接,得到一棵直径为 3 的树。
示例 2:
输入:edges1 = [[0,1],[0,2],[0,3],[2,4],[2,5],[3,6],[2,7]], edges2 = [[0,1],[0,2],[0,3],[2,4],[2,5],[3,6],[2,7]]
输出:5
解释:
将第一棵树中的节点 0 和第二棵树中的节点 0 连接,可以得到一棵直径为 5 的树。
提示:
1 <= n, m <= 105
edges1.length == n - 1
edges2.length == m - 1
edges1[i].length == edges2[i].length == 2
edges1[i] = [ai, bi]
0 <= ai, bi < n
edges2[i] = [ui, vi]
0 <= ui, vi < m
输入保证 edges1 和 edges2 分别表示一棵合法的树。

图论 树

树的层次,距离根的距离,根的层次是0。
树的半径,树的最大层次。
树的重心,树的层次最小的根。
分两种情况:
新树的直径是旧树的直径。
新树的直径跨越两棵树,即两棵树的半径+1。

性质一:如果树的直径是d,令两个端点是n1,n2。d是偶数。则树的半径r = d/2。重心就是直径的中点o。一定不存在端点n3到o的距离大于r。否则n1到n3的距离大于d,与假设矛盾。
性质二:如果d是奇数,直径的两个中点都是重心,r = d/2+1。除了n1,n2外,不存在端点n3距离o的距离大与d/2。
总结: r = (d+1)/2
性质三:如果d是偶数,可以有多条直径,这些直径有共同的中心。否则与性质一矛盾。
性质四:如果d是奇数,只能有一条直径,否则与性质二矛盾。

求树的直径方法一:DFS

以任意节点为根DFS,枚举各子树的直径。DFS(cur)返回值:cur子树的最大层次,cur子树的直径。

求树的直径方法二:两轮BFS

以任意节点root,BFS到任意最大层次节点n3,n3 一定是直径的一个端点。令此树的重心是o,某直径端点是n1,n2。o到n3的路径上不可能同时包括n1,n2。不失一般性,节点n2不在o到n3的路径上。由于o是重心,root到非o方向的距离一定小于等于r,否则此节点到n2的距离大于直径,与题设矛盾。root到o方向的最大距离显然是n2。
以n3为根的最大层次,就是直径。

代码

核心代码

class CNeiBo
{
public:	
	static vector<vector<int>> Two(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0) 
	{
		vector<vector<int>>  vNeiBo(n);
		for (const auto& v : edges)
		{
			vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase);
			if (!bDirect)
			{
				vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase);
			}
		}
		return vNeiBo;
	}	
	static vector<vector<std::pair<int, int>>> Three(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0)
	{
		vector<vector<std::pair<int, int>>> vNeiBo(n);
		for (const auto& v : edges)
		{
			vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase, v[2]);
			if (!bDirect)
			{
				vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase, v[2]);
			}
		}
		return vNeiBo;
	}	
	static vector<vector<int>> Mat(vector<vector<int>>& neiBoMat)
	{
		vector<vector<int>> neiBo(neiBoMat.size());
		for (int i = 0; i < neiBoMat.size(); i++)
		{
			for (int j = i + 1; j < neiBoMat.size(); j++)
			{
				if (neiBoMat[i][j])
				{
					neiBo[i].emplace_back(j);
					neiBo[j].emplace_back(i);
				}
			}
		}
		return neiBo;
	}
};

class CBFSLeve {
public :
	static vector<int> Leve(const vector<vector<int>>& neiBo, vector<int> start) {
		vector<int> leves(neiBo.size(), -1);
		for (const auto& s : start) {
			leves[s] = 0;
		}
		for (int i = 0; i < start.size(); i++) {
			for (const auto& next : neiBo[start[i]]) {
				if (-1 != leves[next]) { continue; }
				leves[next] = leves[start[i]] + 1;
				start.emplace_back(next);
			}
		}
		return leves;
	}
	template<class NextFun>
	static vector<int> Leve(int N,NextFun nextFun, vector<int> start) {
		vector<int> leves(N, -1);
		for (const auto& s : start) {
			leves[s] = 0;
		}
		for (int i = 0; i < start.size(); i++) {
			auto nexts = nextFun(start[i]);
			for (const auto& next : nexts) {
				if (-1 != leves[next]) { continue; }
				leves[next] = leves[start[i]] + 1;
				start.emplace_back(next);
			}
		}
		return leves;
	}
	static vector<vector<int>> LeveNodes(const vector<int>& leves) {
		const int iMaxLeve = *max_element(leves.begin(), leves.end());
		vector<vector<int>> ret(iMaxLeve + 1);
		for (int i = 0; i < leves.size(); i++) {
			ret[leves[i]].emplace_back(i);
		}
		return ret;
	};
};

class Solution {
		public:
			int minimumDiameterAfterMerge(vector<vector<int>>& edges1, vector<vector<int>>& edges2) {
				const int D1 = D(edges1);
				const int D2 = D(edges2);
				return max(max(D1, D2), (D1 + 1) / 2 + (D2 + 1) / 2 + 1);
			}
			int D(vector<vector<int>>& edges) {
				auto neiBo = CNeiBo::Two(edges.size() + 1, edges, false);
				auto leves = CBFSLeve::Leve(neiBo, { 0 });
				int inx = max_element(leves.begin(), leves.end())- leves.begin();
				auto leves2 = CBFSLeve::Leve(neiBo, { inx });
				return *max_element(leves2.begin(), leves2.end());
			}
		};

单元测试

	vector<vector<int>> edges1,edges2;
		TEST_METHOD(TestMethod11)
		{
			edges1 = { {0,1},{0,2},{0,3} }, edges2 = { {0,1} };
			auto res = Solution().minimumDiameterAfterMerge(edges1, edges2);
			AssertEx(3, res);
		}
		TEST_METHOD(TestMethod12)
		{
			edges1 = { {0,1},{0,2},{0,3},{2,4},{2,5},{3,6},{2,7} }, edges2 = { {0,1},{0,2},{0,3},{2,4},{2,5},{3,6},{2,7} }	;
			auto res = Solution().minimumDiameterAfterMerge(edges1, edges2);
			AssertEx(5, res);
		}
		TEST_METHOD(TestMethod13)
		{
			edges1 = { {1,0},{2,3},{1,4},{2,1},{2,5} }
			, edges2 = { {4,5},{2,6},{3,2},{4,7},{3,4},{0,3},{1,0},{1,8} };
			auto res = Solution().minimumDiameterAfterMerge(edges1, edges2);
			AssertEx(6, res);
		}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

测试环境

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

软件架构师何志丹

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

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

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

打赏作者

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

抵扣说明:

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

余额充值