树/差异算法

2023-12-30

我目前正在编写一个差异算法来检测树的两个修订版之间的插入、删除、更新和移动,而每个节点都有一个唯一的 ID,该 ID 不会因修订而改变。

我将按预序遍历每棵树,并动态生成两个节点之间的差异,然后移动cursors相应地(例如,在遇到删除的节点后,仅旧版本上的光标向前移动,对于插入的节点反之亦然)。

现在我的问题是,我必须在移动时检测剪切和粘贴点(其中移动的节点从旧版本中剪切并粘贴到新版本中),以便向前移动右侧光标并进行后续可视化聚合树表示。

我们有一个简单的parent/leftsibling/rightsibling/firstchild/currnode编码,而每个节点都有一个唯一的 ID,一个长值。因为这种编码不了解全局排序,所以我首先考虑按文档顺序在当前节点之后搜索新版本中的 oldNodeKey,并对旧版本上的光标执行反之亦然,并保存是否找到节点以及在多少个节点之后节点访问:

/**
 * Search for the supplied node key in following nodes.
 * 
 * @param paramRtx
 *            Treetank {@link IReadTransaction}
 * @param paramNodeKey
 *            node key to search for
 * @return {@code true} if found, {@code false} otherwise
 */
protected Result searchNode(final IReadTransaction paramRtx, final long paramNodeKey) {
    checkNotNull(paramRtx);
    checkArgument(paramNodeKey >= 0);
    final long nodeKey = paramRtx.getNode().getNodeKey();
    boolean found = false;
    int sumNodes = 0;
    for (final AbsAxis axis = new DescendantAxis(paramRtx); !found && axis.hasNext(); axis.next()) {
        sumNodes++;
        if (axis.getTransaction().getNode().getNodeKey() == paramNodeKey) {
            found = true;
        }
    }
    for (final AbsAxis axis = new FollowingAxis(paramRtx); !found && axis.hasNext(); axis.next()) {
        sumNodes++;
        if (axis.getTransaction().getNode().getNodeKey() == paramNodeKey) {
            found = true;
        }
    }
    paramRtx.moveTo(nodeKey);
    return new Result(found, sumNodes);
}

