求二叉树中两结点的最小公共祖先
来源:互联网 发布:知乎 亚马逊退款订单 编辑:程序博客网 时间:2024/06/05 16:18
求二叉树中两结点的最小公共祖先
据说这是微软的一道面试题,谁知道呢。
问题描述:找出二叉树上任意两个指定结点的最近共同父结点(LCA,Least Common Ancestor)。
这算不上是一道算法题了,主要还是看数据结构基本知识和编程能力。
首先考虑最简单的情况——二叉树结点数据结构中有父指针。
这是不是非常简单呢?只要分别从两个结点出发向上走到树根,得到两个结点的分支路径,求出这两条路径相互重合部分的最靠下的结点,就是所求的LCA。这只需要 O(h) 的时空代价(设h是树高,n是树结点数目,平均情况下h = log(n),最坏情况h = n)。
如果想再稍微节省一点儿时间和空间,可以先找出第一条分支路径,并用这些结点建立哈希表,然后从另外一个指定结点开始向上走到树根,每次遇到一个结点就到哈希表中查一下,一旦发现某个结点存在于哈希表中,这个结点就是所求的LCA。这个方法的代码示意如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21def FindLCA(node1, node2):
# Special cases.
if not node1 or not node2:
return None
if node1 is node2:
return node1
# Get the first branch path.
ancestors1 = set()
while node1:
ancestors1.add(node1)
node1 = node1.parent
# Check if any ancestor of node2 is in the first branch path.
while node2:
if node2 in ancestors1:
return node2 # Got it, the LCA.
node2 = node2.parent
# These two nodes have no common ancestor.
return None
这样确实很简单,但实际情况是,通常二叉树结点中并没有父结点指针,这时候就要遍历二叉树找到这两个结点,并找出它们的LCA。
实际上,在遍历二叉树的时候,很容易就能够记录下根结点到任何结点的分支路径,只要有了分支路径,就可以对比找出LCA。
我们采取前序遍历,即N-L-R的顺序,使用堆栈来避免递归并且记录完整的分支路径。那么,在二叉树中查找指定结点的算法可以这样写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27class Dir:
(Undef, Left, Right) = range(3)
def FindNodes(root, nodeSet, findAll=True):
if not root or not nodeSet:
return None
pathDict = {}
path = []
curr = root
while curr or path:
while curr: # Go down along left branch
path.append((curr, Dir.Left))
if curr in nodeSet:
pathDict[curr] = list(path)
nodeSet.remove(curr)
if not nodeSet or not findAll:
return pathDict
curr = curr.left
(curr, dir) = path.pop()
while dir == Dir.Right: # Back from right branch
if not path: return pathDict
(curr, dir) = path.pop()
path.append((curr, Dir.Right)) # Trun to right from left
curr = curr.right
return pathDict
其中Dir这个类相当于是一个枚举,用来定义当前的分支方向。FindNodes除了需要二叉树根结点外,还需要一个待查找的结点集合。这个函数可以在二叉树中找到所有(或第一个)待查找结点的分支路径,并返回一个字典(结点 --> 路径)。
可以看出,FindNodes函数按照前序顺序遍历整个二叉树,查找指定结点。每遇到一个结点,首先判断它是不是我们要找的,如果不是就沿着左边的分支下降到底,然后转入右侧分支。
有了FindNodes函数的支持,我们就可改写前面的FindLCA函数,即先遍历二叉树求出两个结点的分支路径,然后比较这两条路径找出LCA:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25def FindLCA(root, node1, node2):
# Special cases.
if not root or not node1 or not node2:
return None
if node1 is node2:
return node1
# Try to find the two nodes in the tree, and get their branch paths.
nodeSet = set([node1, node2])
pathDict = FindNodes(root, nodeSet)
if nodeSet:
return None
path1 = [i[0] for i in pathDict[node1]]
path2 = [i[0] for i in pathDict[node2]]
# Compare the two paths, find out the LCA.
lca = None
minLen = min(len(path1), len(path2))
for i in xrange(minLen):
if path1[i] is not path2[i]:
break
lca = path1[i]
return lca
遍历二叉树查找所有指定的结点需要 O(n) 时间,O(h) 额外空间;对比两条分支路径需要 O(h) 的时间,因此总的时间代价为 O(n),空间代价为 O(h)。
- 求二叉树中两结点的最小公共祖先
- 求二叉树中两结点的最小公共祖先
- 求二叉树中两个结点的最近公共祖先
- 求两个结点的公共祖先
- 二叉树中找两个结点的最近的公共祖先结点
- 二叉树两结点的最低公共祖先结点(一)
- 二叉树中找两个结点的最近公共祖先结点
- 二叉树中找两个结点的最近公共祖先结点
- 求二叉树中两个节点的最近公共祖先结点
- 【数据结构】求二叉树中两个结点最近的公共祖先
- 二叉树结点公共祖先
- 二叉树的最小公共祖先问题
- 二叉树的最小公共祖先问题
- 求二叉树中两节点的最近公共祖先
- 题目:在二叉树中给出两个已知结点,求这两个结点的最低公共祖先
- 求二叉树两结点最近的共同祖先结点
- 求二叉树中两个节点的最小公共祖先(LCA)
- 二叉树两个结点的最低公共祖先
- NB学校的NB课程的NB教材——CSAPP
- DOCSIS config file-General Packet Classifier Encodings
- 执行计划---索引扫描
- 取球博弈--蓝桥杯
- mysql服务启动、关闭
- 求二叉树中两结点的最小公共祖先
- open与fopen对比
- 0421周赛 HDU 1498 二分匹配
- 关于instanceof的使用
- 简单工厂模式(SimpleFactory)
- uva439 - Knight Moves 国际象棋的跳马
- 我对ORACLE 弹性域的理解
- Keil中 Program Size: Code RO-data RW-data ZI-data 所代表的意思
- Halcon函数总结(八)
2012年04月02日 22:10
如果二叉树结点结构中有父指针,那也可以使用更节省空间的方法,就是先计算出两个结点各自的深度,如果深度不同,则将较靠下的一个结点拉上去,直到两个结点在同一深度处。然后同步向根结点前进,首次相遇时则为最小公共祖先。示意代码(python 2.7)如下:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Special cases.
if not node1 or not node2:
return None
if node1 is node2:
return node1
# Gets each node's depth.
depth = lambda node: depth(node.parent) + 1 if node else 0
depth1 = depth(node1)
depth2 = depth(node2)
# Pulls up the lower node and makes the two nodes in the same depth.
mindepth = min(depth1, depth2)
for i in xrange(depth1 - mindepth): node1 = node1.parent
for i in xrange(depth2 - mindepth): node2 = node2.parent
# Finds the common ancestor.
while node1 and node2:
if node1 is node2: return node1
node1 = node1.parent
node2 = node2.parent
return None
2012年04月05日 14:50
时间复杂度O(h),设h是二叉树的深度。空间复杂度O(1)。
2012年04月05日 23:31
如果二叉树结点结构中没有父指针,那也可以不缓存指定结点的整条分支路径,而只需要在遍历查找的时候做点儿小小的改动。关于遍历二叉树可以参考后面的一篇文章:程序基本功之遍历二叉树。这里我将在非递归的前序(N-L-R)遍历基础上修改得到求LCA的程序。
为什么用前序遍历?
首先考察一下LCA的特性,只有两种可能:
对于第一种可能,前序遍历时首先找到的结点就是LCA,剩下的事情就是确定第二个结点在它下面。中序和后序也都可以做,但没有这么美妙。
对于第二种可能,假设在前序遍历过程中,首先找到了一个结点(比如下面的H),根据非递归前序遍历的算法特性,这时候栈里一定是依次存储了结点A(根节点)、B、D、G(请自行思考为什么没有C、E、F),再结合LCA的特性,很容易发现,LCA要么是H自身(对应于上面第一种情况),要么就只能是A、B、D或G。剩下的事情就太美妙,继续遍历二叉树,直到找到另外一个结点。这时候看看A、B、D、G和H中还有谁在栈里,最靠下的那个就是LCA。怎么判定谁在栈里?怎么判定最靠下?用辅助变量呗。
示意程序代码:
如果与非递归前序遍历的程序比较一下,就会发现改动其实非常小。(请直接忽略上面正文中的代码)
这段程序时间复杂度都是O(n),空间复杂度是O(h),这些都是遍历二叉树所需的时间和空间消耗。在遍历之外,就只剩下常数量的时空开销了。
原文地址:http://www.gocalf.com/blog/least-common-ancestor.html