利用栈求二叉树中两节点的最近共同祖先(无父节点指针)

最近的数据结构课刚教完二叉树,昨晚在做学院自己弄的一个集成环境上的数据结构题目时,看到一个比起其他二叉树简单遍历、或者计算叶子或者深度看起来难一点的题目。就是最近共同祖先节点问题(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>    //找不到的话便返回空指针
}

总结: 通过迭代降低了时间复杂度,但使用了辅助的栈和数组,空间复杂度上升。具体的时间复杂度和空间复杂度不大会算。。。。

若有需要优化的地方请指出,谢谢。

    原文作者:B树
    原文地址: https://blog.csdn.net/wayne_b/article/details/46276429
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