本文涉及知识点
相关定义和有关名称
有根树的深度:根到叶子节点的距离。
有根数的搞定:叶子到根节点的距离。
两者本质是一样,习惯不同而已。
树的直径——就是树中距离最大的两点的距离。 以任意节点为根的最大深度。
暴力解法
令 直径所在路径为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
n2→pub→n3 pub是 以0为根的有根树的最近公共祖先。
分情况讨论:
一, n1是pub的子孙。
p
u
b
→
n
1
pub \rightarrow n1
pub→n1 一定不小于
p
u
b
→
n
3
pub \rightarrow n3
pub→n3 否则 n3的深度更大。
p
u
b
→
n
1
pub \rightarrow n1
pub→n1 一定不大于
p
u
b
→
n
3
pub \rightarrow n3
pub→n3 否则n1
→
\rightarrow
→n2 才是直径。
二,n1不是pub的子孙。
令puc和n1的最近公共祖先是pub2。
p
u
c
2
→
n
1
puc2 \rightarrow n1
puc2→n1 一定不小于
p
u
b
2
→
n
3
pub2 \rightarrow n3
pub2→n3 否则 n3的深度更大。
p
u
c
2
→
n
1
puc2 \rightarrow n1
puc2→n1 一定不大于
p
u
b
2
→
n
3
pub2 \rightarrow n3
pub2→n3 否则n1
→
\rightarrow
→n2 才是直径。
p
u
c
2
→
n
1
puc2 \rightarrow n1
puc2→n1 ==
p
u
b
2
→
n
3
pub2 \rightarrow n3
pub2→n3
⟺
\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++**实现。