有一个1亿结点的树,已知两个结点, 求它们的最低公共祖先!
对该问题,分为如下几种情形讨论:
情形一:
假如该树为二叉树,并且是二叉搜索树, 依据二叉搜索树是排过序的, 我们只需要从树的根结点开始,逐级往下,和两个输入的结点进行比较.
如果当前结点的值比两个结点的值都大,那么最低的公共祖先一定在当前结点的左子树中,下一步遍历当前结点的左子节点.
如果当前结点的值比两个结点的值都小,那么最低的公共祖先一定在当前结点的右子树中,下一步遍历当前结点的右子结点.
这样在树中从上到下找到的第一个在两个输入结点的值之间的结点,就是最低的公共祖先.
情形二:
如果是一棵普通的树,但是树的结点(除根结点外)中有指向父结点的指针:
这个问题可以转换为求两个链表的第一个公共结点.
假设树结点中指向父结点的指针是pParent,那么从树的每一个叶结点开始都一个由指针pParent串起来的链表,这些链表的尾指针都是树的根结点.
输入两个结点,这两个结点位于两个链表上,它们的最低公共祖先刚好是这两个链表的第一个公共结点. 比如输入的两个结点分别为F和H,那么F在链表F->D->B->A上,
而H在链表H->E->B->A上, 这两个链表的第一个交点B 刚好也是它们的最低公共祖先.参见下面的图示
情形三:
仅是一棵普通的树,没有其它条件:
求出从根结点到指定结点的路径,这样我们就得到两条路径,比较这两条路径的最低公共祖先就可以了.
具体的思路还是,从树的根节点开始遍历,参见下面的图,
假设还是输入结点F和H, 我们先判断A的子树中是否同时包含结点F和H, 得到的结果为true,接着我们再先后判断A的两个子结点B和C的子树是否同时包含F和H,结果是B的结果为true而C的结果是false, 接下来我们再判断B的两个子结点D和E,发现这两个结点得到的结果都是false,于是B是最后一个公共祖先,即是我们的输出.
这里需要注意的问题是, 如何避免对同一个结点重复遍历很多次的问题?
比如当我们判断以结点A为根的树中是否含有结点F的时候, 我们需要对D,E等结点遍历一遍;接下来判断以结点B为根的树中是否含有结点F时, 我们还是需要对D,E等结点再次遍历一遍.
解决这个问题的思路是:
在遍历的过程中使用辅助缓存,用两个链表分别保存从根结点到输入的两个结点之间的路径,然后把问题转换为两个链表的最后公共结点的问题.
比如我们用前序遍历的方法得到从根结点A到H的路径的过程如下:
(1)遍历到A,将A放入路径中,路径中只有一个结点A;
(2)遍历到B,把B存到路径中去,此时路径为A->B;
(3)遍历到D,把D放到路径中去,此时路径为A->B->D;
(4)遍历到F,把F放到路径中去,此时路径为A->B->D->F;
(5)F为叶结点,这条路径不可能到达节点H,我们需要回溯,把F从路径中删除,变为A->B->D;
(6)遍历到G,和结点F一样,这条路径无法到达H,遍历玩G后,这条路径仍是A->B->D;
(7)由于D的所有结点都遍历过了,不可能到达结点H, 因此D不在路径中,将D从路径中删除,变成A->B;
(8)遍历E,把E加入到路径中,此时路径变成A->B->E;
(9)遍历H,已经到达目标结点,A->B->E就是根结点开始到达H必须经过的路径.
按照上面的方法,我们同理可以得到从根结点A开始到F必须经过的路径是A->B->D.
接着,我们求出这两个路径的最后公共结点,也就是B,就是我们要求的F和H的最低公共祖先.
时间复杂度分析:
为了得到从根结点开始到输入的两个结点的两条路径,需要遍历两次树,每次遍历的时间复杂度是O(n),得到的两条路径的长度在最差的情况是O(n),通常情况下两条路径的长度是O(logn)
下面是情形三的实现代码,文件名为common_parent_in_tree.cpp
#include "tree.h"
#include <list>using namespace std;bool GetNodePath(TreeNode* pRoot, TreeNode* pNode, list<TreeNode*>& path){if(pRoot == pNode)return true;path.push_back(pRoot);bool found = false;vector<TreeNode*>::iterator i = pRoot->vec_children.begin();while(!found && i<pRoot->vec_children.end()){found = GetNodePath(*i, pNode, path);++i;}if(!found)path.pop_back();return found;
}TreeNode* GetLastCommonNode(const list<TreeNode*>& path1, const list<TreeNode*>& path2){list<TreeNode*>::const_iterator iterator1 = path1.begin();list<TreeNode*>::const_iterator iterator2 = path2.begin();TreeNode* pLast = NULL;while(iterator1 != path1.end() && iterator2 != path2.end()){if(*iterator1 == *iterator2)pLast = *iterator1;iterator1++;iterator2++;}return pLast;
}TreeNode* GetLastCommonParent(TreeNode* pRoot, TreeNode* pNode1, TreeNode* pNode2){if(pRoot == NULL || pNode1 == NULL || pNode2 == NULL)return NULL;list<TreeNode*> path1;GetNodePath(pRoot, pNode1, path1);list<TreeNode*> path2;GetNodePath(pRoot, pNode2, path2);return GetLastCommonNode(path1,path2);
}//===========================测试代码==================================void Test(const char* testName, TreeNode* pRoot, TreeNode* pNode1, TreeNode* pNode2, TreeNode* pExpected){if(testName != NULL)printf("%s begins: \n", testName);TreeNode* pResult = GetLastCommonParent(pRoot, pNode1, pNode2);if((pExpected == NULL && pResult == NULL) ||(pExpected != NULL && pResult != NULL && pResult->value == pExpected->value))printf("Passed.\n");elseprintf("Failed.\n");
}// 形状普通的树
// 1
// / \
// 2 3
// / \
// 4 5
// /\ /|\
// 6 78 9 10
void Test1(){TreeNode* pNode1 = CreateTreeNode(1);TreeNode* pNode2 = CreateTreeNode(2);TreeNode* pNode3 = CreateTreeNode(3);TreeNode* pNode4 = CreateTreeNode(4);TreeNode* pNode5 = CreateTreeNode(5);TreeNode* pNode6 = CreateTreeNode(6);TreeNode* pNode7 = CreateTreeNode(7);TreeNode* pNode8 = CreateTreeNode(8);TreeNode* pNode9 = CreateTreeNode(9);TreeNode* pNode10 = CreateTreeNode(10);ConnectTreeNodes(pNode1,pNode2);ConnectTreeNodes(pNode1,pNode3);ConnectTreeNodes(pNode2,pNode4);ConnectTreeNodes(pNode2,pNode5);ConnectTreeNodes(pNode4,pNode6);ConnectTreeNodes(pNode4,pNode7);ConnectTreeNodes(pNode5,pNode8);ConnectTreeNodes(pNode5,pNode9);ConnectTreeNodes(pNode5,pNode10);Test("Test1", pNode1, pNode6, pNode8, pNode2);
}//树退化为一个链表
// 1
// /
// 2
// /
// 3
// /
// 4
// /
// 5
void Test2(){TreeNode* pNode1 = CreateTreeNode(1);TreeNode* pNode2 = CreateTreeNode(2);TreeNode* pNode3 = CreateTreeNode(3);TreeNode* pNode4 = CreateTreeNode(4);TreeNode* pNode5 = CreateTreeNode(5);ConnectTreeNodes(pNode1,pNode2);ConnectTreeNodes(pNode2,pNode3);ConnectTreeNodes(pNode3,pNode4);ConnectTreeNodes(pNode4,pNode5);Test("Test2",pNode1,pNode5,pNode4,pNode3);
}//树退化为一个链表,一个结点不在树中
// 1
// /
// 2
// /
// 3
// /
// 4
// /
// 5 6
void Test3(){TreeNode* pNode1 = CreateTreeNode(1);TreeNode* pNode2 = CreateTreeNode(2);TreeNode* pNode3 = CreateTreeNode(3);TreeNode* pNode4 = CreateTreeNode(4);TreeNode* pNode5 = CreateTreeNode(5);ConnectTreeNodes(pNode1,pNode2);ConnectTreeNodes(pNode2,pNode3);ConnectTreeNodes(pNode3,pNode4);ConnectTreeNodes(pNode4,pNode5);TreeNode* pNode6 = CreateTreeNode(6);Test("Test3",pNode1,pNode5,pNode6,NULL);
}//输入NULL
void Test4(){Test("Test4", NULL, NULL, NULL, NULL);
}int main(int argc, char** argv){Test1();Test2();Test3();Test4();return 0;
}
下面是程序运行结果的截图:
相关文章:

