Простой способ найти поддерево в дереве

Я пишу код, который использует дерево (обычное дерево, которое может иметь неограниченное количество узлов, но без пересечения, т.е. два родительских узла не будут указывать на один и тот же дочерний узел). Во всяком случае, две вещи:

1) Существуют ли известные алгоритмы поиска поддерева в дереве.

2) Существуют ли какие-либо библиотеки Java (или любые библиотеки, если на то пошло), которые уже реализуют этот алгоритм? Может ли кто-нибудь порекомендовать хорошую древовидную библиотеку Java общего назначения, даже если ее нет?

Я хочу использовать эти деревья для хранения данных в древовидном формате, а не для их возможностей поиска.

Чтобы немного расширить: я использую дерево как часть игры, чтобы вести историю того, что происходит, когда происходят определенные события. Например, A может ударить B, который может поразить два A, может поразить еще два A и т. Д.

Это будет выглядеть примерно так:

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

Конечно, есть больше, чем просто A и B. Я хочу (для системы достижений) уметь определять, когда, скажем, A попадает в два A:

  A
 / \
A   A

Я хочу иметь возможность легко узнать, содержит ли первое дерево это поддерево. И я не хочу писать для этого весь код, если мне не нужно :)

Ответов (4)

Решение

Выглядит как простой алгоритм: найдите корень дерева поиска в дереве игры и проверьте, являются ли дочерние элементы дерева поиска подмножеством дочерних элементов в дереве игры.

Судя по вашим объяснениям, я не уверен, что дерево поиска

  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();
    }
}

Интересно, есть ли расширение алгоритма Кнута, которое было бы более эффективным, чем наивный обход ...

Вы ищете какие-то конкретные ограничения для поддерева? В противном случае для идентификации поддеревьев должно быть достаточно простого обхода (в основном каждый узел рассматривается как корень поддерева).

Я полагаю, вы обнаружите, что API, который вам нужен для дерева, сильно зависит от вашего конкретного приложения - настолько, что общие реализации не очень полезны. Возможно, если бы вы сказали нам, для каких приложений будет использоваться дерево, мы могли бы предоставить подробности.

Кроме того, если вы просто используете дерево для хранения данных, вы можете спросить себя, зачем вам дерево. Этот ответ также должен отвечать на вопрос в моем предыдущем абзаце.

Если есть одно большое статическое дерево, и вы будете искать много поддеревьев в одном большом дереве, вы можете захотеть аннотировать каждый узел набором хэшей всех его поддеревьев до заданной глубины в зависимости от того, сколько у вас хранилища. готовы потратиться на эту функциональность. Затем постройте карту от хеш-значений к набору узлов, которые являются корнями поддерева с этим хеш-значением. Затем просто проверьте каждый из них, предположительно намного дешевле, чем обход, на предмет хэша корня дерева запросов на ту же глубину.