tem*_*def 45 algorithm tree graph family-tree
我正在努力为一个介绍级别的CS课程设置一个问题集,并提出一个问题,从表面上看,似乎很简单:
您将获得一份包含父母姓名,出生日期和死亡日期的人员名单.你有兴趣找出谁在他们一生中的某个时刻是父母,祖父母,曾祖父母等等.设计一个算法,用这个信息作为一个整数来标记每个人(0表示这个人从来没有过孩子,1表示该人是父母,2表示该人是祖父母,等等.)
为简单起见,您可以假设族图是DAG,其无向版本是树.
这里有趣的挑战是你不能只看树的形状来确定这些信息.例如,我有8位曾祖父母,但由于我们出生时没有一个人活着,在他们的一生中,他们中没有一个是伟大的曾祖父母.
我能解决这个问题的最佳算法是在时间O(n 2)中运行,其中n是人数.这个想法很简单 - 从每个人开始一个DFS,找到在该人死亡日期之前出生的家谱中最远的后代.但是,我很确定这不是问题的最佳解决方案.例如,如果图形只是两个父母及其n个孩子,那么问题可以在O(n)中平凡地解决.我希望的是一些算法要么胜过O(n 2),要么运行时参数化在图形的形状上,这使得它对于宽图形来说很快,在最坏的情况下优雅地降低到O(n 2)案件.
bti*_*lly 11
更新:这不是我提出的最佳解决方案,但我已经离开了,因为有很多与之相关的评论.
你有一系列事件(出生/死亡),父母状态(没有后代,父母,祖父母等)和生活状态(活着,死亡).
我会将我的数据存储在具有以下字段的结构中:
mother
father
generations
is_alive
may_have_living_ancestor
Run Code Online (Sandbox Code Playgroud)
按日期对事件进行排序,然后对于每个事件,请执行以下两个逻辑过程之一:
Birth:
Create new person with a mother, father, 0 generations, who is alive and may
have a living ancestor.
For each parent:
If generations increased, then recursively increase generations for
all living ancestors whose generations increased. While doing that,
set the may_have_living_ancestor flag to false for anyone for whom it is
discovered that they have no living ancestors. (You only iterate into
a person's ancestors if you increased their generations, and if they
still could have living ancestors.)
Death:
Emit the person's name and generations.
Set their is_alive flag to false.
Run Code Online (Sandbox Code Playgroud)
最糟糕的情况是,O(n*n)如果每个人都有很多活着的祖先.然而,一般来说,你已经有了排序预处理步骤,O(n log(n))然后你就O(n * avg no of living ancestors)意味着总时间往往O(n log(n))在大多数人群中.(由于@Alexey Kukanov的修正,我没有正确计算排序前提.)
今天早上我想到了这一点,然后发现@Alexey Kukanov有类似的想法.但我的更充实,并有更多的优化,所以我会发布它无论如何.
该算法O(n * (1 + generations))适用于任何数据集.对于现实数据,这是O(n).
O(n)初始化此数据的总计工作.O(1)计算你有0代的时间.然后,对于每个孩子的递归调用,您需要做的O(generations)工作是将该子项的数据合并到您的子项中.当您在数据结构中遇到它们时,每个人都会被调用,并且可以从每个父项调用一次以进行O(n)调用和总费用O(n * (generations + 1)).O(n * (generations + 1))如果使用线性扫描实现,则再次这样做.所有这些操作的总和是O(n * (generations + 1)).
对于实际数据集,这将是O(n)一个相当小的常量.
我的建议:
O(N).descendant_birthday[0]为这两个父母添加这个人的生日(必要时增长该向量).如果已设置此字段,则仅在新日期较早时更改此字段.descendant_birthday[i]当前记录的向量中可用的所有日期,请遵循上述相同的规则以更新descendant_birthday[i+1]父记录.O(C*N),C是给定输入的"族深度"的最大值(即最长descendant_birthday向量的大小).对于实际数据,它可以被一些合理的常数限制而没有正确性损失(正如其他已经指出的那样),因此不依赖于N.i的这descendant_birthday[i]仍然是早于死亡日期; 也O(C*N).因此,对于实际数据,可以在线性时间内找到问题的解决方案.虽然对于@ btilly评论中提出的人为设计数据,C可能很大,甚至在退化情况下也可能是N的顺序.它可以通过在矢量大小上设置上限或通过@btilly解决方案的第2步扩展算法来解决.
如果输入数据中的父子关系是通过名称提供的(如问题陈述中所述),则哈希表是解决方案的关键部分.如果没有哈希,则需要O(N log N)构建关系图.大多数其他建议的解决方案似乎假设关系图已经存在.