寻找二叉树两节点的最近的公共祖先[转载+整理]

1.树节点定义中带有parent指针

struct TreeNode
{
    int data;
    TreeNode *left,*right,*parent;
};

算法思想:

(1). p->parent

(2). 将q的所有祖先节点依次和p->parent作比较,如果发现两个节点相等,则该节点就是最近公共祖先,直接将其返回。如果没找到相等节点,则转3

(3).p = p->parent,转2

主要代码如下:

const TreeNode* FindNCA(const TreeNode* root,const TreeNode* p,const TreeNode* q)
{
    if(root==NULL || p==NULL || q==NULL)
        return NULL;
    while(p)
    {
        p = p->parent;
        const TreeNode* tmp = q;
        while(tmp)
        {
            if(p == tmp->parent)
                return p;
            tmp = tmp->parent;
        }
    }
}
void TestNCA()
{
    TreeNode *root  = newTreeNode(2);
    TreeNode *p1 = newTreeNode(1);
    root->left = p1;
    p1->parent = root;
    TreeNode *p4 = newTreeNode(4);
    root->right = p4;
    p4->parent = root;
    TreeNode *p3 = newTreeNode(3);
    root->right->left  = p3;
    p3->parent = root->right;
    TreeNode *p5 = newTreeNode(5);
    root->right->right = p5;
    p5->parent = root->right;
//    Constructed binary search tree is
//            2
//           / \
//         1   4
//             / \
//           3   5
    const TreeNode* out = FindNCA(root,p1,p5);
    if(out) printf("\nResult pointer is %d", out->data);
    else printf("\nNot find pointer");
}
时间复杂度:假设p,q的公共父节点为cur,p到cur的路径长度为LP,q到cur的路径长度LQ, 则时间复杂度为O(LP*LQ).空间复杂度O(1).

另外一种可能会降低时间复杂度的方法:

算法思想:

(1). 从root到p简历一条链表list1,从root到q简历一条链表list2

(2). 问题转为为求list1和list2的最后一个相同的节点

主要代码如下

struct Node //链表节点
{
    Node* next;
    int data;
};
bool PushFront(Node** phead, int data)
{
    Node* newNode = new Node;
    if(newNode == NULL) return false;
    newNode->data = data;
    newNode->next = *phead;//无论*phead是否为空都行
    *phead = newNode;
    return true;
}
const Node* FindLastSameNode(const Node* head1,const Node* head2)
{
    if(head1==NULL || head2==NULL)
        return NULL;
    const Node* prev1 = NULL;
//本来应该写成head1==head2,但是这里的实现不大好
    while(head1 && head2 && head1->data==head2->data)   
  {
        prev1 = head1;
        head1 = head1->next;
        head2 = head2->next;
    }
    return prev1;
}
const Node* FindNCA(const TreeNode* root,const TreeNode* p,const TreeNode* q)
{
    if(root==NULL || p==NULL || q==NULL)
        return NULL;
    Node *list1=NULL, *list2=NULL;
    while(p != NULL)
    {
        PushFront(&list1,p->data);
        p = p->parent;
    }
    //cout<<endl;
    while(q != NULL)
    {
        PushFront(&list2,q->data);
        q = q->parent;
    }
    //cout<<"\nlist1: ";Print(list1); //for debug
    //cout<<"\nlist2: ";Print(list2); //for debug
    return FindLastSameNode(list1, list2);
}
void TestNCA()
{
    TreeNode *root  = newTreeNode(2);
    TreeNode *p1 = newTreeNode(1);
    root->left = p1;
    p1->parent = root;
    TreeNode *p4 = newTreeNode(4);
    root->right = p4;
    p4->parent = root;
    TreeNode *p3 = newTreeNode(3);
    root->right->left  = p3;
    p3->parent = root->right;
    TreeNode *p5 = newTreeNode(5);
    root->right->right = p5;
    p5->parent = root->right;
//    Constructed binary search tree is
//            2
//           / \
//         1   4
//             / \
//           3   5
    const Node* out = FindNCA(root,p1,p5);
    if(out) printf("\nResult pointer is %d", out->data);
    else printf("\nNot find pointer");
}

时间复杂度分析:假设p节点到根节点的路径长度为L1,假设q节点到根节点的路径长度为L2,则算法的时间复杂度为O(L1+L2),空间复杂度小于O(L1+L2).

2.树节点定义中不带有parent指针

主要思想:节点cur的左子树包含一个节点(p或者q),右子树包含另一个节点(p或者q),则cur就是我们要找的节点。

