洛谷 P3884 [JLOI2009] 二叉树问题

P3884 [JLOI2009] 二叉树问题

题目描述

如下图所示的一棵二叉树的深度、宽度及结点间距离分别为:

  • 深度: 4 4 4
  • 宽度: 4 4 4
  • 结点 8 和 6 之间的距离: 8 8 8
  • 结点 7 和 6 之间的距离: 3 3 3

其中宽度表示二叉树上同一层最多的结点个数,节点 u , v u, v u,v 之间的距离表示从 u u u v v v 的最短有向路径上向根节点的边数的两倍加上向叶节点的边数。

洛谷 P3884 [JLOI2009] 二叉树问题_第1张图片

给定一颗以 1 号结点为根的二叉树,请求出其深度、宽度和两个指定节点 x , y x, y x,y 之间的距离。

输入格式

第一行是一个整数,表示树的结点个数 n n n
接下来 n − 1 n - 1 n1 行,每行两个整数 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 之间的距离。

输入输出样例 #1

输入 #1

10                                
1 2                            
1 3                            
2 4
2 5
3 6
3 7
5 8
5 9
6 10
8 6

输出 #1

4
4
8

说明/提示

对于全部的测试点,保证 1 ≤ u , v , x , y ≤ n ≤ 100 1 \leq u, v, x, y \leq n \leq 100 1u,v,x,yn100,且给出的是一棵树。保证 u u u v v v 的父结点。

题解

本题标题是“二叉树问题”,但是根据输入数据,我们可以发现,本题是一棵普通的树。

本题涉及三个问题:求深度求宽度求两个结点之间的距离。我们注意到,这里两个结点之间的距离表示从 u u u v v v 的最短有向路径上向根节点的边数的两倍加上向叶节点的边数,这里需要特殊的算法来求解。

针对于一二问,我们首先复习两个概念。

递归

递归是指一个函数在其定义中调用自身的过程。递归函数通常有两个部分:基本情况和递归情况。基本情况是指递归函数在不需要递归调用自身时返回的结果。递归情况是指递归函数在需要递归调用自身时调用自身的过程。

递归可以把⼀个⼤型复杂问题层层转化为⼀个与原问题相似,但规模较⼩的⼦问题来求解;直到⼦问题不能再被拆分,可以直接求解,递归就结束了。递归的思考⽅式就是把⼤事化⼩的过程。

递归中的递就是递推的意思,归就是回归的意思,接下来慢慢来体会。

BFS

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;
}

你可能感兴趣的:(数据结构及STL,数据结构,算法,c++,学习,c语言)