题目描述:
给定一棵树,同时给出树中的两个结点,求它们的最低公共祖先。
思路
这类的题目,方法蛮多的,思路也不难理解,基本都是各种遍历的变种,主要是写代码,尤其基于递归的代码。
首先如果是二叉排序树自然不用说了,判断的一句就是该节点的值是否位于输入的这两个节点之间,可以用前序遍历来做。
如果是普通的树或者二叉树,解题思路是一样的,可以考虑前序遍历,得到两个路径,用链表或数组保存起来,然后找出两条路径的最后一个公共节点即可。
也可以后序遍历的方式,遍历到输入的节点时,将该节点及其后面遍历到的节点都保存到一个链表或数组中,然后找出两条路径的第一个公共机节点即可。
下面利用前序遍历和后续遍历寻找节点在树中的路径,都进行了编码实现。
/*
输入:
输入可能包含多个测试样例。
对于每个测试案例,输入的第一行为一个数n(0<n<1000),代表测试样例的个数。
其中每个测试样例包括两行,第一行为一个二叉树的先序遍历序列,其中左右子树若为空则用0代替,其中二叉树的结点个数node_num<10000。
第二行为树中的两个结点的值m1与m2(0<m1,m2<10000)。
输出:
对应每个测试案例,
输出给定的树中两个结点的最低公共祖先结点的值,若两个给定结点无最低公共祖先,则输出“My God”。
样例输入:
2
1 2 4 6 0 0 7 0 0 5 8 0 0 9 0 0 3 0 0
6 8
1 2 4 6 0 0 7 0 0 5 8 0 0 9 0 0 3 0 0
6 12
样例输出:
2
My God
*/
#include<stdio.h>
#include<stdlib.h>
struct TreeNode{
int mValue;
struct TreeNode *lchild;
struct TreeNode *rchild;
};
void createTree(TreeNode **ppNode){
int val;
scanf("%d",&val);
if(val==0){
*ppNode=NULL;
}
else{
//为新结点开辟空间。
(*ppNode)=(TreeNode *)malloc(sizeof(struct TreeNode));
if((*ppNode)==NULL){
exit(EXIT_FAILURE);
}
(*ppNode)->mValue=val;
(*ppNode)->lchild=NULL;
(*ppNode)->rchild=NULL;
createTree(&((*ppNode)->lchild));
createTree(&((*ppNode)->rchild));
}
}
//在树中找到data的路径并保存在path1数组中。
/*
函数参数的说明
@param pRoot:节点的指针
@param data:待查找的结点的value值
@param index:数组的下标索引。即该节点将存储在数组的位置
@param len:用来保存最终数组中存储的节点数目。
*/
bool getNodePath(TreeNode *pRoot,int *path,int data,int index,int *len){
if(pRoot==NULL){
return false;
}
//利用先序遍历来寻找data存在节点的路径
//第一步:当前结点保存在数组中
path[index]=pRoot->mValue;
//第二步:检查当前结点是否是我们需找的节点。
if(data==path[index]){
*len=index+1;
return true;
}
else{
bool isExist=getNodePath(pRoot->lchild,path, data,index+1,len);
if(!isExist){
isExist=getNodePath(pRoot->rchild,path,data,index+1,len);
}
return isExist;
}
}
//返回的是第一个公共节点在数组中的下标。,由于公共节点在两个数组前几个相同的元素都为公共节点,因此每个公共节点在在两个数组中的下标是相同的。
int findPublicNode(int *path1,int *path2,int len1,int len2){
if(path1==NULL||path2==NULL||len1<1||len2<1){
return -1;
}
//以下思路与求链表的第一个公共节点比较类似
//第一步:将找出大树组、小数组
int *lengthPath=path1;
int *shortPath=path2;
int longthLen=len1;
int shortLen=len2;
if(len1<len2){
lengthPath=path2;
shortPath=path1;
longthLen=len2;
shortLen=len1;
}
//从后往前找,当然也可以从前往后找,一样。
for(int i=shortLen-1;i>=0;i--){
if(lengthPath[i]==shortPath[i]){
return i;
}
}
return -1;
}
void destoryTree(TreeNode **pRoot){
if(*pRoot==NULL)
return;
//按照后序遍历来进行销毁
destoryTree(&((*pRoot)->lchild));
destoryTree(&((*pRoot)->rchild));
if((*pRoot)->lchild==NULL&&(*pRoot)->rchild==NULL){
free(*pRoot);
(*pRoot)=NULL;
}
}
int main(void){
//借助两个数组来保存遍历的路径。
int path1[10000];
int path2[10000];
int n;//测试用例的个数
while(scanf("%d",&n)!=EOF&&n>0){
for(int i=0;i<n;i++){
TreeNode *pRoot;
//根据输入来创建一个树。
createTree(&pRoot);
//接收待查找的节点
int first,second;
scanf("%d%d",&first,&second);
//寻找第一个公共结点
int len1=0;
int len2=0;
bool isExist1=getNodePath(pRoot,path1,first,0,&len1);
bool isExist2=getNodePath(pRoot,path2,second,0,&len2);
if(!(isExist1&&isExist2)){
printf("My God\n");
}
else{
//在两个路径数组中找出公共结点的value值。
int publicNodeIndex=findPublicNode(path1,path2,len1,len2);
if(publicNodeIndex!=-1){
printf("%d\n",path1[publicNodeIndex]);
}
else{
printf("My God\n");
}
}
//最后销毁树的内存空间
destoryTree(&pRoot);
}
}
return 0;
}
运行截图如下:
上面是利用先序遍历来寻找节点在树中的路径,下面是利用后序遍历来做,这里*len的是最大的下标。
//
bool getNodePath(TreeNode *pRoot,int *path,int data,int *len){
if(pRoot==NULL){
return false;
}
bool isExist=getNodePath(pRoot->lchild,path, data,len);
if(!isExist){
isExist=getNodePath(pRoot->rchild,path, data,len);
}
if(isExist){//如果其的子节点存在路径中,则此节点也肯定存在路径中。
path[++(*len)]=pRoot->mValue;
}
if(pRoot->mValue==data){
*len=0;
path[*len]=pRoot->mValue;
return true;
}
return isExist;
}