题目一:二叉树的最近公共祖先,无父指针。
题目链接:. - 力扣(LeetCode)
方式一:使用递归解决。
既在左子树找p和q,又在右子树找p和q。 只要找到p或q的其中一个就可以返回,如果left和right都不为空,说明左右子树各找到p和q当中的一个,那么p和q在root的两侧,此时root必为left和right的最近公共祖先,向上返回即可。 如果left不为空,说明p,q在左子树。 如果right不为空,说明p,q在右子树。 left和right都为空,说明找不到,返回空。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == nullptr || root == p || root == q) {
//只要当前根节点是p和q中的任意一个,就返回(因为不能比这个更深了,再深p和q中的一个就没了)
return root;
}
//根节点不是p和q中的任意一个,那么就继续分别往左子树和右子树找p和q
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
//p和q都没找到,那就没有
if(left == nullptr && right == nullptr) {
return nullptr;
}
//左子树没有p也没有q,就返回右子树的结果
if (left == nullptr) {
return right;
}
//右子树没有p也没有q就返回左子树的结果
if (right == nullptr) {
return left;
}
//左右子树都找到p和q了,那就说明p和q分别在左右两个子树上,所以此时的最近公共祖先就是root
return root;
}
};
方式二:使用哈希表存储每个节点的父节点(若题目给的二叉树定义中包含父节点则非常好用)
我们可以用哈希表存储所有节点的父节点,然后我们就可以利用节点的父节点信息从 p 结点开始不断往上跳,并记录已经访问过的节点,再从 q 节点开始不断往上跳,如果碰到已经访问过的节点,那么这个节点就是我们要找的最近公共祖先。
算法:
从根节点开始遍历整棵二叉树,用哈希表记录每个节点的父节点指针。
从 p 节点开始不断往它的祖先移动,并用数据结构记录已经访问过的祖先节点。
同样,我们再从 q 节点开始不断往它的祖先移动,如果有祖先已经被访问过,即意味着这是 p 和 q 的深度最深的公共祖先,即 LCA 节点。
class Solution {
public:
unordered_map<int, TreeNode*> fa;
unordered_map<int, bool> vis;
void dfs(TreeNode* root){
if (root->left != nullptr) {
fa[root->left->val] = root;
dfs(root->left);
}
if (root->right != nullptr) {
fa[root->right->val] = root;
dfs(root->right);
}
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
fa[root->val] = nullptr;
dfs(root);
while (p != nullptr) {
vis[p->val] = true;
p = fa[p->val];
}
while (q != nullptr) {
if (vis[q->val]) return q;
q = fa[q->val];
}
return nullptr;
}
};
题目二:二叉搜索树
很简单,二叉搜索树,即左子树的节点值都比根节点小,右子树的节点值都比根节点大,判断待求两个节点的值,如果都比根节点小,则最近公共祖先必在根节点的左子树上,来到左子树上去判断。如果都比跟节点大,则来到右子树上判断。如果恰好处在两个节点值之间,则说明一个在左子树,一个在右子树,该节点则为最近公共祖先。
题目三:多叉树的最近公共祖先。
题目链接:【模板】最近公共祖先(LCA) - 洛谷
见题解
b站:【D09 倍增算法 P3379【模板】最近公共祖先(LCA)】 https://www.bilibili.com/video/BV1vg41197Xh/?share_source=copy_web&vd_source=c83e007dbefdc4a5ea41a926c1309a49
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<stdio.h>
#include<vector>
#define maxn 500500
using namespace std;
///隶属邻接表
struct Edge{ //邻接表的结构体
int from,to;
}edges[2*maxn]; //边要乘2,因为是无向图 ;
int first[maxn],next[2*maxn]; //同理;
int read(){ //读入优化,可以照着这个模板来写,这个还算写的比较好看。
int re=0;
char ch=getchar();
while (ch<'0' || ch>'9') ch=getchar();
while (ch>='0' && ch<='9'){
re=re*10+ch-'0';
ch=getchar();
}
return re;
}
///
///全局变量
int n,m;
int root;
int height[maxn];
float log2n;
///
///隶属LCA的全局变量
int f[maxn][20];//
int have[maxn]; //have,有没有找过,这都是套路 。
void dfs(int u,int h){ //u代表点的标号,h代表高度。
int v;
height[u]=h;
for(int i=1;i<=log2n;i++) {
if(h<=(1<<i)) break; //由于i是从小到大计算的,故(1<<i)>=h 时可直接退出。请务必想清楚是<= 还是=。
f[u][i] = f[ f[u][i-1] ][i-1]; //动规计算。同样也是一切倍增算法的核心。
}
int k=first[u];
while(k!=-1){
v=edges[k].to;
if(!have[v]) {
have[v]=1;
f[v][0]=u; //将要找的下一个点的父节点标为当前处理的节点u。
dfs(v,h+1);
}
k=next[k];
}
}
int require_LCA(int a,int b){
int da=height[a],db=height[b];
//第一步,将a,b两点移到同样的高度,只动高度大的那个点而不动高度小的那个点。
if(da!=db) {
if(da<db){ //保证a的高度是大于b的高度的。
swap(a,b);
swap(da,db);
}
int d=da-db;
for(int i=0;i<=log2n;i++)
if( (1<<i) & d) a=f[a][i]; //这里的位运算可以减少代码量
//考虑到d是一个定值,而(1<<i)在二进制中只有第(i+1)位是1;
//那么d与(1<<i)如果某一位为1,那么表示可以向上移动,
//如果此时不移动,那么i增大了后就无法使height[a]==height[b]了
}
//第二步,找到某个位置i,在这个位置时,f[a][i]!=f[b][i],但再向上移动一步,a,b相同了
//从log2n开始从大到小枚举i,如果超过了a,b的高度,则令i继续减小
//如果没有超过a,b的高度,那么就判断移动了后会不会让a==b,
//是,则i继续减小,否则,令此时的a=f[a][i],b=f[b][i];
if(a==b) return b;
int i=0;
for(i=log2n;i>=0;i--) {
if(height[ f[a][i] ]<0) continue;
if( f[a][i]==f[b][i] ) continue;
else a=f[a][i],b=f[b][i]; //顺便一提,在第二步任何地方没有break;
//我就是因为在这里写了一个break,然后找了我两个小时啊。
}
return f[a][0];
}
/
///据说从主函数开始阅读是个好习惯。
int main(){
// freopen("in2.txt","r",stdin);
n=read();m=read();root=read();
memset(first,-1,sizeof(first));
memset(next,-1,sizeof(next));
int s,t;
int dsd=2*(n-1);
for(int i=1;i<=dsd;i+=2) {
s=read();t=read(); //读入优化。
edges[i].from=s;
edges[i].to=t;
edges[i+1].from=t;
edges[i+1].to=s;
next[i]=first[s];
first[s]=i;
next[i+1]=first[t];
first[t]=i+1;
}
// 以上是邻接表,在此不再赘述。
log2n=log(n)/log(2)+1; //C++计算log是自然对数,我们要用的以2为底的对数,故要除以log(2);
//对无理数加上1或是0.5是个好习惯,可以减小误差;
memset(have,0,sizeof(have));
memset(height,0,sizeof(height));
memset(f,-1,sizeof(f));
have[root]=1; //fa[][]和height[]要在dfs理进行计算,不然根本找不到某个非根节点的父亲是谁;
dfs(root,1);
for(int i=1;i<=n;i++){
for(int j=0;j<=log2n;j++) {
if(height[i] <=(1<<j) ) break;
}
}
for(int i=0;i<m;i++) { //应对要求进行求解。
s=read();t=read();
int y=require_LCA(s,t);
printf("%d\n",y);
}
return 0;
}