本质上,如果 newResult.mSum > oldResult.mSum 则意味着该节点已被“粘贴”,反之亦然,并且 newResult.mSum == oldResult.mSum 是一种特殊情况,但我认为在剪切和修改过多的情况下这是不正确的粘贴点将无法被正确识别。我已经编写了很多代码来跟踪不同的情况,但我认为我必须重新考虑完整的移动检测内容:-(

例如我已经实现了这样的东西:

        if (mMovedMap.get(newKey) == null && mMovedMap.get(oldKey) == null) {
            final ExecutorService pool = Executors.newFixedThreadPool(2);
            final Future<Result> foundNew = pool.submit(new Callable<Result>() {
                @Override
                public Result call() throws Exception {
                    return searchNode(paramNewRtx, oldKey);
                }
            });
            final Future<Result> foundOld = pool.submit(new Callable<Result>() {
                @Override
                public Result call() throws Exception {
                    return searchNode(paramOldRtx, newKey);
                }
            });
            pool.shutdown();

            try {
                final Result resultNew = foundNew.get();
                final Result resultOld = foundOld.get();
                paramNewRtx.moveTo(newKey);
                paramOldRtx.moveTo(oldKey);

                if (resultNew.mFound && resultOld.mFound && resultNew.mSumNodes > resultOld.mSumNodes) {
                    moveToNextRightNode(paramOldRtx, null);
                    if (paramOldRtx.getNode().getNodeKey() == newKey) {
                        diff = EDiff.MOVEDCUT;
                        paramOldRtx.moveTo(oldKey);
                        paramNewRtx.moveTo(newKey);
                        fireMovedOldDiffs(paramOldRtx, paramNewRtx, oldKey, diff, paramDepth);
                    } else {
                        diff = EDiff.MOVEDPASTE;
                        paramOldRtx.moveTo(oldKey);
                        paramNewRtx.moveTo(newKey);
                        fireMovedNewDiffs(paramOldRtx, paramNewRtx, newKey, diff, paramDepth);
                    }
                } else if (resultNew.mFound && resultOld.mFound
                    && resultNew.mSumNodes < resultOld.mSumNodes) {
                    moveToNextRightNode(paramNewRtx, null);
                    if (paramNewRtx.getNode().getNodeKey() == oldKey) {
                        diff = EDiff.MOVEDPASTE;
                        paramOldRtx.moveTo(oldKey);
                        paramNewRtx.moveTo(newKey);
                        fireMovedNewDiffs(paramOldRtx, paramNewRtx, newKey, diff, paramDepth);
                    } else {
                        diff = EDiff.MOVEDCUT;
                        paramOldRtx.moveTo(oldKey);
                        paramNewRtx.moveTo(newKey);
                        fireMovedOldDiffs(paramOldRtx, paramNewRtx, oldKey, diff, paramDepth);
                    }
                } else {
                    assert foundOld.get() != null && foundOld.get().mFound;
                    assert foundNew.get() != null && foundNew.get().mFound;
                    assert foundNew.get().mSumNodes == foundOld.get().mSumNodes;
                    ...
                }

而 mMovedMap 是一个简单的 Map,用于在遇到移动节点后跟踪它们。

编辑:我尝试检测树中的插入/删除/更新和移动,而节点具有唯一的 ID。困难的部分似乎是检测动作。我正在进行两次预购遍历(一次在旧版本上,另一次在新版本上)。确定插入/删除和更新非常容易,但我很难检测移动,因为我总是比较两个节点(旧版本中的一个与新版本中的一个)我必须知道这两个节点中的哪一个实际上已移动(如果它是旧版本中的节点,则为剪切点,如果新版本中的节点已移动,则为粘贴点)。我还必须知道它是旧版本中的节点还是新版本中的节点已被移动以及如何移动,因为我正在创建一个聚合树表示,其中包含所有编辑操作以在专门的 Sunburst 视图中可视化差异。

编辑:我认为即使我有全局标识符,也无法确定哪一个是剪切的节点(或子树),哪一个是粘贴的节点(或子树)。由于其他修改,仅知道两个节点中哪一个先出现是不够的:(

编辑:有谁知道找出树中哪个节点已移动(比较两个节点)的问题是否是 NP 完全的?或者更一般地检测两个节点之一是否已被移动,考虑到旧版本中的节点上的光标和另一个光标位于新版本中的节点处,以及移动的节点是否已从旧树中删除,或者是否移动的节点是否已插入到新位置? diff 算法的设计方式使我可以将两棵树聚集或融合在一起,以便它们共享公共节点,这对于插入/删除/相同节点/更新来说很好,而且很可能也适用于替换节点,但我认为它可以还没有完成动作吗?如果它是 NP 完全的或不可解的,我需要一个参考,因为它是我硕士论文的一部分,至少我想描述为什么我没有实现移动检测(或恢复非功能性实现;-))。

编辑:也许解决方案是:

// Check if it has been INSERTED, DELETED or MOVED.
// ================================================================
final long nodeKeyOld = paramOldRtx.getNode().getNodeKey();
final long nodeKeyNew = paramNewRtx.getNode().getNodeKey();
final boolean movedOld = paramOldRtx.moveTo(nodeKeyNew);
final boolean movedNew = paramNewRtx.moveTo(nodeKeyOld);
if (!movedNew && mDiff == EDiff.DELETED) {
    paramOldRtx.moveTo(nodeKeyOld);
    if (paramOldRtx.getNode().getNodeKey() == mDeletedKey) {
        movedNew = true;
    }
}

if (movedOld && movedNew) {
    diff = EDiff.MOVED;
} else if (movedOld) {
    paramOldRtx.moveTo(nodeKeyOld);
    mDeletedKey = paramOldRtx.getNode().getNodeKey();
    diff = EDiff.DELETED;
} else {
    diff = EDiff.INSERTED;
}

检测 MOVE 操作本身,就像我现在所做的那样(要检查的特殊情况!movedNew && mDiff == EDiff.DELETED树的末尾只需要删除,但节点也可能被移动)。在所有其他情况下,测试新修订版上的光标(事务)是否可以移动到旧修订版中的节点以及旧修订版上的光标是否可以移动到新修订版中的节点就足够了,对吗?

然后我必须跟踪所有即将发生的更改(或者相同的节点),如果检测到另一个移动,我必须检查两个节点键之一(来自旧版本中的节点和新版本中的节点) )以前也遇到过。如果它是旧节点,则它一定是剪切,当前遇到的移动是粘贴,否则反之亦然)。如果它不是其中一个键,那么它一定是另一个移动操作。

你怎么认为?如果我不能至少 99% 确定它是否有效,我有点不愿意实施它。我花了大约 6 天的时间寻找一个不起作用的解决方案。

编辑:好吧,我认为这是一个坏主意,因为如果我当时不知道哪个是已移动的节点,我不知道如何向前移动光标。

亲切的问候,
约翰内斯


None

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

树/差异算法 的相关文章

随机推荐

  • 在react-native上使用styled-components,如何计算组件的高度?

    使用样式组件 我需要根据屏幕尺寸计算组件的高度 如下所示 const ForgotPasswordContainer styled View height calc 100 20px 这样使用是不行的 样式组件不支持基于百分比的高度值 使用
  • 确定链接服务器的 SQL Server 版本

    这里有人知道我如何通过使用 TSQL 语句来确定链接服务器上运行的 SQL 版本吗 我正在运行 SQL2005 我的链接服务器正在运行 sql2000 2005 和 2008 的混合 select from openquery MyLink
  • 从 ANTLR 生成 EBNF

    有人知道从 ANTLR 生成 EBNF 的工具吗 ANTLR 已经接近 EBNF 但出于文档目的 我希望有一个干净的 EBNF 描述 中间没有代码 有了antlrworks 就可以得到语法图了 java cp antlrworks 1 1
  • NSEvent 和 Magic Mouse

    如何区分事件是否发生 void scrollWheel NSEvent event是由魔术鼠标或触控板触发的吗 我问这个问题的原因是因为我想在使用触控板时为滚动事件分配不同的操作 因为用户可以在触控板上捏合缩放 然而 在魔术鼠标上 用户无法
  • 如何监听使用 Chrome 开发者工具所做的 DOM 更改

    我需要制作一个应用程序 可以检测我何时使用 chrome 开发人员工具更新网页上的属性 例如 如果我打开开发人员工具 请使用元素选择器并更改特定元素的字体大小 见图 我应该能够运行一个程序 通知该程序更新了页面上的哪些元素以及更改了哪些属性
  • R:X 错误中的 NA/NaN/Inf

    我正在尝试使用 R 执行负二项式回归 当我执行以下命令时 DV2 25112013 nb lt glm nb DV2 25112013 Bcorp Geographic Proximity Dirty Industry Clean Indu
  • 在 Windows 上为“therubyracer”安装“libv8”gem

    我安装时遇到问题therubyracerWindows 上的宝石 Using Ruby 2 1 6 32 bit和跑步 gem install libv8 v 3 16 14 3 with system v8 这是我得到的错误 Instal
  • 为什么这段代码会出现空指针异常?我认为字符类可以处理 null 被分配? [复制]

    这个问题在这里已经有答案了 public class Playground public static void main String args String s blah Character lclfs s contains s con
  • 在 Linux 上将 Android Studio 设置重置为默认设置

    每个人 我曾经在 Mac 上开发 Android 应用程序 最近我在运行Xubuntu的Thinkpad上安装了android开发环境 我通过文件 gt 导入设置将Mac上的Android studio设置导入到Xubuntu上的Andro
  • 堆栈变量与堆变量

    我的想法是否正确 char buff 500 创建一个堆栈变量 并且 char buff char malloc 500 创建一个堆变量 如果这是正确的 那么何时以及为什么要使用堆变量而不是堆栈变量 反之亦然 我知道堆栈更快还有其他什么吗
  • async/await 是否适合同时受 IO 和 CPU 限制的方法?

    MSDN 文档似乎指出async and await适用于 IO 密集型任务 而Task Run应该用于 CPU 密集型任务 我正在开发一个应用程序 该应用程序执行 HTTP 请求来检索 HTML 文档 然后对其进行解析 我有一个看起来像这
  • JSP中的编码问题

    我有一个带有几个文本字段的 html 表单 当我尝试提交非英文字符 在我的例子中是俄语 时 服务器收到 不可读 字符串 不是问题 而是一些奇怪的字符 我简化了我的代码以在此处显示
  • Cookie 在 ASP.NET 中如何工作?

    我工作的网站由多个项目组成 用多种语言编写 现在 我们必须在查询字符串和会话变量中使用一些笨拙的代码 以使人们在从一个项目转到另一个项目时保持登录状态 由于 cookie 是特定于域的 因此我们尝试将它们转换为它们 因为它们可以使用一种语言
  • 如何根据区域设置获取带有时区的数据时间模式?

    以下代码是我已经拥有的代码 DateFormat f DateFormat getDateTimeInstance DateFormat SHORT DateFormat SHORT Java Locale SimpleDateFormat
  • 如何配置 nginx 与 Jetty6 网络服务器一起工作?

    看来nginx是和php ruby python一起使用的 有人有如何设置 nginx 与后端 jetty tomcat 一起使用的示例吗 Thanks 正确的 我想我有资格成为一名自学者 不是吗 只需在 nginx conf 文件的 ht
  • 在项目创建时自动加载库。安卓、日食

    我已经弄清楚如何在桌面上获得我想要的效果 窗口 gt 首选项 gt Java gt 安装的 JRE gt jre7 编辑 gt 添加外部 JAR 但我无法在 Android 上获得相同的效果 在桌面项目中 我可以看到文件夹 JRE Syst
  • 在 MongoEngine 中过滤嵌入列表

    如果我有这些模型 class Sub EmbeddedDocument name StringField class Main Document subs ListField EmbeddedDocumentField Sub 我想要一个返
  • 从地图中写入和读取时的竞争条件

    跟进旧帖子here https stackoverflow com questions 71562369 add unique values in an array as a value in concurrent map golang 我
  • C# 中如何检查字符串是否包含字符?

    是否有一个函数可以应用于字符串 如果字符串包含字符 该函数将返回 true 或 false 我有带有一个或多个字符选项的字符串 例如 var abc s var def aB var ghi Sj 例如 我想做的是有一个函数 如果上面包含小
  • 树/差异算法

    我目前正在编写一个差异算法来检测树的两个修订版之间的插入 删除 更新和移动 而每个节点都有一个唯一的 ID 该 ID 不会因修订而改变 我将按预序遍历每棵树 并动态生成两个节点之间的差异 然后移动cursors相应地 例如 在遇到删除的节点