寻找最近公共祖先,示例如下:

1

/           \

2           3

/    \        /    \

4    5      6    7

/    \             \

8    9           10

LCA(8,9)=4; LCA(5,8)=2; LCA(10,4)=1

思路一:

递归法

1.如果有一个结点是树的根结点,则必定不存在公共祖先;遍历二叉树每个结点,检查其左右子树是否包含指定的两个结点;

2.遍历二叉树每个结点,检查其左右子树是否包含指定的两个结点;3.如果步骤一满足条件,则重新检查以该结点的孩子为根结点的子树是否同时包含指定的两个结点;如果包含则重复步骤2,否则该结点便是指定两个结点的最近公共祖先。

假设LCA(root, id1, id2)用于判断以root为根的树是否含有id1和id2的后代结点。

LCA(root, id1, id2, &ans)

ans=root

while(1)

if(ans->leftchild!=NULL)

if(find(ans->leftchild, id1)&&find(ans->leftchild, id2))   //均找到id1和id2

ans=ans->leftchild

continue

if(ans->rightchild!=NULL)

if(find(ans->rightchild, id1)&&find(ans->rightchild, id2))

ans=ans->rightchild

continue

break

显然这样做将重复遍历多个结点,因此,使用动态规划,可以降低时间复杂度。

思路二:

动态规划法--保存以前遍历过的已知结果,组成上层子问题的结果,从而提升效率,但会增加空间复杂度。

1.对树进行层遍历(详见之前的帖子),并将结点存入数组中;

2.从数组尾部开始,检查是否存在目标结点,假设m[i,1]和m[i,2]表示第i个结点拥有id1或id2的后代,有k<i且结点k是i的双亲, m[k,1]=m[i,1]+(n(i)==id1), m[k,2]=m[i,2]+(n(i)==id2)

3.当m[k,1]==1且m[k,2]==1时找到公共祖先,否则数组遍历完成,表示找不到公共祖先。

为方便起见,这里修改一下树结点结构

struct tree_node;
struct tree_node{
struct tree_node *lc;
struct tree_node *rc;
int id;
int index;
};
typedef struct tree_node treenode;

具体代码如下

