如何以增量方式构建非二叉树(具有依赖关系)

Posted

技术标签:

【中文标题】如何以增量方式构建非二叉树(具有依赖关系)【英文标题】:How to build a non-binary tree (with dependencies) incrementally 【发布时间】:2016-01-08 15:15:03 【问题描述】:

简短说明

我需要从确实相互依赖但不重复且不循环的项目列表中构建一个非二叉树(语言现在无关紧要,但最好使用 C++)。

节点的数据从文件中读取并增量插入到树中。 麻烦的部分是如何处理那些没有父节点但满足插入节点依赖关系的节点。

详细说明

粗略的轮廓

分配很简单:在非二叉树中表示一堆任务和子任务。 这个分配很容易理解和实现,如果不是一个小条件:任务列表必须增量生成,树中的节点也是如此。

场景

任务是异步生成的,一旦收到某个任务的数据,就必须将其添加到树中。 这是通过读取一个 csv 文件来“模拟”的,该文件的每一行都有一个特定的任务,其中包含一些数据,最重要的是 PID 和 PPID 属性。

读取并解析一行后,将创建一个任务并将其插入树中。 树应该按照两个简单的规则自动解决依赖关系:

    仅在满足依赖关系时(即之前插入父节点时)显示节点,但记住(现在孤立的)节点。 每当添加一个任务(节点)时,检查它是否是上述孤立节点之一的父节点,如果在这样做时没有违反规则 #1,则协调节点。

请忽略此场景背后的错误逻辑:通常情况下,没有 ParentTask 就不可能有任何 SubTask(至少在单片内核设计中)。 虽然任务列表确实包含建模树所需的 ParentTasks,但不知道 何时 ParentNode-Data 被读取并插入到树中。

期望的结果

下图显示了“原始数据”,即(未排序的)任务列表,该列表是在将一个又一个任务添加到列表中时增量创建的。 树表示到目前为止已插入的任务子集:

请记住,在插入 PID 为 1、2 和 3 的任务之前,树是完全“裸露的”,因为其他节点都依赖于它们。

到目前为止我做了什么

我编写了一个包含三个粗略组件的 Qt-C++ 代码:

包含根节点(没有任何任务数据的节点)的任务树 TaskNode 有一个字段来保存任务数据和一个 QList,简单来说,就是一个引用子节点的 TaskNodes 向量 任务具有相关属性(如 pid 和 ppid)

如果父节点已经存在,插入TaskNode是没有问题的。 这只适用于完美世界,其中任务根据各自的依赖关系进行排序,并且要添加确定数量的任务。

我不必告诉您这种情况极不可能发生,因此树的创建必须记住任何孤立节点(这是一个还没有父节点的节点,呵呵)。

我以不同的方式解决了这种“记忆”问题,但都失败了,因为我无法理解它背后的算法。 我有两个最有希望的想法是:

    将每个孤立节点插入向量中。插入父节点后,检查它是否在 Orphan-Vector 中有子节点并进行协调。对新创建的子树递归执行此操作以匹配所有可能性。 将 PPID 分配给树的根节点,0 表示最顶端的节点。当出现孤立节点时,创建一个新的TaskTree,将孤立节点的PPID分配给新创建的树并将孤立节点添加到其中。 这会创建子树,如果有多个孤儿匹配其中一棵树,则可以自行退出复杂的子树。在每个插入节点之后,尝试将子树与根树协调。

不幸的是,由于递归等原因出现了几个自发的 SIGSEGV 和其他问题,我不得不放弃继续这两个概念。


所以最后我在这里试图找到一种方法来实际完成这项工作,而不会通过假设和其他作弊来降低问题的复杂性......

你们知道我可以使用哪种算法来解决这个问题,或者这到底是什么类别的问题?

【问题讨论】:

我不确定 Stack Overflow 是否适合回答这个问题。 Computer Science.SE 或 Programmers.SE 上的运气可能会更好 您可以尝试将所有孤立任务作为根节点的子节点。然后,当添加新节点时,您检查作为根的子节点的所有任务,并将新添加节点的子节点移动到现在添加的父节点。 我还建议您发布另一个问题,重点围绕您遇到的特定错误以及如何修复它们。 听起来您的第一个解决方案是一个正确的解决方案,而您的代码中的某个地方出现了问题。将孤立节点暂时插入到一个存储空间中,然后在找到它们的父节点时将它们从那里移动是一种完全有效的方法。有一些替代方案可能更有效,但这是完全可用的。 @NathanOliver 在引用其他网站时,指出cross-posting is frowned upon 通常会有所帮助 【参考方案1】:

方法 2 是正确的选择。您缺少的部分是您需要一个名为node_neededunordered_map,它将尚未看到的父节点映射到正在等待它的子树的vector。您需要一个类似的映射 node_seen 到已看到的节点的关联树。

然后,当您看到一个节点时,您执行以下操作:

Create TaskTree with only this node.
Add this TaskTree to the node_seen map

If this node's ID is in the parent_needed map:
    Add each tree in the parent_needed map to this tree
    Remove this node's ID from the parent_needed map

If this node has no parent:
    Add this node's tree to the root tree
Else if this node's parent ID is in the node_seen map:
    Add this node's tree to the parent tree
Else if this node's parent_ID is in the parent_needed map:
    Append this node's tree to the parent_needed vector
Else:
    Create a vector containing this node's tree
    Add a mapping from this node's parent ID to that vector in the parent_needed map

假设没有错误(哈哈!错误是生活的一部分......),这应该可以工作。

【讨论】:

这个方法看起来可行,我会尝试实现它,如果它真的有效会报告,谢谢:)【参考方案2】:

经过一些深思熟虑的设计更改,我想出了 - 我认为 - 实现这一点的最简单方法:

InsertTask(Task newTask)

    Task parentTask = searchTreeForParent(newTask->ppid)
    If (parentTask not found)
    
        parentTask = treeRootNode;
    

    If (treeRootNode has children)
    
        For (every children in treeRootNode: child)
        
            If (child->ppid != treeRootNode->pid AND child->ppid == newTask->pid)
            
                newTask->addChild(child)
                treeRootNode->remove(child)
            
        
    

    parentTask->addChild(newTask)

其背后的算法非常简单:如果还没有父节点,则将新任务添加到根节点,同时检查新添加的任务是否在根节点中有潜在的子节点(因为那些孤立的之前被添加到根节点)。

因此,如果您实际插入所有任务来满足依赖关系,您最终会得到一个完整且有效的树。 如果你不提供所有的父节点,你最终会得到一些完整且有效的分支,以及根节点中的一堆孤立分支。 但这没问题,因为有一个简单的技巧可以区分完整分支和孤立分支:只需检查 ppid 是否等于根节点的 pid,瞧,您只输出那些完整的分支。

【讨论】:

以上是关于如何以增量方式构建非二叉树(具有依赖关系)的主要内容,如果未能解决你的问题,请参考以下文章

如何“构建”具有依赖关系的 python 脚本

非二叉树的中序树遍历

Gradle:如何在java中以编程方式声明对项目特定配置的依赖关系

如何使用具有依赖关系的gradle构建jar文件

如何在非二叉树中找到最长路径并将其返回到向量中?

如何基于 preorder&inorder 或 postorder&inorder 遍历构造非二叉树?