在一棵树中查找子树的简便方法

6

我正在编写一些代码,使用树(Tree)(一个常规的树形结构,可以拥有无限数量的节点,但没有交叉,即两个父节点不会指向同一个子节点)。无论如何,需要解决两个问题:

1)是否有已知的算法可以在树中查找子树。

2)是否有任何Java库(或任何其他库)已经实现了这个算法? 即使没有,有没有人推荐好的通用Java树库?

我想使用这些树以树形式存储数据,而不是为了它们的搜索功能。

稍作说明:我正在将树作为游戏的一部分,以保留发生某些事件时发生的历史记录。例如,A可以打B,B可以打两个A,然后可以再打两个A等等。

这将类似于:

    A
    |
    B
   /
  A 
 / \  
A   A
   / \
  A   A

当然不仅仅有A和B。我想要做的是(针对成就系统)能够告诉我们,比如A已经击中了两个A:
  A
 / \
A   A

我希望能够轻松地知道第一棵树是否包含该子树。如果不必要的话,我不想自己编写所有执行此操作所需的代码 :)

4个回答

8

看起来是一个简单的算法:在游戏树中找到搜索树的根节点,并检查搜索树的子节点是否是游戏树中子节点的子集。

从你的解释中,我不确定搜索树是什么。

  A
 / \
A   A

应该匹配这棵树:

  A
 /|\
A C A

(即如果非匹配子项应该被忽略。)
无论如何,这是我刚刚玩耍的代码。它是一个完全运行的示例,并带有一个主方法和一个简单的“Node”类。随意使用它:
import java.util.Vector;

public class PartialTreeMatch {
    public static void main(String[] args) {
        Node testTree = createTestTree();
        Node searchTree = createSearchTree();

        System.out.println(testTree);
        System.out.println(searchTree);

        partialMatch(testTree, searchTree);
    }

    private static boolean partialMatch(Node tree, Node searchTree) {
        Node subTree = findSubTreeInTree(tree, searchTree);
        if (subTree != null) {
            System.out.println("Found: " + subTree);
            return true;
        }
        return false;
    }

    private static Node findSubTreeInTree(Node tree, Node node) {
        if (tree.value == node.value) {
            if (matchChildren(tree, node)) {
                return tree;
            }
        }

        Node result = null;
        for (Node child : tree.children) {
            result = findSubTreeInTree(child, node);

            if (result != null) {
                if (matchChildren(tree, result)) {
                    return result;
                }
            }
        }

        return result;
    }

    private static boolean matchChildren(Node tree, Node searchTree) {
        if (tree.value != searchTree.value) {
            return false;
        }

        if (tree.children.size() < searchTree.children.size()) {
            return false;
        }

        boolean result = true;
        int treeChildrenIndex = 0;

        for (int searchChildrenIndex = 0;
                 searchChildrenIndex < searchTree.children.size();
                 searchChildrenIndex++) {

            // Skip non-matching children in the tree.
            while (treeChildrenIndex < tree.children.size()
                  && !(result = matchChildren(tree.children.get(treeChildrenIndex),
                                              searchTree.children.get(searchChildrenIndex)))) {
                treeChildrenIndex++;
            }

            if (!result) {
                return result;
            }
        }

        return result;
    }

    private static Node createTestTree() {
        Node subTree1 = new Node('A');
        subTree1.children.add(new Node('A'));
        subTree1.children.add(new Node('A'));

        Node subTree2 = new Node('A');
        subTree2.children.add(new Node('A'));
        subTree2.children.add(new Node('C'));
        subTree2.children.add(subTree1);

        Node subTree3 = new Node('B');
        subTree3.children.add(subTree2);

        Node root = new Node('A');
        root.children.add(subTree3);

        return root;
    }

    private static Node createSearchTree() {
        Node root = new Node('A');
        root.children.add(new Node('A'));
        root.children.add(new Node('A'));

        return root;
    }
}

class Node {
    char value;
    Vector<Node> children;

    public Node(char val) {
        value = val;
        children = new Vector<Node>();
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();

        sb.append('(');
        sb.append(value);

        for (Node child : children) {
            sb.append(' ');
            sb.append(child.toString());
        }

        sb.append(')');

        return sb.toString();
    }
}

2
您是否正在寻找子树上的特定约束条件?如果没有,简单遍历应该足以识别子树(基本上将每个节点视为子树的根)。
我认为,您所需的树API因特定应用而异,因此通用实现并不是非常有用。也许,如果您能告诉我们树将用于什么样的应用程序,我们可以提供详细信息。
另外,如果您只是将树用作数据存储,您可能需要问自己为什么需要树。那个答案也应该回答我上一个段落中的问题。

0
如果有一棵大的静态树,你需要在同一棵大树中搜索许多子树,你可能想要用给定深度的所有子树的哈希集合来注释每个节点,具体取决于你愿意为该功能花费多少存储空间。然后建立一个从哈希值到节点集合的映射,这些节点是具有相同哈希值的子树的根节点。然后只需检查其中的每一个,这通常比遍历更便宜,以查找查询树的根哈希值到相同深度的子树。

0

我想知道是否有Knuth算法的扩展能比朴素遍历更有效率...


很确定这是不可能的,因为所有的“树算法”都基于每个节点包含一些关于其子节点的信息(例如它们的最大值和最小值),而这里并非如此。 - Michael Borgwardt
我不明白这是怎么回事。字符串中的一个字符并不能包含关于后续字符的信息。在这种情况下,根节点A与目标模式的根节点匹配,但它的子节点B却不匹配,现在我应该能够跳过检查B与目标模式的根节点A是否匹配的步骤。 - Ken
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.21.3770 使用树的字符串表示形式进行子线性匹配。 - Pete Kirkham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接