【问题标题】:C# Data structure to store items in a hierarchical way that once a branch is built, it allows me to retrieve it and add it as part of another oneC# 数据结构以分层方式存储项目,一旦构建了一个分支,它允许我检索它并将其添加为另一个分支的一部分
【发布时间】:2016-04-13 04:14:52
【问题描述】:

我正在努力解决一个问题,我必须找到一种方法来存储程序可能遵循的所有可能路径。 以这张图片为例。

在该图像中,每个数字代表一个复杂的进程,它可能会调用其他进程来执行自身,描述了您在图像中可以看到的所有路径。

所有实线表示过程必须遵循的路径,而虚线表示可选路径。

知道执行是从左到右和从上到下开始的,必须始终牢记,如果已经构建了一个分支,则它必须被重用,并且永远不会再次构建。

在另一个图像中,例如,黄线代表在执行 37 号进程期间遵循的所有路径。

在其中您可以看到从进程 18 (18->17->16) 开始的路径是先前构建的,因此当到达进程 19 时不应重建它,因为所有这些进程都需要相当长的时间并且在已经知道它们产生的结果的情况下尝试再次构建它们是浪费时间。相反,如果发现之前已经构建了某个数量(例如进程 18),则应将其复制/附加到调用它的进程(图像中的进程 19)。所有这些都是为了一个日志,我必须在其中存储所有完整的路径,这就是为什么我提到复制/重用分支的部分,因为我稍后必须查询该日志以显示所有这些路径。

为了执行所有这些过程,目前使用了递归过程,但由于它不考虑可以重用之前构建的路径,所以整个过程需要很长时间。

你知道任何可以帮助我优化这个过程的数据结构,以便如果一个过程已经执行,它只会被重用。至于日志,如上所述,我需要存储完整的路径。

任何想法或资源都将受到高度赞赏。

谢谢

编辑 1

---------------

