概念
先来一张绝妙的百科配图
最近公共祖先(Lowest/Least Common Ancestor, LCA)
在图论和计算机科学领域,树或有向无环图(DAG)T中两个结点p、q的最近公共祖先是指这样一个具有最低高度(最大深度)结点:p和q为均该结点的子孙,这里我们定义每个结点都是自己的子孙(这样一来,如果从q可以直接向下追溯到p,那么q就是最近公共祖先)——维基百科
Hmmm 浓浓的翻译腔
这里我们只讨论一般二叉树的最近公共祖先问题
算法思想
这里我们只讨论一次遍历的brute-force算法,亦即对于每次询问,遍历所有结点,所以时间复杂度为O(n)
(至于别的高效算法╮(╯▽╰)╭挖个坑学会了再填上)
brute-force算法思路简单,代码写起来也不长,但时间复杂度决定了其不适用于多次询问的场景
递归算法思想:
当遍历到结点ROOT时
- 若ROOT为空节点,返回NULL
- 若ROOT为p或q,则ROOT即LCA,返回ROOT
- 若ROOT既不是p也不是q,递归左右子树
- 若p、q分别位于ROOT的左右子树,ROOT为LCA
- 若p、q均位于ROOT的左(右)子树,LCA在ROOT的左(右)子树中
非递归算法思想:
利用非递归的后序遍历的特点
- 查找p、q,当找到其中一个结点时,栈中包含该结点的所有祖先,将其栈中结点制到一个辅助栈中
- 继续遍历直至找到另一个结点,此时我们就得到了p、q的祖先序列,分别存储在两个栈中
- 从栈顶开始逐结点比较,第一个相同的结点即为LCA
代码
递归算法
BTNode* GetLCA(BTNode* ROOT, BTNode* p, BTNode *q)
{
if (ROOT == NULL)
return NULL;
if (ROOT == p || ROOT == q)
return ROOT;
BTNode* left = GetLCA(ROOT->LLINK, p, q);
BTNode* right = GetLCA(ROOT->RLINK, p, q);
// p 和 q 不存在祖先关系
if (left != NULL && right != NULL)
return ROOT;
// p 和 q 其中一个是另一个的祖先
else if (left != NULL)
return left;
else if (right != NULL)
return right;
else
return NULL;
}
甚至我们可以写得更简洁一些
BTNode* GetLCA(BTNode* ROOT, BTNode* p, BTNode *q)
{
if (ROOT == NULL || ROOT == p || ROOT == q)
return ROOT;
BTNode* left = GetLCA(ROOT->LLINK, p, q);
BTNode* right = GetLCA(ROOT->RLINK, p, q);
if (left != NULL && right != NULL)
return ROOT;
return left != NULL?left:right;
}
非递归算法
这种算法是来自王道19数据结构P136的修正
typedef struct{
BiTree t;
int tag;//0表示左子女已被访问,1表示右子女已被访问
}stack;
BTNode* GetLCA2(BTNode* ROOT, BTNode* p, BTNode *q)
{
stack s[20], s1[20];//s[]存放先找到的元素及其祖先,s1存[]放后找到的元素及其祖先
int top = 0, top1 = 0, i, j, gotp = 0, gotq = 0; //已找到p、q时,gotp、gotq为1
BTNode *bt = ROOT;
while (bt != NULL || top > 0){
//while (bt != NULL&&bt != p&&bt != q){ //书中多了这层循环导致算法出错
while (bt != NULL){
//若结点非空
s[++top].t = bt; //结点入栈
s[top].tag = 0;
bt = bt->LLINK; //沿左分支向下
}
//}
while (top != 0 && s[top].tag == 1){
//若结点为空 且 栈不空 且 栈顶结点已访问过左右子女
//书中“不失一般”地假设先找到p,再找到q(我???),所以这里加入了对找到p、q先后次序的判断,
//以及对两种情况的分别处理
if (s[top].t == p&&top1==0){
//若先找到p
for (i = 1; i <= top; i++) //将s的元素转入辅助栈s1保存
s1[i] = s[i];
top1 = top;
gotp = 1;
} //if
if (s[top].t == q&&top1 == 0){
//若先找到q
for (i = 1; i <= top; i++) //将s的元素转入辅助栈s1保存
s1[i] = s[i];
top1 = top;
gotq = 1;
} //if
if (s[top].t == p&&gotq){
//若后找到p
for (i = top; i > 0; i--){
//将s的元素与s1的元素匹配
for (j = top1; j > 0; j--){
if (s1[j].t == s[i].t)
return s[i].t; //返回LCA
}
}
} //if
if (s[top].t == q&&gotp){
//若后找到q
for (i = top; i > 0; i--){
//将s的元素与s1的元素匹配
for (j = top1; j > 0; j--){
if (s1[j].t == s[i].t)
return s[i].t; //返回LCA
}
}
} //if
top--;
} //while
if (top != 0){
//若结点为空 且 栈空或栈顶结点未访问右子女
s[top].tag = 1;
bt = s[top].t->RLINK; //沿右分支向下
}
} //while
return NULL; //无CA,即公共祖先
}
但是我仍然觉得上面这种后序遍历之中的判断条件过于诡异,不是说不对,就是初次看时难以理解,不合我的思路,于是我改写成下面的
BTNode* GetLCA3(BTNode* ROOT, BTNode* p, BTNode *q)
{
BTNode *bt = ROOT, *r = NULL, *s[20], *s1[20];//s[]存放先找到的元素及其祖先
//s1存放后找到的元素及其祖先
int top = 0, top1 = 0, i, j, gotp = 0, gotq = 0; //已找到p、q时,gotp、gotq为1
while (bt != NULL || top > 0){
if (bt != NULL){
//若结点非空
s[++top] = bt; //结点入栈
bt = bt->LLINK; //沿左分支向下
}
else{
//若结点为空 且 栈不空
bt = s[top];
if (bt->RLINK&&bt->RLINK != r) //若结点为空 且 未访问右子女
bt = bt->RLINK; //沿右分支向下
else {
if (s[top] == p&&top1 == 0){
//若先找到p
for (i = 0; i <= top; i++) //将s的元素转入辅助栈s1保存
s1[i] = s[i];
top1 = top;
gotp = 1;
} //if
if (s[top] == q&&top1 == 0){
//若先找到q
for (i = 0; i <= top; i++) //将s的元素转入辅助栈s1保存
s1[i] = s[i];
top1 = top;
gotq = 1;
} //if
if (s[top] == p&&gotq){
//若后找到p
for (i = top; i > 0; i--){
//将s的元素与s1的元素匹配
for (j = top1; j > 0; j--){
if (s1[j] == s[i])
return s[i]; //返回LCA
}
}
} //if
if (s[top] == q&&gotp){
//若后找到q
for (i = top; i > 0; i--){
//将s的元素与s1的元素匹配
for (j = top1; j > 0; j--){
if (s1[j] == s[i])
return s[i]; //返回LCA
}
}
} //if
top--;
r = bt;
bt = NULL;
} //else
} //else
} //while
return NULL; //无CA,即公共祖先
}
结构体定义
typedef struct BTNode
{
char INFO;
struct BTNode *LLINK,*RLINK;
}BTNode,*BiTree;
头文件及主函数
void main(){
BTNode *A = (BTNode*)malloc(sizeof(BTNode));
BTNode *B = (BTNode*)malloc(sizeof(BTNode));
BTNode *C = (BTNode*)malloc(sizeof(BTNode));
BTNode *D = (BTNode*)malloc(sizeof(BTNode));
BTNode *E = (BTNode*)malloc(sizeof(BTNode));
BTNode *F = (BTNode*)malloc(sizeof(BTNode));
BTNode *G = (BTNode*)malloc(sizeof(BTNode));
BTNode *H = (BTNode*)malloc(sizeof(BTNode));
BTNode *I = (BTNode*)malloc(sizeof(BTNode));
BTNode *p, *q, *ROOT, *res, *res2, *res3;
A->INFO = 'A'; A->LLINK = B; A->RLINK = C;
B->INFO = 'B'; B->LLINK = D; B->RLINK = E;
C->INFO = 'C'; C->LLINK = F; C->RLINK = G;
D->INFO = 'D'; D->LLINK = H; D->RLINK = I;
E->INFO = 'E'; E->LLINK = NULL; E->RLINK = NULL;
F->INFO = 'F'; F->LLINK = NULL; F->RLINK = NULL;
G->INFO = 'G'; G->LLINK = NULL; G->RLINK = NULL;
H->INFO = 'H'; H->LLINK = NULL; H->RLINK = NULL;
I->INFO = 'I'; I->LLINK = NULL; I->RLINK = NULL;
ROOT = A;
p = E;
q = I;
res = GetLCA(ROOT, p, q);
res2 = GetLCA2(ROOT, p, q);
res3 = GetLCA3(ROOT, p, q);
printf("LCA of %c and %c is %c.\nLCA of %c and %c is %c.\nLCA of %c and %c is %c.\n", \
p->INFO, q->INFO, res->INFO, p->INFO, q->INFO, res2->INFO, p->INFO, q->INFO, res3->INFO);
}
原来文章中出现了豹栗二字,文章在发表之前还要过审,口怕
2019.05.29update:原来现在所有文章都要过审了