最近的数据结构课刚教完二叉树,昨晚在做学院自己弄的一个集成环境上的数据结构题目时,看到一个比起其他二叉树简单遍历、或者计算叶子或者深度看起来难一点的题目。就是最近共同祖先节点问题(NCA-nearest common ancestor)。
想了挺久终于用递归把它解决了。但在回宿舍的路上又想到了不用递归更高效一点的算法,今天回到工作室也成功实现。但是代码还是太长了,想着能在bing或者baidu上看到更厉害的算法,但大多数还是递归实现的。接下来我就把我的非递归方法贴上来吧。
问题介绍:
【题目】试编写算法,求二叉树T中节点a和b的最近共同祖先。
二叉树类型定义:
typedef struct BiTNode {
TElemType data;
struct BiTNode *lchild,*rchild;
} BiTNode, *BiTree;
可用栈类型Stack的相关定义:
typedef struct {
BiTNode *ptr; // 二叉树节点的指针类型
int tag; // 0..1
} SElemType; // 栈的元素类型
Status InitStack(Stack &S);//初始化栈
Status StackEmpty(Stack S);//判断栈是否为空
int StackLength(SqStack S);//获得栈大小
Status Push(Stack &S, SElemType e);//进栈
Status Pop(Stack &S, SElemType &e);//出栈
BiTree CommAncestor(BiTree T, TElemType a, TElemType b);//接口
我的
初步思路是: (1)它只提供了两个节点的内容而不是地址,所以首先要找到这两个节点的地址。 (2)分别得到这两个节点的祖先。 (3)将两个节点的祖先比对找到最近的共同祖先。
既然都提供了栈定义,每个栈的元素除了指针域还有个标志域,很自然的我就想到了二叉树的非递归后序遍历。
其中还有一个更重要的原因是,利用栈的后序遍历可以保留目标节点的所有祖先节点。使用先序遍历或者中序遍历时,目标节点若位于其祖先节点的右子树上,该祖先节点不会存在栈中。(可参考三种遍历的定义,不详述。)
这样一来,更
详细的思路出来了: (1)利用两个栈(Sa,Sb)分别后序遍历二叉树找到a,b节点后退出排序。保留栈里面的祖先节点。 (2)将其中一个栈Sa的节点循环出栈到一个指针数组Aa中。这样一来数组Aa中的指针便是由根到子节点的顺序存放的(与栈Sa相反)。 (3)然后Sb循环出栈,与Aa中的元素循环比较,第一个相同的节点就是a,b最近的共同祖先节点NCA。
最后是
代码实现。
/******
后序遍历辅助函数 深入二叉树并区分节点入栈
栈里面每个元素的tag域用来区分遍历的阶段
0表示刚遍历完该节点的左子树,需要遍历右子树
1表示已经遍历完该节点左右子树
******/
void GoFar(BiTree &T,Stack &S){
SElemType temp;
while(1){
temp.ptr = T;
temp.tag = 1; //默认只有一个子节点
if(NULL != T->lchild) { //若有左孩子就继续深入左孩子
if(NULL != T->rchild) temp.tag = 0; //若还有右孩子就致tag为0,仍要继续遍历
T = T->lchild;
}
else if(NULL != T->rchild) T = T->rchild; //没有左孩子只有右孩子就深入右孩子
else return;
Push(S,temp); //T节点入栈
}
}
/****
根据节点数据后序遍历寻找a节点,栈因为在主函数还要用到因此作为引用参数
****/
bool PostOrder(BiTree T,Stack &S,TElemType a){
if(T == NULL||T->data == a) return FALSE; //根节点为空或根节点为目标节点(本身不能是本身的祖先)既该节点没有祖先,返回错误
GoFar(T,S); //调用函数深入二叉树
SElemType temp;
while(T != NULL){
if(T->data == a) return TRUE; //找到该节点,返回成功
if(StackEmpty(S)) return FALSE; //遍历结束,没有找到节点
else Pop(S,temp); //祖先节点出栈
T = temp.ptr;
if(0 == temp.tag){ //若刚遍历完该节点的左孩子
temp.tag = 1; //有的话tag致1入栈
Push(S,temp);
T = T->rchild;
GoFar(T,S); //且深入它的右子树继续遍历
}
//若已遍历完左右子树就访问自己
}
}
<pre name="code" class="cpp">BiTree CommAncestor(BiTree T, TElemType a, TElemType b)
/* 求二叉树T中节点a和b的最近共同祖先 */
{
Stack Sa,Sb; //辅助栈
BiTree *Aa; //辅助数组
int SaLength; //栈Sa长度
if(a == b) return NULL; //若两节点相等,返回找不到祖先
InitStack(Sa); //初始化栈
InitStack(Sb);
if(!PostOrder(T,Sa,a)||!PostOrder(T,Sb,b)) return NULL; //找不到a或b的话,返回找不到祖先
SaLength = StackLength(Sa); //获取栈Sa长度
Aa = (BiTree*)malloc(SaLength * sizeof(BiTree)); //依据Sa长度开辟数组
for(int i = 0;i < SaLength;i++){ //Sa循环出栈赋值到Aa中
SElemType temp;
Pop(Sa,temp);
*(Aa + i) = temp.ptr;
}
while(!StackEmpty(Sb)){ //从Sb的最深祖先节点开始于Aa中的元素比较
SElemType temp;
Pop(Sb,temp);
for(int i = 0;i < SaLength;i++){
if(*(Aa + i) == temp.ptr) return temp.ptr; //第一次出现的便是节点a和b的最近共同祖先
}
}
return NULL;<span style="white-space:pre"> </span> //找不到的话便返回空指针
}
总结: 通过迭代降低了时间复杂度,但使用了辅助的栈和数组,空间复杂度上升。具体的时间复杂度和空间复杂度不大会算。。。。
若有需要优化的地方请指出,谢谢。