这个问题可以分为三种情况来考虑:
情况一:root未知,但是每个节点都有parent指针

此时可以分别从两个节点开始,沿着parent指针走向根节点,得到两个链表,然后求两个链表的第一个公共节点,这个方法很简单,不需要详细解释的。

情况二:节点只有左、右指针,没有parent指针,root已知
思路:有两种情况,一是要找的这两个节点(a, b),在要遍历的节点(root)的两侧,那么这个节点就是这两个节点的最近公共父节点;

二是两个节点在同一侧,则 root->left 或者 root->right 为 NULL,另一边返回a或者b。那么另一边返回的就是他们的最小公共父节点。

递归有两个出口,一是没有找到a或者b,则返回NULL;二是只要碰到a或者b,就立刻返回。

  1. // 二叉树结点的描述
  2. typedef struct BiTNode
  3. {
  4. char data;
  5. struct BiTNode lchild, rchild; // 左右孩子
  6. }BinaryTreeNode;
  7. // 节点只有左指针、右指针,没有parent指针,root已知
  8. BinaryTreeNode findLowestCommonAncestor(BinaryTreeNode root , BinaryTreeNode* a , BinaryTreeNode* b)
  9. {
  10. if(root == NULL)
  11. return NULL;
  12. if(root == a || root == b)
  13. return root;
  14. BinaryTreeNode* left = findLowestCommonAncestor(root->lchild , a , b);
  15. BinaryTreeNode* right = findLowestCommonAncestor(root->rchild , a , b);
  16. if(left && right)
  17. return root;
  18. return left ? left : right;
  19. }

情况三: 二叉树是个二叉查找树,且root和两个节点的值(a, b)已知

  1. // 二叉树是个二叉查找树,且root和两个节点的值(a, b)已知
  2. BinaryTreeNode* findLowestCommonAncestor(BinaryTreeNode* root , BinaryTreeNode* a , BinaryTreeNode* b)
  3. {
  4. char min , max;
  5. if(a->data < b->data)
  6. min = a->data , max = b->data;
  7. else
  8. min = b->data , max = a->data;
  9. while(root)
  10. {
  11. if(root->data >= min && root->data <= max)
  12. return root;
  13. else if(root->data < min && root->data < max)
  14. root = root->rchild;
  15. else
  16. root = root->lchild;
  17. }
  18. return NULL;
  19. }

1、二叉树定义:

  1. typedef struct BTreeNodeElement_t_ {
  2. void *data;
  3. } BTreeNodeElement_t;
  4. typedef struct BTreeNode_t_ {
  5. BTreeNodeElement_t *m_pElemt;
  6. struct BTreeNode_t_ *m_pLeft;
  7. struct BTreeNode_t_ *m_pRight;
  8. } BTreeNode_t;

2、查找二叉树中两个节点的最低祖先节点(或最近公共父节点等)

最低祖先节点就是从根节点遍历到给定节点时的最后一个相同节点

例如:

A

B                                            C

D                        E                     F                                G

H            I            J            K        L        M                        N    O

如上图,H和J的最低祖先节点是B。

因为从根节点A到H的链路为:   A     B    D   H

从根节点A到J的链路为:   A   B    E   J

查看链路节点可知,B是最后一个相同节点,也就是所谓的最近公共父节点或者说最低祖先节点。

(1)递归方式

如果给定pRoot是NULL,即空树,则返回的公共节点自然就是NULL;

如果给定pRoot与两个节点中任何一个相同,说明,pRoot在就是所要找的两个节点之一,则直接返回pRoot,表明在当前链路中找到至少一个节点;

如果给定pRoot不是两个节点中任何一个,则说明,需要在pRoot的左右子树中重新查找,此时有三种情况:两个节点都在左子树上;两个节点都在右子树上;一个在左子树,一个在右子树上;具体来说,就是:

情况一:如果左子树查找出的公共节点是NULL,则表明从左子树根节点开始到左子树的所有叶子节点等所有节点中,没有找到两个节点中的任何一个,这就说明,这两个节点不在左子树上,不在左子树,则必定在右子树上;

情况二:如果右子树查找的公共节点是NULL,说明在右子树中无法找到任何一个节点,则两个节点必定在左子树上;

