求树中两个结点的最近公共祖先(LCA)算法详解
创作时间:
作者:
@小白创作中心
求树中两个结点的最近公共祖先(LCA)算法详解
引用
1
来源
1.
https://vks-feng.github.io/2024/05/09/POJ1330%E2%80%94%E2%80%94%E6%B1%82%E6%A0%91%E4%B8%AD%E4%B8%A4%E4%B8%AA%E7%BB%93%E7%82%B9%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88%EF%BC%88LCA%EF%BC%89/
问题描述
如图所示是一棵有根树,图中每个结点用1~16的整数标识,结点8是树根。如果结点_x_位于根结点到结点_y_之间的路径中,则结点_x_是结点_y_的祖先。如果结点_x_是结点_y_和结点_z_的祖先,则结点_x_称为两个不同结点_y_和_z_的公共祖先。如果_x_是_y_和_z_的共同祖先并且在所有共同祖先中最接近_y_和_z_,则结点_x_被称为结点_y_和_z_的最近公共祖先,如果_y_是_z_的祖先,那么_y_和_z_的最近共同祖先是_y_。例如结点16和7的最近公共祖先是结点4,结点2和3的最近公共祖先是结点10,结点4和12的最近公共祖先是结点4。编写一个程序,找到树中两个不同结点的最近公共祖先。
输入输出格式
输入格式:
每个测试用例的第一行为树中结点数_n_(2≤n_≤10,000),所有结点用整数1~_n_标识。
接下来的_n-1行中的每一行包含一对表示边的整数,第一个整数是第二个整数的父结点。
请注意,具有_n_个结点的树具有恰好_n_-1个边。
每个测试用例的最后一行为两个不同整数,需要计算它们的最近公共祖先。
输出格式:
为每个测试用例输出一行,该行应包含最近公共祖先结点的编号。
样例
- 输入样例:
16
1 14
8 5
10 16
5 9
4 6
8 4
4 10
1 13
6 15
10 11
6 7
10 2
16 3
8 1
16 12
16 7
- 输出样例:
4
样例说明:
测试数据的文件名为in.txt
评分标准:
该题目有10个测试用例,每通过一个测试用例,得10分。
解题思路
方法一:并查集与双重循环
时间复杂度$O(n^2)$的笨方法
- 首先按照初始化的条件初始化并查集
- 对找所需寻找关系的两个结点,一个结点作为外层循环,一个结点放入内层循环,退出条件是两个结点的祖先相同,由于我们从所找结点向根节点的方向寻找,所以退出时找到的祖先结点就是最近祖先
方法二:保存路径寻找最近公共祖先
- 由于树中任意两结点之间的路径是唯一的,所以直接把所找结点到根节点的路径保存下来,在路径中找到最近公共祖先即可
- 如何寻找找到最近公共祖先,以下是两种$O(n)$的方法
- 如果从当前节点先根节点移动的路径,则公共部分一定处在最后,所以如果两条路径长度相同,则设计两个指针指向两个,同步移动,每次移动后比较;如果长度不同,则需要长的那条指针先移动至两条路径长度相同再去比较(如(1))
- 逆向比较,则最后一个不同的就是最近公共祖先(如(2))当然如果使用递归寻找路径,路径本身就是(2)中的效果,本文采用(2)
代码实现
#include<iostream>
#include<vector>
#include<fstream>
using namespace std;
vector<int> parent;
vector<int> rnk;
void Init(int n) {
for (int i = 0; i <= n; i++) {
parent.push_back(i);
rnk.push_back(0);
}
}
int FindAncestor1(int a, int b) {
int temp_for_a = a;
bool flag = false;
while (temp_for_a != parent[temp_for_a]) {
int temp_for_b = b;
while (parent[temp_for_b] != temp_for_b) {
if (parent[temp_for_b] == temp_for_a) {
flag = true;
break;
}
temp_for_b = parent[temp_for_b];
}
if (flag) {
break;
}
temp_for_a = parent[temp_for_a];
}
return temp_for_a;
}
void FindPath(int x, vector<int>& path) {
if (parent[x] != x) {
FindPath(parent[x], path);
path.push_back(x);
} else {
path.push_back(x);
}
}
int FindAncestor2(vector<int> path1, vector<int> path2) {
int i = 0, j = 0;
int path1_size = path1.size();
int path2_size = path2.size();
while (i < path1_size && j < path2_size) {
if (path1[i] == path2[j]) {
i++;
j++;
} else {
break;
}
}
return path1[i - 1];
}
int main() {
int n;
ifstream file("in.txt");
file >> n;
Init(n);
int num = n - 1;
int a, b;
// 此循环用于构建结点间的关系
while (num--) {
file >> a >> b;
parent[b] = a;
}
// 找到a、b的最近公共祖先
file >> a >> b;
vector<int> path1, path2;
FindPath(a, path1);
FindPath(b, path2);
int ans = FindAncestor1(a, b);
cout << ans << endl;
int ans = FindAncestor2(path1, path2);
cout << ans << endl;
return 0;
}
热门推荐
绿色出行宣传月,市妇联向您发出倡议!
2024年新交规正式实施!这“11项规定”车主们务必注意
律师职业发展攻略:五大要素助力成长
专家:调理痰湿体质,从补脾开始
园区道路检验批划分最新实践:以徐州某市政道路工程为例
园区道路检验批划分,你做对了吗?
从八贤王到阿其那:胤禩的悲惨人生
股票还是ETF?投资者必看的六大关键对比
ETF vs 股票:投资新手必知的五大区别
上海田子坊商业化提速,艺术氛围何去何从?
“画绣”顾绣:上海非遗在新时代绽放新光彩
袁庭栋揭秘成都人性格:敢为天下先的创新精神与安逸巴适的生活哲学
袁庭栋解码成都人性格:血性与安逸的和谐统一
宽窄巷子打卡,成都火锅吃到爽!
宋庄美术馆展出91件当代艺术请柬,重现80年代艺术盛况
武汉百年老街昙华林:建筑、人文、夜景摄影全攻略
从石库门到渔村:上海厦门的文艺地标巡礼
海乐妙驱虫药假货风波,教你辨别真伪
兽医推荐:大宠爱猫用驱虫药成分解析
猫咪驱虫新选择:普安特阿维菌素透皮溶液使用指南
猫咪驱虫药大宠爱 vs 爱沃克,谁才是王者?
如何保护个人信息安全?这些保护措施有哪些实际意义?
如何查询自己名下是否有贷款?这四种方式简单明了
一文读懂ETF与ETF联接:从交易到费用的全面对比
牛森:心衰康复治疗——看中医药如何 “对症下药”
聊一聊心衰
厦门质检站严查室外给排水检验批次
猫咪驱虫药选购指南:如何选择最适合的驱虫产品?
从《野兽之战》到《兽械争霸》:变形金刚的进化之旅
中科院发现骨关节炎与大脑存在双向调节机制