【问题标题】:Traversing a nested hierarchy of any depth from bottom - up自下而上遍历任意深度的嵌套层次结构
【发布时间】:2019-08-31 01:51:03
【问题描述】:

采用这样的嵌套递归 JSON sn-p 可以继续到任何深度:

{
   "Id": null,
   "Foos": [
      {
         "FooId": 1,
         "FooName": "ABC",
         "Foos": [
            {
               "FooId": 2,
               "FooName": "DEF",
               "Foos": null
            },
            {
               "FooId": 3,
               "FooName": "GHI",
               "Foos": [
                  {
                     "FooId": 4,
                     "FooName": "JKL",
                     "Foos": null
                  },
                  {
                     "FooId": 5,
                     "FooName": "MNO",
                     "Foos": [
                        {
                           "FooId": 6,
                           "FooName": "PQR",
                           "Foos": null
                        },
                        {
                           "FooId": 7,
                           "FooName": "STU",
                           "Foos": null
                        }
                     ]
                  }
               ]
            }
         ]
      }
   ]
}

使用 JSON.NET,我可以将其映射到这样的结构中:

public class Root {
    public string Id { get; set; }
    public List<Foo> Foos { get; set; }
}

public class Foo {
    public int FooId { get; set; }
    public string FooName { get; set; }
    public List<Foo> Foos { get; set; }
}

到目前为止一切都很好......但现在我需要从层次结构的底部向上工作(从 FooId=5 的孩子开始),然后再回到根目录。如何有效地解决这个问题?

【问题讨论】:

  • 反序列化后迭代一次树会不会不好?
  • 澄清一下,您希望按 6、7、5、4、3、2、1 的顺序迭代对象?
  • 如果我们有树A [ B [ C, D ] , E [ F, G ] ] 怎么办?你想要C、D、B、F、G、E、A吗?或者你想要 C、D、F、G、B、E、A 吗?第一个是后序遍历,第二个是级别遍历,它们的区别很大。从您的问题中不清楚您需要哪个。
  • 请阅读en.wikipedia.org/wiki/Tree_traversal并说清楚你想要哪种遍历。
  • 或者,你真的只想遍历FooId": 5 节点的父节点,类似于XElement.AncestorsAndSelfJToken.AncestorsAndSelf() 枚举给定节点的父节点的方式吗?例如。祖先是5, 3, 1, null

标签: c# .net json recursion


【解决方案1】:

从您的问题中不清楚您是想要后序(深度优先)遍历还是反向级别遍历(广度优先,反向)。假设你想要后序,算法很简单:

public static IEnumerable<T> Postorder<T>(
  this IEnumerable<T> nodes,
  Func<T, IEnumerable<T>> children)
{
  foreach(T node in nodes)
  {
    foreach(T descendant in children(node).Postorder(children))
      yield return descendant;
    yield return node;
  }
}

每个节点只有在其所有后代之后才会产生,所以这是一个后序遍历。

如果树很浅,这是相当有效的,但您说您希望解决“任何深度”树的问题。 这种方法只对深度可达几十级的树有效,因为它是 O(nd),其中 n 是节点总数,d 是平均深度;平均深度取决于分支因子,因此可能低至 1 或高至 n,这使其成为一种潜在的二次算法。

此外,由于它使用 O(dmax) 堆栈空间,其中 dmax 是最大深度,我们可以破坏调用堆栈。

因此:如果您有数百或数千个级别,请使用显式堆栈技术。

练习:重写我的算法以使用显式堆栈,而不是将调用堆栈用作隐式堆栈。

但你说你需要任意深度的树。如果树中有数十亿或数万亿个节点,数十亿或数万亿深怎么办?在这种情况下,您需要使用外部存储器解决方案,我建议您构建一个专门解决此问题的自定义存储系统;做一些大规模图数据库的研究,可以解决这类问题。

无论如何,既然您有了通用解决方案,那么您的具体解决方案就很简单了:

var ids = root.Foos
              .Postorder(f => f.Foos)
              .Select(f => f.FooId)
              .ToList();

或其他。

【讨论】:

  • 感谢您的详细回复,这有助于澄清一些事情。实际上,我可能不需要树中超过六个级别的深度,所以这应该可以正常工作。我总是可以将输入限制在合理的范围内
猜你喜欢
  • 1970-01-01
  • 2012-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-28
  • 1970-01-01
  • 1970-01-01
  • 2021-07-07
相关资源
最近更新 更多