情况三: 如果左右子树查找的公共节点都不是NULL,说明左右子树中各包含一个节点,则当前节点pRoot就是最低公共节点,返回就可以了。

三种情况是互斥的, 只能是其中之一。

  1. BTreeNode_t *GetLastCommonParent( BTreeNode_t *pRoot, BTreeNode_t *pNode1, BTreeNode_t *pNode2){
  2. if( pRoot == NULL ) //说明是空树,不用查找了,也就找不到对应节点,则返回NULL
  3. return NULL;
  4. if( pRoot == pNode1 || pRoot == pNode2 )//说明在当前子树的根节点上找到两个节点之一
  5. return pRoot;
  6. BTreeNode_t *pLeft = GetLastCommonParent( pRoot->m_pLeft, pNode1, pNode2); //左子树中的查找两个节点并返回查找结果
  7. BTreeNode_t *pRight = GetLastCommonParent( pRoot->m_pRight, pNode1, pNode2);//右子树中查找两个节点并返回查找结果
  8. if( pLeft == NULL )//如果在左子树中没有找到,则断定两个节点都在右子树中,可以返回右子树中查询结果;否则,需要结合左右子树查询结果共同断定
  9. return pRight;
  10. if ( pRight == NULL )//如果在右子树中没有找到,则断定两个节点都在左子树中,可以返回左子树中查询结果;否则,需要结合左右子树查询结果共同断定
  11. return pLeft;
  12. return pRoot;//如果在左右子树中都找两个节点之一,则pRoot就是最低公共祖先节点,返回即可。
  13. }

(2)非递归方式:

  1. BTreeNode_t *GetLastCommonParent(BTreeNode_t *pRoot, BTreeNode_t *pNode1, BTreeNode_t *pNode2){
  2. if( pRoot == NULL || pNode1 == NULL || pNode2 == NULL)
  3. return NULL;
  4. vector < BTreeNode_t *> vec1;//用来保存从根节点到指定节点的遍历路径,前序遍历
  5. vector <BTreeNode_t *> vec2;
  6. stack <BTreeNode_t *> st;
  7. bool findflag1 = false;
  8. bool findflag2 = false;
  9. BTreeNode_t *commonParent = NULL;
  10. while( pRoot != NULL || !st.empty() ){
  11. while( pRoot != NULL ){
  12. if( findflag1 == false){//没有找出所有的节点:从根节点到指定节点,在遍历时继续入栈
  13. vec1.push_back( pRoot);
  14. if( pRoot == pNode1)//找到,则设置标志位
  15. findflag1 = true;
  16. }
  17. if( findflag2 == false ){
  18. vec1.push_back( pRoot);
  19. if( pRoot == pNode2 )
  20. findflag2 = true;
  21. }
  22. if( findflag1 == true && findflag2 == true)//如果都已找到,则退出
  23. break;
  24. st.push( pRoot);
  25. pRoot = pRoot->m_pLeft;
  26. }
  27. while( !st.empty()){
  28. pRoot = st.top();
  29. st.pop();
  30. pRoot = pRoot->right;
  31. if( findflag1 == false )//没有找到全部路径节点时,就需要将错误路径节点退出
  32. vec1.pop_back();
  33. if( findflag2 == false )
  34. vec2.pop_back();
  35. }
  36. if( findflag1 == true && findflag2 == true)//如果都已找到,则退出
  37. break;
  38. }
  39. if( findflag1 == true && findflag2 == true){//在两个遍历路径上查找最后一个相同的节点,就是最低公共祖先节点(最近公共父节点)
  40. vector< BTreeNode_t *> ::iterator iter1 = vec1.begin();
  41. vector< BTreeNode_t *> ::iterator iter2 = vec2.begin();
  42. while( iter1 != vec1.end() && iter2 != vec2.end() ){
  43. if( *iter1 == *iter2)
  44. commonParent = *iter1;
  45. else
  46. break;
  47. ++iter1;
  48. ++iter2;
  49. }
  50. }
  51. vec1.clear();
  52. vec2.clear();
  53. st.clear();
  54. return commonParent;
  55. }
</pre><pre>