数据库SQL优化大总结之百万级数据库优化方案
1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t w…

js定时执行函数
方法一: //直接现定义函数 var time window.setInterval(function(){ $(’.lingdao_right’).click(); },5000); 方法二: //执行已经有的函数 var time window.setInterval(‘abc()’,5000); 清除js自动执行 clearInterval(time); //time就是定义时的名称,如上

BST(binary search tree)类型题目需要用到的头文件binary_tree.h
下面是二叉搜索树需要用到的头文件binary_tree.h #include <stdio.h>struct BinaryTreeNode{int value;BinaryTreeNode* pLeft;BinaryTreeNode* pRight; };BinaryTreeNode* CreateBinaryTreeNode(int value){BinaryTreeNode* pNode new BinaryTreeNode();pNode->valu…

终止js程序执行的方法
js终止程序执行的方法共有三种 (一)在function里面(普通js方法) (1)return; (2)return false; (二)非function方法里面(如ajax方法) alert(“发生异常”); throw SyntaxError(); ale…

将BST转换为有序的双向链表!
在二叉树中,每个结点都有两个指向子结点的指针. 在双向链表中, 每个结点也有两个指针,它们分别指向前一个结点和后一个结点.由于这两种结构的相似性, 同时二叉搜索树也是一种排过序的数据结构, 因此在理论上有可能实现二叉搜索树和排序的双向链表之间的转换. 下面的文件BST_to_…

