如下图所示的一棵二叉树的深度、宽度及结点间距离分别为:
其中宽度表示二叉树上同一层最多的结点个数,节点 u , v u, v u,v 之间的距离表示从 u u u 到 v v v 的最短有向路径上向根节点的边数的两倍加上向叶节点的边数。
给定一颗以 1 号结点为根的二叉树,请求出其深度、宽度和两个指定节点 x , y x, y x,y 之间的距离。
第一行是一个整数,表示树的结点个数 n n n。
接下来 n − 1 n - 1 n−1 行,每行两个整数 u , v u, v u,v,表示树上存在一条连接 u , v u, v u,v 的边。
最后一行有两个整数 x , y x, y x,y,表示求 x , y x, y x,y 之间的距离。
输出三行,每行一个整数,依次表示二叉树的深度、宽度和 x , y x, y x,y 之间的距离。
10
1 2
1 3
2 4
2 5
3 6
3 7
5 8
5 9
6 10
8 6
4
4
8
对于全部的测试点,保证 1 ≤ u , v , x , y ≤ n ≤ 100 1 \leq u, v, x, y \leq n \leq 100 1≤u,v,x,y≤n≤100,且给出的是一棵树。保证 u u u 是 v v v 的父结点。
本题标题是“二叉树问题”,但是根据输入数据,我们可以发现,本题是一棵普通的树。
本题涉及三个问题:求深度、求宽度、求两个结点之间的距离。我们注意到,这里两个结点之间的距离表示从 u u u 到 v v v 的最短有向路径上向根节点的边数的两倍加上向叶节点的边数,这里需要特殊的算法来求解。
针对于一二问,我们首先复习两个概念。
递归是指一个函数在其定义中调用自身的过程。递归函数通常有两个部分:基本情况和递归情况。基本情况是指递归函数在不需要递归调用自身时返回的结果。递归情况是指递归函数在需要递归调用自身时调用自身的过程。
递归可以把⼀个⼤型复杂问题层层转化为⼀个与原问题相似,但规模较⼩的⼦问题来求解;直到⼦问题不能再被拆分,可以直接求解,递归就结束了。递归的思考⽅式就是把⼤事化⼩的过程。
递归中的递就是递推的意思,归就是回归的意思,接下来慢慢来体会。
BFS(Breadth First Search)是一种图搜索算法,用于在图中查找从一个顶点到另一个顶点的最短路径。BFS 从起点开始,逐层向外扩展,直到找到目标顶点或遍历完整个图。BFS 通常使用队列来实现,每次从队列中取出一个顶点,将其未访问过的邻居顶点加入队列中。
例如:
#include
#include
using namespace std;
const int N = 1e6 + 10;
int l[N],r[N];
void bfs(int u){
queue<int> q;
q.push(u);
while(q.size()){
int t = q.front();
q.pop();
cout << t << " ";
if(l[t]) q.push(l[t]);
if(r[t]) q.push(r[t]);
}
}
int main(){
int n;
cin >> n;
for(int i = 1;i <= n;i++){
cin >> l[i] >> r[i];
}
bfs(1);
cout << endl;
return 0;
}
我们很容易理解到,整个BFS的过程就是从根节点开始,先把根节点入队,然后每次取出队头元素,输出,同时把队头元素的孩子入队,直到队列为空。
基于以上的复习,我们很快可以写出本题一二部分的代码。
本题求深度的过程,就是求出左右子树的深度,然后取最大值,再加上1即可。同时在求左右子树的深度时,同样求其左右子树的深度,以此类推,直到叶子节点。这也就是运用到了递归的思想。
#include
#include
using namespace std;
const int N = 110;
vector<int> edges[N];
int dfs(int u){
int ret = 0;
/*遍历所有的子树,求深度*/
for(auto v:edges[u]){
ret = max(ret,dfs(v));
}
return ret + 1;
}
int main(){
int n;
cin >> n;
for(int i = 1;i < n;i++){
int u,v;
cin >> u >> v;
edges[u].push_back(v); /*题目中给出的是有向边,因此只需要建立一个有向边*/
}
cout << dfs(1) << endl;
return 0;
}
本题求宽度的过程,就是求出每一层的宽度,然后取最大值即可。借助队列,当我们每处理一层时,将该层所有孩子入队,同时将该层结点出队,这样就可以保证每次处理时,队列都处于同一层。
后续代码的main函数部分与前面类似,仅仅增加调用函数部分,因而都省略了,只给出关键函数部分。
int bfs(){
queue<int> q;
q.push(1);
int ret = 0;
while(q,size()){
int sz = q.size();
ret = max(ret,sz);
while(sz--){
int t = q.front();
q.pop();
for(auto v:edges[t]){
q.push(v);
}
}
}
return ret;
}
这部分解法关键在于如何找到两个结点的最近公共祖先。
根据题目,节点 u , v u, v u,v 之间的距离表示从 u u u 到 v v v 的最短有向路径上向根节点的边数的两倍加上向叶节点的边数,我们可以求向上直到根节点的每一个结点的距离,然后求两个结点的最近公共祖先,再求距离即可。
具体来说,例如从x到y,我们可以先求x向上直到根结点,x到每一个结点的距离并记录在单独的距离数组中,再让y向上查找,直到找到一个结点其距离数组不为0,我们就可以认为该结点就是最近公共祖先。
然后再计算该x到公共祖先的距离的两倍加上y到公共祖先的距离即可。
另外,因为涉及到向上查找,因此我们在输入时,需要建立一个父结点数组,记录每个结点的父结,即在主函数的for循环中,增加fa[v] = u;
。
int di[N];
void dist(int x,int y){
int len = 0;
while(x != 1){
di[fa[x]] = di[x] + 1;
x = fa[x];
}
while(y != 1&&!di[y]){
len++;
y = fa[y];
}
}
#include
#include
#include
using namespace std;
const int N = 110;
vector<int> edges[N];
int fa[N];
int di[N];
int dfs(int u){
int ret = 0;
for(auto v:edges[u]){
ret = max(ret,dfs(v));
}
return ret + 1;
}
int bfs(){
queue<int> q;
q.push(1);
int ret = 0;
while(q.size()){
int sz = q.size();
ret = max(ret,sz);
while(sz--){
int t = q.front();
q.pop();
for(auto v:edges[t]){
q.push(v);
}
}
}
return ret;
}
void dist(int x,int y){
int len = 0;
while(x!= 1){
di[fa[x]] = di[x] + 1;
x = fa[x];
}
while(y!= 1&&!di[y]){
len++;
y = fa[y];
}
cout << (di[y] + len) * 2 - len << endl;
}
int main(){
int n;
cin >> n;
for(int i = 1;i < n;i++){
int u,v;
cin >> u >> v;
edges[u].push_back(v);
fa[v] = u;
}
cout << dfs(1) << endl;
cout << bfs() << endl;
int x,y;
cin >> x >> y;
dist(x,y);
return 0;
}
nt n;
cin >> n;
for(int i = 1;i < n;i++){
int u,v;
cin >> u >> v;
edges[u].push_back(v);
fa[v] = u;
}
cout << dfs(1) << endl;
cout << bfs() << endl;
int x,y;
cin >> x >> y;
dist(x,y);
return 0;
}