#include <stdio.h>
#include <stdlib.h>
#include <string.h> struct tree_node;
struct tree_node{
struct tree_node *lc;
struct tree_node *rc;
int id;
int index;
};
typedef struct tree_node treenode;
//*先序为DLR(D:根节点,L:左子树,R:右子树)
// a
// / \
// b c
// / \ / \
// d * * e
//*/
//先序序列为abdce,输入为abd***c*e**(*表示空格,代表空树),输入按满二叉树输入
//每一个节点都是一个子树的根节点
void pre_create_tree(treenode **T){ //递归法
int datatemp; fflush(stdin);
scanf("%d", &datatemp); if(datatemp==-1){
*T=NULL;
}
else{
if((*T=(treenode*)malloc(sizeof(treenode)))==NULL){
exit(0);
}
else{
(*T)->id=datatemp;
(*T)->lc = (*T)->rc = NULL;
pre_create_tree(&(*T)->lc);
pre_create_tree(&(*T)->rc);
}
}
} void pre_visit_tree(treenode *T, int *n){ //递归法
if(T!=NULL){
(*n)++;
printf("%d ", T->id);
pre_visit_tree(T->lc, n);
pre_visit_tree(T->rc, n);
}
else{
return;
}
} typedef struct info{
treenode *node;
int have_id1;
int have_id2;
}info; void level_visit_tree(treenode *T, int n, int id1, int id2){
info *myinfo, *tt, *temp;
treenode *ptemp, *nodetemp;// **temp;
int k=1, levelcnt=0, cnt, levelcnttemp, prelevelcnt=1;
int index=0;
//levelcnt:当前层元素个数,levelcnttemp:下一层元素个数 //初始化循环记录
myinfo = (info*)malloc(sizeof(info)*n); //记录用数组
myinfo->node = T;
if(T->lc!=NULL)
levelcnt++;
if(T->rc!=NULL)
levelcnt++;
temp = myinfo;
tt = temp+1;
printf("%d ", temp->node->id);
T->index = index++;
while(levelcnt){
//本层没有元素,可以结束循环了
for(cnt=0, levelcnttemp=0; cnt<levelcnt; ){ //tt:从数组myinfo中指向本层元素,遍历本层元素,有cnt计数,不用怕访问到其它层的元素
if(temp->node->lc!=NULL){ //打印本层元素的孩子,有则输出孩子值,没有就输出#
printf("%d ", temp->node->lc->id);
(tt+cnt)->node = temp->node->lc;
temp->node->lc->index = index++;
if((tt+cnt)->node->lc!=NULL)
levelcnttemp++;
if((tt+cnt)->node->rc!=NULL)
levelcnttemp++;
cnt++;
}
else
printf("# ");
if(temp->node->rc!=NULL){
printf("%d ", temp->node->rc->id);
(tt+cnt)->node = temp->node->rc;
temp->node->rc->index = index++;
if((tt+cnt)->node->lc!=NULL)
levelcnttemp++;
if((tt+cnt)->node->rc!=NULL)
levelcnttemp++;
cnt++;
}
else
printf("# ");
temp++;
}
//k=k+levelcnt; //k木有用
tt = tt+cnt;
levelcnt=levelcnttemp;
}
printf("\n"); for(k=n-1; k>=0; k--){
nodetemp = (myinfo+k)->node;
(myinfo+k)->have_id1 = 0;
(myinfo+k)->have_id2 = 0;
if(nodetemp->lc==NULL&&nodetemp->rc==NULL){
//这个结点是叶子结点,肯定不是他们两的祖先
(myinfo+k)->have_id1 = 0;
(myinfo+k)->have_id2 = 0;
}
else{
if(nodetemp->lc!=NULL){
if((myinfo+nodetemp->lc->index)->have_id1||nodetemp->lc->id==id1)
(myinfo+k)->have_id1 = 1;
if((myinfo+nodetemp->lc->index)->have_id2||nodetemp->lc->id==id2){
(myinfo+k)->have_id2 = 1;
}
}
if(nodetemp->rc!=NULL){
if((myinfo+nodetemp->rc->index)->have_id1||nodetemp->rc->id==id1)
(myinfo+k)->have_id1 = 1;
if((myinfo+nodetemp->rc->index)->have_id2||nodetemp->rc->id==id2){
(myinfo+k)->have_id2 = 1;
}
}
}
if((myinfo+k)->have_id1&&(myinfo+k)->have_id2){
printf("They have a common an ancestor:%d", (myinfo+k)->node->id);
break;
}
}
if(k<0)
printf("They have no common ancestors");
} int main(){
treenode *mytree;
int n=0;
int id1, id2; printf("输入需要查找公共祖先的id号:"); scanf("%d %d", &id1, &id2);
pre_create_tree(&mytree);
pre_visit_tree(mytree, &n); printf("\n");
level_visit_tree(mytree, n, id1, id2); printf("\n"); system("pause");
return 0;
}

测试用例:

1 2 3 -1 -1 4 10 -1 -1 11 -1 -1 5 6 -1 9 -1 -1 7 8 13 -1 -1 14 -1 -1 12 -1 -1

