计算最小操作以使两个树结构相同

Posted

技术标签:

【中文标题】计算最小操作以使两个树结构相同【英文标题】:Calculate minimal operations to make two tree structures identical 【发布时间】:2011-08-19 04:31:31 【问题描述】:

这更像是一个 CS 问题,但也是一个有趣的问题:

假设我们有 2 个树结构,它们或多或少地重新组织了相同的节点。你会怎么找到

    任何 在某种意义上最小

操作顺序

MOVE(A, B) - 将节点 A 移动到节点 B 下(连同整个子树) INSERT(N, B) - 在节点 B 下插入一个 new 节点 N DELETE (A) - 删除节点 A(连同整个子树)

将一棵树转换为另一棵树。

显然,在某些情况下,这种转换是不可能的,例如根 A 与子 B 到根 B 与子 A 等)。在这种情况下,算法会简单地提供一个结果“不可能”。

更壮观的版本是网络的泛化,即当我们假设一个节点可以在树中多次出现(实际上有多个“父母”),而循环是被禁止的。

免责声明:这不是家庭作业,实际上它来自一个真正的业务问题,我发现想知道是否有人可能知道解决方案非常有趣。

【问题讨论】:

MOVE(A,B) 似乎与 INSERT(A,B) 相同,如果 A 没有任何孩子。如果INSERT(A,B) 的孩子们会发生什么A ? (它们会被附加到A 的父级吗?) 不同之处在于,INSERT 真正意味着一个新节点,以前不在树中(因此没有任何子节点,至少在它甚至不存在的原始状态下)。另一方面,MOVE 确实是一个移动,即节点的移动,包括它的子节点 这听起来你需要检测graph-isomorphism。关于转换的部分让我想起了Levenshtein distance,它可以使用动态规划在 O(n*m) 内巧妙地解决。也许这些指针对你有帮助。 您有没有想出解决方案?查看***文章和链接的参考资料,我在任何地方都看不到算法。我想在 javascript 中执行此操作,我已经知道使两棵树不同的原始操作,但想产生一个可选的差异:例如,如果树的一部分被修剪然后重新移植到同一个位置它会优化到没有变化。 @Michael,你找到有用的东西了吗?我正在关注减少树的相同变化。 【参考方案1】:

不仅有一篇关于图同构的 Wikipedia 文章(正如 Space_C0wb0y 指出的那样),还有一篇关于 graph isomorphism problem 的专门文章。它有一个部分Solved special cases,其中已知多项式时间解决方案。树就是其中之一,它引用了以下两个参考:

P.J. Kelly, "A congruence theorem for trees" Pacific J. Math., 7 (1957) pp. 961–968 啊,阿尔弗雷德五世;霍普克罗夫特,约翰; Ullman, Jeffrey D.(1974 年),计算机算法的设计和分析,马萨诸塞州雷丁:Addison-Wesley。

【讨论】:

【参考方案2】:

您不清楚您是在比较源代码的抽象语法树、解释为树的 XML 文档还是其他类型的树。

有许多论文讨论了比较语法树和通过各种方式计算最小距离。这些想法应该是相关的。

一篇好论文是Change Distilling,它试图比较两个抽象语法树的源代码并报告一个最小的差异。该论文讨论了一种具体的方法,并简要提及(并提供了参考)各种类似的技术。

在用于比较计算机程序源文本的可用工具中实际实现的这些算法很少。我们的Smart Differencer就是其中之一;它由许多语言的显式语言语法驱动。

【讨论】:

实际上,在我们的例子中,它不是源代码,它们是真正的树。这些树中有一些语义,但总的来说并不那么重要 - 它们由用户直接操作作为一棵树 链接断开:我只花了 20 分钟寻找“Change Distilling”论文。这是更新的链接:merlin.uzh.ch/publication/show/2531 软件项目本身已移至bitbucket.org/sealuzh/tools-changedistiller/wiki/Home(这是我获得 PDF 的正确链接的方式)【参考方案3】:

