【问题标题】:Sort objects by timestamp, but then group dependencies按时间戳对对象进行排序,然后对依赖项进行分组
【发布时间】:2017-03-26 03:50:44
【问题描述】:

假设我有一个对象列表(数组)。这些对象中的每一个都有两个属性:时间戳和可选的父对象,它可以为空。我想先按时间戳对这个数组进行排序,这很容易;但是,我希望依赖对象保持连续。

例如,考虑这个简化的示例:三个对象 A、B 和 C。B 的父对象是 A,但时间戳是 A=1、B=3、C=2。按时间戳排序给出[A,C,B],但是因为B的父母是A,所以我希望B在A之后;所以理想的结果毕竟应该是[A, B, C]。

注意,如果两个或多个对象具有相同的父对象,它们应该都是相邻的,但它们仍然应该按时间戳相对排序。

最好的方法是什么?我能想到的唯一方法是按时间戳排序,然后遍历数组,对于每个依赖对象,将其移到其父对象之后;但这似乎效率低下,因为它需要额外的一轮迭代。有没有办法将分组合并到初始排序中,这样它就可以只完成一轮排序? (我目前正在使用 QuickSort,但如果需要,我可以切换到另一种算法。)

【问题讨论】:

  • 旁注:我认为在排序后重新排序节点不会超过排序的复杂性(如果你小心的话),所以可能没问题。

标签: arrays algorithm sorting grouping


【解决方案1】:

蛮力非工作方法 - 一种在单个操作中执行排序的选项,您需要按以下方式将父级作为排序键的一部分,而不是按 {order(Node.Parent), timestamp(Node)} 排序使用您喜欢的任何算法进行配对。

  • "A 是 B 的父级" => "order(A)
  • "C.timestamp order(C)

不幸的是,这个“排序”功能需要首先对所有子节点进行排序以满足第二个条件,从而打破了“单一排序”的要求。


要获得单一排序,您可以使用包含所有父节点时间戳的复合键,然后按此类复合键排序。

构建复合键的最简单方法是基于父对象构造树,并将键的值设置为使用任何tree traversal 连接父键和自己的时间戳。

示例:

  • 数据

    A (ts = 5) parent of B (ts = 7),C (ts = 2)
    B parent of D (ts = 3)
    
  • 建筑树:

    A -> B -> D  
      -> C
    
  • 预购遍历:A、B、D、C

  • 复合键 -

    A -> A.timestamp = 5 B -> key(A) concat B.timestamp = 5.7 C -> key(A) concat C.timestamp = 5.2 D -> key(B) concat D.timestamp = 5.7.2

  • 用于按 {order, timestamp} 对排序的数据

     A {order(no-parent), ts} = {0, 5}
     B {order(A), ts} = {1,7}
     C {1,2}
     D {2,3}
    
  • 排序后的序列 - {5}、{5.2}、{5.7}、{5.7.2} 映射回节点 - A、C、B、D

这种方法的复杂度是 O(n log(n) max_depth):

  • 构建树/遍历树/构建键 - O(n)
  • 排序是排序的复杂度(通常是 O(num_elm log(num_elem))乘以比较键的复杂度,这取决于父子树的深度。这部分主要是准备阶段所需的 O(n)。

或者,您可以只构建树,按时间戳对每个级别进行排序,然后通过 pre-order traversal 将它们放回列表中,这消除了键比较的复杂性,但打破了单一排序的要求。

【讨论】:

  • 只要复杂性不增加(或至少不增加太多),我愿意做两步。但是有一个问题:在您构建树的最终建议中,对每个级别进行排序,然后再次列出树节点,我如何对每个级别进行比 O(n log n * max_depth) 更好的排序?我想树的每一层都需要独立排序,每层都需要 O(n log n),对吧?
  • @IceMetalPunk 是的,每个节点的子节点都需要单独排序。所有类型的复杂性将是 O(n log n),因为要排序的最大节点数是 n(单个节点只能有一个父节点 - 所以你永远不会对同一个节点进行两次排序,因为它们都按父节点在单独的组中)。构建(使用哈希表查找父对象)并同时走 O(n),因此总体 O(n log n)。
【解决方案2】:

您可以使用一个或两个数字的序列作为排序键将对象按字典顺序排序,其中如果一个对象没有父级,则它在序列中只有一个元素,即它的编号,如果一个对象有一个父级序列中的第一个元素是它的父编号,第二个是它自己的编号。

所以 A、B 和 C 得到序列 {1}、{1、3} 和 {2},而 B 在其父序列之后排序。

【讨论】:

  • 旁注:当只有一级子节点时,非常适合帖子中的示例。如果有多个级别的父母,您需要先构建树,并且密钥的长度将是可变的(我的answer 中的步骤)。
猜你喜欢
  • 2019-01-16
  • 2011-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-30
  • 2020-01-01
  • 1970-01-01
相关资源
最近更新 更多