计算机病毒实践汇总五:搭建虚拟网络环境
在尝试学习分析的过程中,判断结论不一定准确,只是一些我自己的思考和探索。敬请批评指正! 涉及内容: INetSim安装及使用 ApateDNS安装及使用 1. 搭建病毒分析网络环境原因 使用虚拟机作为沙箱能把病毒与外界完全隔离开,…

form表单提交前进行ajax或js验证,校验不通过不提交
在使用form表单进行提交数据前,需要进行数据的校验->表单的校验(如:两次密码输入是否相同)后台数据的校验(如:账号是否存在),这个时候,如果哪步校验不通过,…

中体骏彩C++面试题
下面是我凭记忆想到的几个题目,有需要的同学就拿去吧,我也算做了点善事. 中体骏彩C笔试题 2013-11-18 1.指针的含义是:B A.名字 B.地址 C.名称 D.符号 2.给出下面的程序输出: #include <iostream> #include <cstdlib> #include <cstring> #include <l…

Fibonacci数列的java实现
关于Fibonacci应该都比较熟悉,0,1,1,2,3.。。。。 基本公式为f(n) f(n-1) f(n-2); f(0) 0; f(1) 1; 方法1:可以运用迭代的方法实现: public static int f1(int n){if(n<1)return n;return f1(n-1) f1(n-2); }实现方法简单。 方法2&am…

stream流对象的理解及使用
我的理解:用stream流式处理数据,将数据用一个一个方法去 . (点,即调用) 得到新的数据结果,可以一步达成。 有多种方式生成 Stream Source: 从 Collection 和数组 Collection.stream()Collecti…

永成科技C++笔试题
最后几个题有点难度,在这里说一下: 永成科技C笔试题 2013-11-19 1.将1亿以内的质数存到一个超级大的数组中,用算法如何实现? 使用"筛法"求解1亿以内的质数的程序的思路: 先动态分配1亿个bit(总计12500000字节),用字节中的每一位代表每一个整数,首先将代表奇数的那…

事务库事务隔离级别
为了快速同步数据的需要,我分段执行了两次python脚本,即开启了两个进程同步数据,结果服务器不时报出数据库死锁异常,通过排查代码和数据库日志发现,是由长事务并发引起的。代码中有入账和出账两个方法,里面…
十大算法,描述+代码+演示+分析+改进(赶紧收藏!)
十大算法 1.冒泡排序 (1)算法描述 冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是…

webkit入门准备
《webkit入门准备》1. Ca) Webkit代码风格b) Inlinec) Constd) 构造与析构e) 重载f) 继承2. 泛式编程a) Vector/List/HashTableb) Iteratorc) 智能指针3. 面向对象编程a) 对象概念b) …

oracle操作
一、导入dmp文件: 1.创建表空间create tablespace 表空间 datafile 路径\文件名.dbf size 1500M autoextend on next 5M maxsize 3000M;注:路径必须为已创建2.创建用户create user 用户名 identified by 密码 default tablespace 表空间;3.更改用户的表空…

一个form表单,多个提交按钮(实现不同功能和地址的提交)
直接上代码 表单部分: <form action"" name"find" method"post" enctype"multipart/form-data"><input type"text" name"name"/><br/><button οnclick"f1()"/>找…