如果人们发现这个问题并需要为 Node.js 或浏览器实现某些东西,我将提供我编写的实现的链接和代码示例,您可以在 github 上找到:(https://github.com/hoonto/jqgram.git)基于现有的 PyGram Python 代码 (https://github.com/Sycondaman/PyGram)。

这是一种树编辑距离近似算法,但它比试图找到真正的编辑距离要快得多。近似在 O(n log n) 时间和 O(n) 空间中执行,而真正的编辑距离通常是 O(n^3) 或 O(n^2),使用已知的算法来计算真实的编辑距离。参见PQ-Gram算法出处的学术论文:(http://www.vldb2005.org/program/paper/wed/p301-augsten.pdf)

所以使用jqgram:

例子:

var jq = require("jqgram").jqgram;
var root1 = 
    "thelabel": "a",
    "thekids": [
         "thelabel": "b",
        "thekids": [
             "thelabel": "c" ,
             "thelabel": "d" 
        ],
         "thelabel": "e" ,
         "thelabel": "f" 
    ]


var root2 = 
    "name": "a",
    "kiddos": [
         "name": "b",
        "kiddos": [
             "name": "c" ,
             "name": "d" ,
             "name": "y" 
        ],
         "name": "e" ,
         "name": "x" 
    ]


jq.distance(
    root: root1,
    lfn: function(node) return node.thelabel; ,
    cfn: function(node) return node.thekids; 
,
    root: root2,
    lfn: function(node) return node.name; ,
    cfn: function(node) return node.kiddos; 
, p:2, q:3 ,
function(result) 
    console.log(result.distance);
);

这给了你一个介于 0 和 1 之间的数字。越接近零,两棵树看起来与 jqgram 的关系越密切。一种方法是使用 jqgram 从许多树中缩小几棵密切相关的树,因为它的速度,然后在剩下的几棵树上使用真正的编辑距离,你需要仔细检查,为此你可以找到 python Zhang & Shasha 算法的参考或移植实现。

请注意,lfn 和 cfn 参数指定每棵树应如何独立确定每个树根的节点标签名称和子数组,以便您可以做一些时髦的事情,例如将对象与浏览器 DOM 进行比较。您需要做的就是提供这些函数以及每个根,而 jqgram 将完成其余的工作,调用您提供的 lfn 和 cfn 函数来构建树。所以从这个意义上说,它(无论如何在我看来)比 PyGram 更容易使用。另外,它的 Javascript,所以在客户端或服务器端使用它!

另外,要回答关于循环检测的问题,请查看 jqgram 中的 clone 方法,那里有循环检测,但这要归功于 node-clone 的作者,其中对该部分进行了轻微修改和包含。

【讨论】:

这是否允许多个 lfn ?我想匹配的不仅仅是标签,即。也是储值。节点值。【参考方案4】:

虽然这个问题很老,但我会在下面添加更多参考和算法:

    X-Diff: An Effective Change Detection Algorithm for XML Documents , Yuan Wang, David J. DeWitt, Jin-Yi Cai KF-Diff+: Highly Efficient Change Detection Algorithm for XML Documents diffX: An Algorithm to Detect Changes in Multi-Version XML Documents Change Detection in XML Trees: a Survey, Luuk Peters Similarity in Tree Data Structures

此外,GitHub 上有一些库和框架(在 javascript 中),它们实现了树状结构的差异,例如处理 JSON 数据或 XML 树的应用程序(例如,用于客户端 MVC/MVVM):

    React.js JSON-Patch jsondiffpatch objectDiff

【讨论】:

强烈推荐阅读Change Detection in XML Trees: a Survey 论文——它列出了数十种用于 XML diffing 的算法(这只是树 diffing)。【参考方案5】:

这称为树到树校正问题树到树编辑问题。出于某种原因,大多数与此相关的文献都明确涉及比较 XML 树,因此搜索“XML diffing algorithm”会产生很多结果。除了 Nikos 的链接列表,我还发现了这些:

Fine-grained Change Detection in Structured Text Documents (2014) Change Detection by Level (CDL): An Efficient Algorithm to Detect Change on XML Documents (2010) Comparing XML Documents as Reference-aware Labeled Ordered Trees (2011) 这个代码 - VTracker 仍然存在! 编辑:实际上有趣的代码不包括在内。这让我想到... UMLDiff: An Algorithm for Object-Oriented Design Differencing (2005)。 Revisiting the tree edit distance and its backtracing: A tutorial (2018) - 看起来像是 Zhang-Shasha 算法的一个很好的教程,这似乎是“经典”的解决方案,但时间复杂度很差,因为它会将每个子树与其他所有子树进行比较。李>

我也强烈建议阅读Change Detection in XML Trees: a Survey,但它是从 2005 年开始的,所以它提到的任何工具都几乎不存在了。 Comparing XML Documents as Reference-aware Labeled Ordered Trees对我目前发现的一些算法进行了最直观的描述(从第 2.1.2 节开始)。

不幸的是,似乎没有多少可用的开源代码可以做到这一点,而且并不古老。只是很多过于复杂的论文。 :-/

【讨论】:

我看不到这篇论文,是不是pdf链接坏了? Change Detection in XML Trees: a Survey 为我工作。您是否单击了Download full-test PDF 按钮?如果由于某种原因被阻止,不妨试试 Sci-hub。

以上是关于计算最小操作以使两个树结构相同的主要内容,如果未能解决你的问题,请参考以下文章

数据结构与算法最小生成树算法

「数据结构」 平衡树

普通平衡树

3224. 普通平衡树平衡树-splay

模板普通平衡树

Tyvj 1728 普通平衡树