【Leetcode】查找二叉树中任意结点的最近公共祖先(LCA问题)的更多相关文章

  1. 二叉树中两节点的最近公共父节点(360的c++一面问题)

    面试官的问题:写一个函数  TreeNode* Find(TreeNode* root, TreeNode* p, TreeNode* q) ,返回二叉树中p和q的最近公共父节点. 本人反应:当时有点 ...

  2. 查找最近公共祖先(LCA)

    一.问题 求有根树的任意两个节点的最近公共祖先(一般来说都是指二叉树).最近公共祖先简称LCA(Lowest Common Ancestor).例如,如下图一棵普通的二叉树. 结点3和结点4的最近公共 ...

  3. 【Java】 剑指offer(68) 树中两个结点的最低公共祖先

      本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 输入两个树结点,求它们的最低公共祖先. 思路 该题首先要和面试 ...

  4. 剑指Offer - 九度1509 - 树中两个结点的最低公共祖先

    剑指Offer - 九度1509 - 树中两个结点的最低公共祖先2014-02-07 01:04 题目描述: 给定一棵树,同时给出树中的两个结点,求它们的最低公共祖先. 输入: 输入可能包含多个测试样 ...

  5. 树中两个结点的最低公共祖先--java

    题目:对于任意一个树,不仅仅限于二叉树,求树中两个结点的最低公共祖先结点. 解析:对于任意一棵树,显然并不局限于二叉树,也就是说树的非叶子结点可能存在多个子节点.所以,我们可以定义两个链表结构,存储这 ...

  6. 【剑指Offer面试编程题】题目1509:树中两个结点的最低公共祖先--九度OJ

    题目描述: 给定一棵树,同时给出树中的两个结点,求它们的最低公共祖先. 输入: 输入可能包含多个测试样例. 对于每个测试案例,输入的第一行为一个数n(0<n<1000),代表测试样例的个数 ...

  7. 【剑指Offer学习】【面试题50:树中两个结点的最低公共祖先】

    题目:求树中两个结点的最低公共祖先,此树不是二叉树,而且没有指向父节点的指针. 树的结点定义 private static class TreeNode { int val; List<Tree ...

  8. 【IT笔试面试题整理】寻找二叉树两节点的最近的公共祖先

    [试题描述] 求二叉树中任意两个节点的最近公共祖先也称为LCA问题(Lowest Common Ancestor). 二叉查找树 如果该二叉树是二叉查找树,那么求解LCA十分简单. 基本思想为:从树根 ...

  9. (剑指Offer)面试题50:树中两个结点的最低公共祖先

    题目: 求树中两个结点的最低公共祖先 思路: 考虑一下几种情况: 1.该树为二叉搜索树 二叉搜索树是排序树,位于左子树点的结点都比父结点小,而位于右子树的结点都比父结点大,只需要从树的根结点开始和两个 ...

随机推荐

  1. Android framework浅析[转]

    Android系统从底向上一共分了4层,每一层都把底层实现封装,并暴露调用接口给上一层. 1. Linux内核(Linux Kernel) 1)Android运行在linux kernel 2.6之上 ...

  2. 【Python脚本】Python创建删除文件-----------我的第一个Python脚本

    Python相对C++和Java来说,是解释性语言,非常适合来编写脚本. 很久之前就开始学习Python的语法了,今天写了第一个Python的脚本,来简化我的一些日常工作. 我平时学习的时候喜欢新建一 ...

  3. 解决struts2配置文件没有提示的问题

    1.在WEB-INF下新建一个dtd文件夹 2.将

  4. Redis 命令参考

    Redis 命令参考 http://redis.readthedocs.org/en/latest/index.html

  5. Serializable在C#中的作用——.net中的对象序列化

    序列化是指将对象实例的状态存储到存储媒体的过程,在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流,在随后对对象进行反序列化时,将创建出与 ...

  6. ASP.NET通用权限组件实现一

    沙发(SF)通用权限验证组件 开篇 上一篇提到了通用权限的设计思路,根据设计思路一步一步的来实现一个相对通用的权限验证组件.在VS2010下用C#语言基于.net framework2.0框架实现具体 ...

  7. android第一天错误

    1.自己写的activity并在AndroidManifest.xml中注册 在模拟机上运行时出现 No Launcher activity found! The launch will only s ...

  8. Log4j学习手记

    注:今天难得清闲,学了下log4j,参考博文 http://www.cnblogs.com/pony/archive/2008/08/25/1275484.html ,文字部分基本上来自该博文,我只是 ...

  9. hdu3123GCC

    Problem Description The GNU Compiler Collection (usually shortened to GCC) is a compiler system prod ...

  10. 从汇编看c++中指向成员变量的指针(一)

    在c++中,指向类成员变量的指针存储的并不是该成员变量所在内存的地址,而仅仅是该成员变量在该类对象中相对于对象首地址的偏移量.因此,它必须绑定到某一个对象或者对象指针上面,这里的对象和对象指针,就相当 ...