【问题标题】:Algorithm for converting hierarchical flat data (w/ ParentID) into sorted flat list w/ indentation levels将分层平面数据(带 ParentID)转换为带缩进级别的排序平面列表的算法
【发布时间】:2010-05-20 00:11:12
【问题描述】:

我有以下结构:

MyClass {
  guid ID
  guid ParentID
  string Name
}

我想创建一个数组,其中包含元素按照它们应该在层次结构中显示的顺序(例如,根据它们的“左”值),以及一个将 guid 映射到缩进级别的哈希。

例如:

ID     Name     ParentID
------------------------
1      Cats     2
2      Animal   NULL
3      Tiger    1
4      Book     NULL
5      Airplane NULL

这基本上会产生以下对象:

// Array is an array of all the elements sorted by the way you would see them in a fully expanded tree
Array[0] = "Airplane"
Array[1] = "Animal"
Array[2] = "Cats"
Array[3] = "Tiger"
Array[4] = "Book"

// IndentationLevel is a hash of GUIDs to IndentationLevels.
IndentationLevel["1"] = 1
IndentationLevel["2"] = 0
IndentationLevel["3"] = 2
IndentationLevel["4"] = 0
IndentationLevel["5"] = 0

为了清楚起见,这是层次结构的样子:

Airplane
Animal
  Cats
    Tiger
Book

我想尽可能少地迭代项目。我也不想创建分层数据结构。我更喜欢使用数组、哈希、堆栈或队列。

两个目标是:

  1. 将 ID 的哈希存储到缩进级别。
  2. 根据所有对象的左值对包含所有对象的列表进行排序。

当我得到元素列表时,它们没有特定的顺序。兄弟姐妹应按其 Name 属性排序。

更新:这似乎是我自己没有尝试提出解决方案,只是希望其他人为我完成工作。但是,我尝试提出三种不同的解决方案,但我都陷入了困境。一个原因可能是我试图避免递归(可能是错误的)。我没有发布到目前为止的部分解决方案,因为它们不正确并且可能严重影响其他人的解决方案。

【问题讨论】:

  • 递归数据需要递归解决方案。它们是递归解决方案存在的唯一原因。
  • 并不总是需要递归,请参阅我的回答。

标签: algorithm language-agnostic hierarchical-data


【解决方案1】:

我需要一个类似的算法来对具有依赖关系的任务进行排序(每个任务可能有一个需要首先完成的父任务)。我找到了拓扑排序。这是一个iterative implementation in Python,里面有非常详细的cmets。

可以在进行拓扑排序时计算缩进级别。只需将节点的缩进级别设置为其父节点的缩进级别 + 1,因为它被添加到拓扑排序中。

请注意,可以存在许多有效的拓扑排序。为确保生成的拓扑顺序将父节点与子节点分组,请选择基于部分排序信息生成的图的深度优先遍历的拓扑排序算法。

维基百科给出two more algorithms for topological sort。请注意,这些算法不是那么好,因为第一个是广度优先遍历,而第二个是递归的。

【讨论】:

    【解决方案2】:

    对于分层结构,您几乎肯定需要递归(如果您允许任意深度)。我快速编写了一些 ruby​​ 代码来说明如何实现这一点(虽然我还没有完成缩进):

    # setup the data structure
    class S < Struct.new(:id, :name, :parent_id);end
    
    class HierarchySorter
    
        def initialize(il)
            @initial_list = il
            first_level = @initial_list.select{|a| a.parent_id == nil}.sort_by{|a| a.name }
            @final_array = subsort(first_level, 0)
        end
    
        #recursive function
        def subsort(list, indent_level)
            result = []
            list.each do |item|
                result << [item, indent_level]
                result += subsort(@initial_list.select{|a| a.parent_id == item.id}.sort_by{|a| a.name }, indent_level + 1)
            end
            result
        end
    
        def sorted_array
            @final_array.map &:first
        end
    
        def indent_hash
            # magick to transform array of structs into hash
            Hash[*@final_array.map{|a| [a.first.id, a.last]}.flatten]
        end
    
    end
    
    hs = HierarchySorter.new [S.new(1, "Cats", 2), S.new(2, "Animal", nil), S.new(3, "Tiger", 1), S.new(4, "Book", nil),
        S.new(5, "Airplane", nil)]
    
    puts "Array:"
    puts hs.sorted_array.inspect
    
    puts "\nIndentation hash:"
    puts hs.indent_hash.inspect
    

    如果你不会说红宝石,我可以用其他东西重新制作它。

    编辑:我更新了上面的代码以输出两种数据结构。

    输出:

    Array:
    [#<struct S id=5, name="Airplane", parent_id=nil>, #<struct S id=2, name="Animal", parent_id=nil>, #<struct S id=1, name="Cats", parent_id=2>, #<struct S id=3, name="Tiger", parent_id=1>, #<struct S id=4, name="Book", parent_id=nil>]
    
    Indentation hash:
    {5=>0, 1=>1, 2=>0, 3=>2, 4=>0}
    

    【讨论】:

    • 这正是我正在寻找的,因为我遇到了与 OP 类似的问题。但是,我注意到这仅适用于 ruby​​ 1.8.7 或更高版本,不适用于 ruby​​ 1.8.6(我仍然必须在某些机器上使用)。在 1.8.6 中,我得到一个带有 @final_array.map &amp;:first 的“错误的参数类型符号(预期的 Proc)”。您可以使用 backport gem 来解决这个问题。
    • 这是 Symbol#to_proc 的已知问题。请改用@final_array.map {|a| a.first }
    • 我也有同样的工作场景,如果可能的话,请您分享使用递归函数对层次结构数据进行排序的 JavaScript 代码
    【解决方案3】:

    Wonsungi 的帖子帮助很大,但这是针对通用图而不是树。所以我对其进行了相当多的修改,以创建一个专门为树设计的算法:

    // Data strcutures:
    nodeChildren: Dictionary['nodeID'] = List<Children>;
    indentLevel: Dictionary['nodeID'] = Integer;
    roots: Array of nodes;
    sorted: Array of nodes;
    nodes: all nodes
    
    // Step #1: Prepare the data structures for building the tree
    for each node in nodes
      if node.parentID == NULL
        roots.Append(node);
        indentLevel[node] = 0;
      else
        nodeChildren[node.parentID].append(node);
    
    // Step #2: Add elements to the sorted list
    roots.SortByABC();
    while roots.IsNotEmpty()
      root = roots.Remove(0);
      rootIndentLevel = indentLevel[root];
      sorted.Append(root);
      children = nodeChildren[root];
      children.SortByABC();
      for each child in children (loop backwards)
        indentLevel[child] = rootIndentLevel + 1
        roots.Prepend(child)
    

    【讨论】:

      猜你喜欢
      • 2010-12-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-26
      • 1970-01-01
      • 2022-11-19
      • 1970-01-01
      • 2019-04-04
      相关资源
      最近更新 更多