程序员面试100题之十六:二叉树中两个节点的最近公共父节点(最低的二叉树共同祖先)相关推荐

  1. 程序员面试100题之十六:二叉树中两个节点的最近公共父节点

    这个问题可以分为三种情况来考虑: 情况一:root未知,但是每个节点都有parent指针 此时可以分别从两个节点开始,沿着parent指针走向根节点,得到两个链表,然后求两个链表的第一个公共节点,这个 ...

  2. 程序员面试100题之十五:数组分割

    一.题目概述:有一个没有排序,元素个数为2N的正整数数组.要求把它分割为元素个数为N的两个数组,并使两个子数组的和最接近. 假设数组A[1..2N]所有元素的和是SUM.模仿动态规划解0-1背包问题的 ...

  3. 程序员面试100题之十二:求数组中最长递增子序列

    写一个时间复杂度尽可能低的程序,求一个一维数组(N个元素)中最长递增子序列的长度. 例如:在序列1,-1,2,-3,4,-5,6,-7中,其最长递增子序列为1,2,4,6. 分析与解法 根据题目要求, ...

  4. 程序员面试100题之十四:强大的和谐

    实现一个挺高级的字符匹配算法: 给一串很长字符串,要求找到符合要求的字符串,例如目的串:123 1******3***2 ,12*****3 这些都要找出来,其实就是类似一些和谐系统..... 这题的 ...

  5. 程序员面试100题之十:快速寻找满足条件的两个数

    能否快速找出一个数组中的两个数字,让这两个数字之和等于一个给定的值,为了简化起见,我们假设这个数组中肯定存在至少一组符合要求的解. 假如有如下的两个数组,如图所示: 5,6,1,4,7,9,8 给定S ...

  6. 程序员面试100题之五:二叉树两个结点的最低共同父结点

    题目:二叉树的结点定义如下: struct TreeNode { int m_nvalue; TreeNode* m_pLeft; TreeNode* m_pRight; }; 输入二叉树中的两个结点 ...

  7. 程序员面试100题之六:最长公共子序列

           题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串.注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中.请编写一个函数,输 ...

  8. 程序员面试100题之六 最长公共子序列

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴!      ...

  9. 程序员面试100题之十三:求二叉查找树的镜像

    题目:输入一颗二元查找树,将该树转换为它的镜像,即在转换后的二元查找树中,左子树的结点都大于右子树的结点.用递归和循环两种方法完成树的镜像转换. 例如输入:      8 / \ 6   10 /\  ...

最新文章

  1. 报错解决:Liquid Warning: Liquid syntax error (line 2): Expected dotdot but found id in {{(site.github.p
  2. Centos7使用yum安装MySQL5.6的正确姿势
  3. Android 每天定时提醒功能实现
  4. Java EE企业系统性能问题的原因和解决建议
  5. 太方便 微信能精准搜图片了!网友:出点有用的功能有多难?
  6. 也谈大公司病3——治大国不是烹小鲜
  7. JAVA常见命名规范
  8. Nginx源码分析 - 实战篇 - 编写一个自定义的模块(24)
  9. The General Framework Of Signal ProcessingOTFS Modulation Scheme(信号处理的一般框架OTFS调制)(4)
  10. python爬取起点中文网小说
  11. java 串口 rxtx_java使用RXTX进行串口通信
  12. 【Windows】替换系统文件
  13. python爬虫:Selenium 爬取东方财富网上市公司财务报表
  14. stc单片机“全自动下载”(程序版)
  15. 背景与字体的搭配经验
  16. Nginx反向代理与系统参数配置conf
  17. python 2 入门
  18. 数字化转型,我只推荐看这两本书
  19. DHT协议(官方版本)
  20. 【总结】56个JavaScript 实用工具函数助你提升开发效率!

热门文章

  1. 极简的 PNG 编码函数 svpng(),用来学习C语言,真的很爽
  2. 今天我勇敢的点就一个gpio口
  3. 一个从华为离职的朋友
  4. Linux poll
  5. 设计模式_1_工厂模式与抽象工厂
  6. LeetCode 1813. 句子相似性 III
  7. LeetCode MySQL 1194. 锦标赛优胜者
  8. LeetCode 480. 滑动窗口中位数(大小堆升级版+set实现)
  9. LeetCode 第 187 场周赛(1336/3107,前43.0%)
  10. 数据结构--链表--单链表归并排序mergesort