我有多个二叉树存储为数组。在每个槽中都是nil(或null;选择你的语言)或存储两个数字的固定元组:两个“children”的索引。没有节点只有一个孩子 - 它不是一个或两个。
将每个插槽视为仅存储指向其子节点的二进制节点,并且没有固有的值。
采用这种二叉树系统:
0 1 / \ / \ 2 3 4 5 / \ / \ 6 7 8 9 / \ 10 11
关联的数组将是:
0 1 2 3 4 5 6 7 8 9 10 11
[ [2,3] , [4,5] , [6,7] , nil , nil , [8,9] , nil , [10,11] , nil , nil , nil , nil ]
我已经编写了简单的函数来查找节点的直接父节点(只需从前面搜索,直到有一个包含该节点的节点)
此外,让我们说在相关时间,所有树木都在几个到几千个深度之间。
我想找一个功能
P(m,n)
找到m和n的最低共同祖先 - 更正式地说,LCA被定义为“最低”或最深的节点,其中有m和n作为后代(儿童,或儿童的孩子等) )。如果没有,则nil将是有效的回报。
给出我们给定的树的一些例子:
P( 6,11) # => 2
P( 3,10) # => 0
P( 8, 6) # => nil
P( 2,11) # => 2
我能找到的主要方法是使用Euler跟踪的方法,该方法将给定的树(添加节点A作为0和1的不可见父项,“值”为-1)转换为:
A-0-2-6-2-7-10-7-11-7-2-0-3-0-A-1-4-1-5-8-5-9-5-1-A
然后,只需在给定的m和n之间找到编号最小的节点;例如,要查找P(6,11),请在轨迹上查找6和11。它们之间的最低数字是2,这就是你的答案。如果A(-1)介于它们之间,则返回nil。
-- Calculating P(6,11) --
A-0-2-6-2-7-10-7-11-7-2-0-3-0-A-1-4-1-5-8-5-9-5-1-A
^ ^ ^
| | |
m lowest n
不幸的是,我确实认为找到一棵树的Euler痕迹可能数千级别的深度有点机器负担......并且因为我的树在整个过程中不断变化编程过程中,每次我想找到LCA时,我都必须重新计算Euler跟踪并且每次都将它保存在内存中。
考虑到我正在使用的框架,是否有更高效的内存方式?一个可能向上迭代?我能想到的一种方法是“计算”两个节点的生成/深度,并攀爬最低节点,直到它与最高节点的深度相匹配,然后增加两个节点直到找到相似的人。
但这涉及从3025级别上升到0级,两次,以计算代数,并首先使用非常低效的攀爬算法,以及然后重新爬上去。
还有其他更好的方法吗?
在建立这个系统的过程中,每个孩子的数字都会超过他们的父母。
不保证如果n在X代中,则生成(X-1)中没有大于n的节点。例如:
0 / \ / \ / \ 1 2 6 / \ / \ / \ 2 3 9 10 7 8 / \ / \ 4 5 11 12
是一个有效的树系统。
此外,树木构建方式的工件是同一父母的两个直系子女将始终连续编号。
答案 0 :(得分:1)
在您的示例中,节点是否按顺序排列,其中子节点的ID大于父节点?如果是这样,您可以执行类似于合并排序的操作来查找它们。例如,6和11的父树是:
6 -> 2 -> 0
11 -> 7 -> 2 -> 0
所以算法可能是:
left = left_start
right = right_start
while left > 0 and right > 0
if left = right
return left
else if left > right
left = parent(left)
else
right = parent(right)
哪个会运行:
left right
---- -----
6 11 (right -> 7)
6 7 (right -> 2)
6 2 (left -> 2)
2 2 (return 2)
这是对的吗?
答案 1 :(得分:1)
也许这会有所帮助:Dynamic LCA Queries on Trees。
摘要:
Richard Cole,Ramesh Hariharan
我们将展示如何维护数据 允许的树木结构 全部进行以下操作 最坏情况下的恒定时间。 1.插入 叶子和内部节点。 2。 删除叶子。 3.删除 只有一个孩子的内部节点。 4。 确定最不共同的祖先 任何两个节点。
会议:离散研讨会 算法 - SODA 1999
答案 2 :(得分:0)
我已经在Haskell中解决了你的问题。假设你知道森林的根,解决方案需要时间线性的森林大小和不断的额外内存。您可以在http://pastebin.com/ha4gqU0n找到完整的代码。
解决方案是递归的,主要的想法是你可以在一个子树上调用一个函数,它返回四个结果之一:
m
也不包含n
。m
但不包含n
。n
但不包含m
。m
和n
,其最不常见的祖先的索引为k
。没有子项的节点可能包含m
,n
或两者都不包含,您只需返回相应的结果。
如果索引为k
的节点有两个子节点,则按如下方式组合结果:
join :: Int -> Result -> Result -> Result
join _ (HasBoth k) _ = HasBoth k
join _ _ (HasBoth k) = HasBoth k
join _ HasNeither r = r
join _ r HasNeither = r
join k HasLeft HasRight = HasBoth k
join k HasRight HasLeft = HasBoth k
计算此结果后,您必须检查节点本身的索引k
;如果k
等于m
或n
,您将“扩展”join
操作的结果。
我的代码使用代数数据类型,但我一直小心地假设您只需要以下操作:
由于您的问题与语言无关,我希望您能够调整我的解决方案。
您可以进行各种性能调整。例如,如果您发现根目录中只有两个节点m
和n
中的一个,则可以立即退出,因为您知道没有共同的祖先。此外,如果您查看一个子树并且它具有共同的祖先,您可以忽略另一个子树(我使用延迟评估免费获得的子树)。
您的问题主要是关于如何节省内存。如果线性时间解决方案太慢,您可能需要辅助数据结构。时空权衡是我们生存的祸根。
答案 3 :(得分:0)
我认为你可以简单地向后循环遍历数组,总是用它的父替换两个索引中的较高者,直到它们相等或没有找到更多父代:
(defun lowest-common-ancestor (array node-index-1 node-index-2)
(cond ((or (null node-index-1)
(null node-index-2))
nil)
((= node-index-1 node-index-2)
node-index-1)
((< node-index-1 node-index-2)
(lowest-common-ancestor array
node-index-1
(find-parent array node-index-2)))
(t
(lowest-common-ancestor array
(find-parent array node-index-1)
node-index-2))))