//不带parent指针的树节点
struct TreeNode
{
    int data;
    TreeNode *left,*right;
};
TreeNode* newTreeNode(int data)
{
    TreeNode* node = new TreeNode;
    node->data = data;
    node->left = NULL;
    node->right = NULL;
    return node;
}
//返回root为根的树种包含p,q两个节点中节点的数目:0,1,2
int FindNCA(TreeNode* root,const TreeNode* p,const TreeNode* q,TreeNode** out)
{
    if(root==NULL) return 0;
    if(root==p || root==q) return 1;
    int iLeft = FindNCA(root->left,p,q,out);
    if(iLeft==2) return 2;
    int iRight = FindNCA(root->right,p,q,out);
    if(iRight==2) return 2;
    if(iLeft+iRight==2) *out = root;
    return iLeft+iRight;
}
void TestNCA()
{
    TreeNode *root  = newTreeNode(2);
    TreeNode *p1 = newTreeNode(3);
    root->left = p1;
    TreeNode *p4 = newTreeNode(3);
    root->right = p4;
    TreeNode *p3 = newTreeNode(3);
    root->right->left  = p3;
    TreeNode *p5 = newTreeNode(5);
    root->right->right = p5;
//    Constructed binary search tree is
//            2
//           / \
//         1   4
//             / \
//           3   5
    TreeNode* out = NULL;
    int i = FindNCA(root, p1, p5, &out);
    if( i == 2 ) printf("Result pointer is %d", out->data);
    else printf("Not find pointer");
}

时间复杂度分析:设书中节点的数目为N(遍历所有的树节点),则时间复杂度为O(N),空间复杂度O(1)


再加上一个老外的实现:

Given the values of two nodes in a *binary search tree*, write a c program to find the lowest common ancestor. You may assume that both values already exist in the tree.

The function prototype is as follows:

 int FindLowestCommonAncestor(node* root, int value1, int value)

  I/P : 4 and 14
  O/P : 8
  (Here the common ancestors of 4 and 14, are {8,20}.
  Of {8,20}, the lowest one is 8).

Here is the solution

Algorithm:
The main idea of the solution is — While traversing Binary Search Tree from top to bottom, the first node n we encounter with value between n1 and n2, i.e., n1 < n < n2 is the Lowest or Least Common Ancestor(LCA) of n1 and n2 (where n1 < n2). So just traverse the BST in pre-order, if you find a node with value in between n1 and n2 then n is the LCA, if it's value is greater than both n1 and n2 then our LCA lies on left side of the node, if it's value is smaller than both n1 and n2 then LCA lies on right side.

Implementation:

#include <stdio.h>
#include <stdlib.h>
 
/* A binary tree node has data, pointer to left child
   and a pointer to right child */
struct node
{
    int data;
    struct node* left;
    struct node* right;
};
 
struct node* newNode(int );
 
/* Function to find least comman ancestor of n1 and n2 */
int leastCommanAncestor(struct node* root, int n1, int n2)
{
  /* If we have reached a leaf node then LCA doesn't exist
     If root->data is equal to any of the inputs then input is
     not valid. For example 20, 22 in the given figure */
  if(root == NULL || root->data == n1 || root->data == n2)
    return -1; 
 
  /* If any of the input nodes is child of the current node
     we have reached the LCA. For example, in the above figure
     if we want to calculate LCA of 12 and 14, recursion should
     terminate when we reach 8*/
  if((root->right != NULL) &&
    (root->right->data == n1 || root->right->data == n2))
    return root->data;
  if((root->left != NULL) &&
    (root->left->data == n1 || root->left->data == n2))
    return root->data;    
 
  if(root->data > n1 && root->data < n2)
    return root->data;
  if(root->data > n1 && root->data > n2)
    return leastCommanAncestor(root->left, n1, n2);
  if(root->data < n1 && root->data < n2)
    return leastCommanAncestor(root->right, n1, n2);
}    
 
/* Helper function that allocates a new node with the
   given data and NULL left and right pointers. */
struct node* newNode(int data)
{
  struct node* node = (struct node*)
                       malloc(sizeof(struct node));
  node->data  = data;
  node->left  = NULL;
  node->right = NULL;
 
  return(node);
}
 
/* Driver program to test mirror() */
int main()
{
  struct node *root  = newNode(2);
  root->left         = newNode(1);
  root->right        = newNode(4);
  root->right->left  = newNode(3);
  root->right->right = newNode(5); 
 
/* Constructed binary search tree is
            2
           / \
         1   4
             / \
           3   5
*/
  printf("\n The Least Common Ancestor is \n");
  printf("%d", leastCommanAncestor(root, 3, 5));
 
  getchar();
  return 0;
}

Note that above function assumes that n1 is smaller than n2.

Time complexity: Time complexity is O(Logn) for a balanced BST and O(n) for a skewed BST.

The question was asked by Varun Bhatia


好像发现上面的实现都是假设二叉树左子节点小于根节点,右子树节点大于根节点,假如我的数不是那样存储的,貌似就没有用咯。。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值