【问题标题】:How do I calculate tree edit distance? [closed]如何计算树编辑距离? [关闭]
【发布时间】:2023-05-24 15:12:01
【问题描述】:

我需要计算树之间的编辑距离。 This 论文描述了一种算法,但我无法从中得出正面或反面。你能用更平易近人的方式描述一个适用的算法吗?伪代码或代码都会有所帮助。

【问题讨论】:

    标签: algorithm tree


    【解决方案1】:

    这个 Python 库正确地实现了张莎莎算法Zhang-Shasha: Tree edit distance in Python

    它最初是当前接受的答案(带有 tarball 链接的那个)中列出的 Java 源代码的直接端口,但该实现不正确并且几乎不可能运行。

    【讨论】:

    • 感谢您的回馈——很高兴您能够正确实现 Zhang-Shasha 算法。抱歉,我链接的代码不起作用。
    • Steve 的分叉不再是算法的规范分叉,请参阅:github.com/timtadh/zhang-shasha
    【解决方案2】:

    这里有一些 Java source code(底部的 gzip 压缩包)用于 树编辑距离 算法,可能对您有用。

    该页面包含参考资料和一些幻灯片,逐步完成“张和莎莎”算法以及其他有用的链接,以帮助您快速了解。

    链接中的代码有错误。 Steve Johnson 和 tim.tadh 提供了working Python code。详情请见Steve Johnson's comment

    【讨论】:

    • 此处链接的实现不正确。 (见我的回答。)我通过移植它开始我的实现,但是当我最终找到它所引用的论文时,我发现与原始论文有一些不同之处,导致它未能通过对称性、三角形不等式等基本测试。
    【解决方案3】:

    我基于现有 PyGram Python 代码 (https://github.com/Sycondaman/PyGram) 为希望在浏览器和/或 Node.js 中使用 PQ-Gram 算法使用树编辑距离近似的人编写了一个实现 (https://github.com/hoonto/jqgram.git)。 js.

    jqgram树编辑距离近似模块实现了服务器端和浏览器端应用的PQ-Gram算法; O(n log n) 时间和 O(n) 空间性能,其中 n 是节点数。算法出处见学术论文:http://www.vldb2005.org/program/paper/wed/p301-augsten.pdf

    PQ-Gram 近似比通过 Zhang & Shasha、Klein 或 Guha 等获得真实编辑距离要快得多。 al,他们提供了真正的编辑距离算法,这些算法都执行最小 O(n^2) 时间,因此通常不适合。

    通常在实际应用中,如果可以获得多个树与已知标准的相对近似值,则无需知道真实的编辑距离。随着 Node.js 的出现,浏览器和服务器上的 JavaScript 经常处理树结构,最终用户性能通常在算法实现和设计中至关重要;因此 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, depth:10 },
    function(result) {
        console.log(result.distance);
    });
    

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

    现在您可以使用的一种方法是使用 jqgram 或 PyGram 来获取一些靠近的树,然后继续使用真正的编辑距离算法来处理较小的树集。为什么要把所有的计算都花在你已经很容易确定距离很远的树上,反之亦然?所以你也可以使用 jqgram 来缩小选择范围。

    【讨论】:

    • www.vldb2005.org 链接已损坏:“糟糕!找不到该页面。在此位置似乎找不到任何内容。”
    【解决方案4】:

    在这里您可以找到树编辑距离算法的 Java 实现:

    Tree Edit Distance

    除了 Zhang&Shasha 1989 的算法之外,还有更新的算法的树编辑距离实现,包括 Klein 1998、Demaine 等。 2009 年,以及 Pawlik&Augsten 的稳健树编辑距离 (RTED) 算法,2011 年。

    【讨论】:

    【解决方案5】:

    我使用 jpypeAPTED 算法制作了一个简单的 Python 包装器 (apted.py):

    # To use, create a folder named lib next to apted.py, then put APTED.jar into it
    
    import os, os.path, jpype
    
    global distancePackage
    distancePackage = None
    
    global utilPackage
    utilPackage = None
    
    def StartJVM():
      # from http://www.gossamer-threads.com/lists/python/python/379020
      root = os.path.abspath(os.path.dirname(__file__))
      jpype.startJVM(jpype.getDefaultJVMPath(),
      "-Djava.ext.dirs=%s%slib" % (root, os.sep))
      global distancePackage
      distancePackage = jpype.JPackage("distance")
      global utilPackage
      utilPackage = jpype.JPackage("util")
    
    
    def StopJVM():
      jpype.shutdownJVM()
    
    
    class APTED:
      def __init__(self, delCost, insCost, matchCost):
        global distancePackage
        if distancePackage is None:
          raise Exception("Need to call apted.StartJVM() first")
        self.myApted = distancePackage.APTED(float(delCost), float(insCost), float(matchCost))
    
      def nonNormalizedTreeDist(self, lblTreeA, lblTreeB):
        return self.myApted.nonNormalizedTreeDist(lblTreeA.myLblTree, lblTreeB.myLblTree)
    
    
    class LblTree:
      def __init__(self, treeString):
        global utilPackage
        if utilPackage is None:
          raise Exception("Need to call apted.StartJVM() first")
    
        self.myLblTree = utilPackage.LblTree.fromString(treeString)
    
    '''
    # Example usage:
    
    import apted
    apted.StartJVM()
    aptedDist = apted.APTED(delCost=1, insCost=1, matchCost=1)
    treeA = apted.LblTree('{a}')
    treeB = apted.LblTree('{b{c}}')
    dist = aptedDist.nonNormalizedTreeDist(treeA, treeB)
    print dist
    
    
    # When you are done using apted
    apted.StopJVM()
    # For some reason it doesn't usually let me start it again
    # and crashes the Python interpreter upon exit when I do
    # this, so call only as needed.
    '''
    

    【讨论】:

      【解决方案6】:

      树编辑距离有很多变化。如果您可以使用自上而下的树编辑距离,这限制了对叶子的插入和删除,我建议您尝试以下论文:Comparing Hierarchical Data in External Memory

      实现是一个简单的动态规划矩阵,成本为 O(n2)。

      【讨论】:

        【解决方案7】:

        您参考了 ICALP2007 论文的期刊版本,An Optimal Decomposition Algorithm for Tree Edit Distance

        这个版本也有伪代码。

        【讨论】:

        • 链接仍然有效!