chrome 硬件渲染(GPU Accelerated Compositing in Chrome)
原文链接 http://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome chrome 中集成了webkit,这篇文章对webkit 硬件渲染过程有详细的介绍,很好。 简介 这篇文档讲解chrome硬件加速合成的实现细节和背景。 介绍 通常来讲&#…

CCS Font 知识整理总结
总是搞不懂 CCS 中如何正确的使用字体,这下明白了。 1、什么是 font-face font-face 顾名思义,就是文字的脸。字体是文字的外在形式,就是文字的风格,是文字的外衣。比如行书、楷书、草书,都是一种字体。同样一个字每个…

Maven安装与配置(最实用!!!)eclipse中配置maven
Maven安装与配置 一、需要准备的东西 JDKEclipse(本章主要是在eclipse中进行配置maven)Maven程序包 二、下载与安装 1. 前往maven下载最新版的Maven程序: 2. 将文件解压到D:\Program Files\Apache\maven目录下(这样子放目录结…
在Ubuntu 12.04 64bit上配置,安装和运行go程序
注意:下面的安装配置均遵从官网或是教材《Go语言程序设计》中的部分内容. 顺便说下,这是一本很难得的Go语言的入门教程,非常基础和全面。起初我因为这本书的封面比较讨厌它,闲置几年之后,一次偶尔要用时静心翻阅之后,发…

Linux下三个密码生成工具
http://code.csdn.net/news/2820879 想出一个难破解且容易记的密码对不是一件简单的事情。在我为电脑设定一个新密码,或者在线注册了一个新的账号,需要输入密码的时候,脑袋就一片空白。不过,Linux下有几个密码生成工具可以使用&am…

javabean实体类与实体类之间的快速转换
一、Dozer是什么? dozer是一个能把实体和实体之间进行转换的工具.只要建立好映射关系.就像是ORM的数据库和实体映射一样. 使用方法示例如下: // article(PO) -> articleVOArticleVO articleVO dozerMapper.map(article, ArticleVO.class);这段示例代码。将从数…

ATS程序功能和使用方法详解
转载自https://blog.zymlinux.net/index.php/archives/374 Apache Traffic Server的程序文件,与传统的服务器系统有大不同,这里我们将会对这些文件进行详细的解读,并尽可能的对程序的功能和基本用法、参数等进一步说明,以利于新入…

java 读取txt,java读取大文件
java 读取txt,java读取大文件 package com.bbcmart.util; import java.io.File;import java.io.RandomAccessFile;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel; public class Test { public static void main(String[] args) throws Exception …

Spring Boot整合Spring Data JPA操作数据
一、 Sping Data JPA 简介 Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用框架,底层使用了 Hibernate 的 JPA 技术实现,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能&…
常用Linux命令总结
常用Linux命令总结 2013-12-08 压缩为gz格式 gzip error_2018082217.log 解压gz格式 gzip -d error_2018082217.log.gz 不解压来搜索gz格式的文件中的匹配行内容 gunzip -c 不真正解压.gz文件,而是检查该文件,不会生成多余的文件 gunzip -c error_20…
调试uIP出现死机问题
在调试uIP,加入http功能时,调试出现死循环 原因是所加入的http文件中含有printf等输出函数,遇到这种情况,有2种解决方法: 1.Keil中勾选“Use MicroLIB” 2. //加入以下代码,支持printf函数,而…

html+spring boot简单的ajax数据传输实现
本篇讲解在前后端不分离情况下的htmlspring boot的项目数据传输实现 首先,后台我写了三个接口 package com.demo.ajax.controller;import com.demo.ajax.Entity.Person; import lombok.extern.slf4j.Slf4j; import org.jboss.logging.Param; import org.springfram…

Tafficserver旁路接入方案综述
转载自 https://blog.zymlinux.net/index.php/archives/821 随着宽带技术的加速普及,目前,几款高性能开源CDN方案在广大开源爱好团队的充分的测试、企业服务应用验证中破壳而出。实际这个地球的互联网用户都在知情与不知情之间使用了ATS的环保服务。这方…

url中去掉index.php,方便redirect()
01 配置文件 return Array( URL_MODEL > 2,); 02 index.php入口文件下面加入文件 .htaccess -->使用editplus-->另存为 <IfModule mod_rewrite.c>RewriteEngine onRewriteCond %{REQUEST_FILENAME} !-dRewriteCond %{REQUEST_FILENAME} !-fRewriteRule ^(.*)$ i…