我可能没有说得很清楚的一件事是,我需要创建的数据结构有两个目的:

  1. 跟踪主进程(示例中为 37 个)在执行期间遵循的所有路径,让我有机会随时判断某个路径是否已经存在跟随,然后能够将该路径复制到应该调用它的节点(在示例中,复制整个分支:18->17->16 到进程 19。
  2. 通过让我有机会判断路径是否已在此数据结构中,我可以避免执行已执行的子流程以及已知的结果,从而优化整个执行流程。李>

编辑 2

---------------

关于我为什么不考虑使用Dictionary 的问题,起初我有这个想法,但后来我找不到字典可以告诉我的方法,例如,路径开始有 18 (18->17->16) 来自进程 37 和 19。你看,一个节点可以有一个或多个父节点。我怎么能用字典来表达呢?

【问题讨论】:

  • 这不是一个带有主键的数据库表吗?
  • 你没有指定,为什么你不能在HashMap(字典)中保存结果...?
  • 我会使用一棵树(进程 n°37 为根)并在插入新节点(即进程 n°18)时快速检查它是否已经在我的树中正在建设中……
  • @PieterGeerkens 因为所有这些进程都使用编译器(在 C# 中)来评估表达式,并且只有当我找到一个数字时,我才需要查询我的数据库以检索执行由该数字标识的进程所需的数据,否则不需要查询db。
  • Tree data structure in C#的可能重复

标签: c# algorithm recursion data-structures


【解决方案1】:

我相信这是您正在寻找的数据结构:

var paths = new Dictionary<int, HashSet<int>>()
{
    { 37, new HashSet<int>() { 18, 33, 34, 35, 36, } },
    { 18, new HashSet<int>() { 17, } },
    { 33, new HashSet<int>() { } },
    { 34, new HashSet<int>() { 19, 17, 15, } },
    { 35, new HashSet<int>() { 17, } },
    { 36, new HashSet<int>() { } },
    { 17, new HashSet<int>() { 16, } },
    { 19, new HashSet<int>() { 12, 18, } },
    { 15, new HashSet<int>() { 14, } },
    { 16, new HashSet<int>() { } },
    { 12, new HashSet<int>() { 11, } },
    { 14, new HashSet<int>() { } },
    { 11, new HashSet<int>() { } },
};

下面是添加路径的代码:

public bool TryAddPath(Dictionary<int, HashSet<int>> paths, int x, int y)
{
    if (!paths.ContainsKey(x))
    {
        paths[x] = new HashSet<int>() { };
    }

    if (!paths[x].Contains(y))
    {
        paths[x].Add(y);
        if (!paths.ContainsKey(y))
        {
            paths[y] = new HashSet<int>() { };
        }
        return true;
    }
    return false;
}

上面的数据结构可以通过以下方式构建:

var paths = new Dictionary<int, HashSet<int>>();
var results = new bool[]
{
    TryAddPath(paths, 37, 18),
    TryAddPath(paths, 37, 33),
    TryAddPath(paths, 37, 34),
    TryAddPath(paths, 37, 35),
    TryAddPath(paths, 37, 36),
    TryAddPath(paths, 18, 17),
    TryAddPath(paths, 17, 16),
    TryAddPath(paths, 34, 19),
    TryAddPath(paths, 34, 17),
    TryAddPath(paths, 34, 15),
    TryAddPath(paths, 19, 12),
    TryAddPath(paths, 19, 18),
    TryAddPath(paths, 12, 11),
    TryAddPath(paths, 18, 17),
    TryAddPath(paths, 17, 16),
    TryAddPath(paths, 17, 16),
    TryAddPath(paths, 15, 14),
    TryAddPath(paths, 35, 17),
    TryAddPath(paths, 17, 16),
};

这将返回数组{ true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, true, true, false, },其中显示了不需要处理的路径。

要获得一种方法来回溯列表,请执行以下操作:

ILookup<int?, int> parents =
    paths
        .Keys
        .AsEnumerable()
        .SelectMany(
            k => paths[k].Select(x => (int?)x).DefaultIfEmpty(),
            (k, v) => new { k, v })
        .ToLookup(x => x.v, x => x.k);

现在我可以询问parents[17],我会得到{ 18, 34, 35, } 的回复。我什至可以执行parents[null] 并返回{ 33, 36, 16, 14, 11, },它显示了叶子节点。

【讨论】:

  • 谜题谢谢。你认为你可以看看我的问题中的 Edit 2 吗?再次感谢您。非常感谢您花时间帮助我。
  • @eddy - 我已经编辑了我的答案以展示如何为父母遍历。
  • 如果我没记错的话,您可以采取另一种方法来解决问题。您无需尝试创建父子关系,而是在两个节点之间存储所有可能的connetions,然后查询所有这些连接以检索遵循的实际路径。对吗?
  • @eddy - 好吧,您可能会将其视为父子关系,但它实际上只是一个 n-ary 树。然后我只是通过查询我的 paths 对象来创建一个反向树(即ILookup&lt;int?, int&gt; parents)以返回树。
  • Enigmativity,我真的很喜欢您的解决方案,因为据我所知,不需要将元素从一个节点复制到另一个节点。相反,如果一个进程与另一个进程相关,则只需添加由该进程组成的另一个项目以及与之相关的项目。
【解决方案2】:

详细说明我在这里简单地使用列表的评论是我的意思(部分):

void Main()
{
  var data = new Dictionary<int, List<int[]>>();

  data.Add(16, new List<int[]> {
    new int[]{ 37, 18, 17, 16 },
    new int[]{ 37, 34, 19, 18, 17, 16 },
    new int[]{ 37, 34, 17, 16 },
    new int[]{ 37, 35, 17, 16 }});

  data.Add(17, new List<int[]> {
    new int[]{ 37, 18, 17 },
    new int[]{ 37, 34, 17 },
    new int[]{ 37, 35, 17 }});


  data.Add(18, new List<int[]> {
    new int[]{ 37, 18 },
    new int[]{ 37, 34, 19, 18 } });

  var node35_IsAncestorOf = data
    .Where(d => d.Value.Any(v => v.Contains(35)))
    .Select( d => d.Key);   

  node35_IsAncestorOf.Dump();    // LinqPad
}

此外,此结构类似于目录结构,其中 16、17 等是 NodeText 而不是 NodeId,因此树结构可以工作(就像文件名可以在不同目录下存在同名)。

PS:在数据库级别,这看起来像 Neo4j、OrientDb、VelocityGraph 之类的图形数据库……如果您想检查它们的数据结构,它们中的大多数都是开源的。

【讨论】:

    【解决方案3】:

    使用两种数据结构。似乎您已经在使用遍历的树来查找需要处理的下一个节点;没关系,保留那棵树。但也可以创建一个新的数据结构,例如 Dictionary 来跟踪已处理的节点,让您可以通过其 ID 快速查找节点并查看它之前的计算结果。

    【讨论】:

    • 请参阅我的问题中的 edit。它提供了我所追求的更多细节。谢谢
    • @eddy 在阅读编辑后,我支持答案。您想要一棵树(显然一开始是不完整的?),并单独查找已计算的结果。
    • Servy,目前我没有使用树来查找下一个要处理的节点。您会看到,每个进程都由几个表达式(例如If ([AGE]&gt;20) then PRO#18#RET1)组成,您可以在其中找到对另一个由其编号(18)标识的进程的引用,然后是将要使用的该进程的结果(RET1) .只有当我发现这些表达式时,我才知道我必须调用另一个进程。
    • @eddy 听起来你在功能上有一棵树,虽然你实际上没有展示你实际在做什么,我当然不能确定。如果你正在做一些完全不同的事情,那么你需要在你的问题中解释这一点。但同样,您似乎只需要添加一个字典,将 id 映射到它们的计算结果。
    • 您认为可以创建一棵树并通过引用我用来执行流程的递归方法来传递它吗?然后当递归方法完成时,我得到了所有节点的树,这些节点准确地描述了所有遵循的路径。当然,在执行过程中,我必须能够查询树并将分支从一个节点复制到另一个节点,例如需要从节点 37 复制到的分支 18-&gt;17-&gt;16节点 19。我能用一棵树做所有这些吗?
    猜你喜欢
    • 2018-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-18
    相关资源
    最近更新 更多