Найти, является ли дерево поддеревом другого

есть два двоичных дерева T1 и T2, которые хранят символьные данные, дубликаты разрешены.
Как я могу найти, является ли T2 поддеревом T1 ? .
T1 имеет миллионы узлов, а T2 имеет сотни узлов.

10 ответов


Траверс T1. Если текущий узел равен корневому узлу T2, пересечь оба дерева (T2 и текущее поддерево T1) одновременно. Сравните текущий узел. Если они всегда равны, T2 является поддеревом T1.


Я предлагаю алгоритм, который использует предобработку:

1) предварительно обработать дерево T1, сохранив список всех возможных корней поддерева (список кэша будет иметь миллионы записей);

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

3) Используйте двоичный поиск, чтобы найти необходимое под-дерево. Вы можете быстро отклонить поддеревья, с количеством узлов, не равным числу узлов T2 (или с разной глубиной).

обратите внимание, что шаги 1) и 2) должны выполняться только один раз, затем вы можете протестировать много кандидатов под-дерева, используя тот же результат предварительной обработки.


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

если вы знаете глубину каждого узла T1 (от листа к узлу) вы можете пропустить любые узлы, которые не так глубоки, как поддерево, которое вы ищете. Это может помочь вам исключить множество ненужных сравнений. Скажи это T1 и T2 хорошо сбалансированы, то дерево T1 будет иметь общую глубину до 20 (2**20>1,000,000) и дерево T2 будет иметь глубину 7 (2**7>100). Вы просто должны пройти 13 Первый слои of T1 (8192 узла -- или это 14 слоев и 16384 узла?) и сможете пропустить около 90%T1...

, если поддерева вы имеете в виду, что узлы лист T2 также узлы лист T1, тогда вы можете сделать первый обход T1 и вычислить глубину каждого узла (расстояние от листа до узла), а затем проверить только поддеревья, которые имеют ту же глубину, что и T2.

Если вы привязаны к памяти / хранилищу (т. е. не можете предварительно манипулировать и хранить деревья в альтернативных формах), вы, вероятно, не сможете сделать ничего лучше, чем поиск грубой силы, предложенный некоторыми другими ответами (траверс P1 ищет соответствующий корень P2, траверс как для определения того, является ли узел на самом деле корнем соответствующего под-дерева, продолжайте исходный обход, если он не соответствует). Этот поиск работает в O (n * m), где n-размер P1, а m-размер P2. С проверки глубины и другие потенциальные оптимизации в зависимости от данных дерева, которые у вас есть, этот человек немного оптимизирован, но он все еще обычно O(n * m). В зависимости от ваших конкретных обстоятельств, это может быть лишь разумный подход.

Если вы не связаны с памятью/хранилищем и не возражаете против некоторой сложности, я считаю, что это можно улучшить до O (n + m), уменьшив до самая длинная общая подстрока проблема с помощью обобщенная суффикс дерево!--4-->. Некоторое обсуждение этого для аналогичной проблемы можно найти здесь. Возможно, когда у меня будет больше времени, я вернусь и отредактирую с большей спецификой реализации.


Если задан корень обоих деревьев и учитывая, что узлы имеют один и тот же тип, почему тогда просто удостоверяется, что корень T2 в T1 недостаточен?

Я предполагаю, что" заданное дерево T " означает заданный указатель на корень T и тип данных узла.

с уважением.


Я не уверен, правильна ли моя идея. Тем не менее, для вашего persual.

  1. проведите прогулку по порядку в дереве 1 и дереве 2, назовите его P1 и P2.
  2. Сравните P1 & P2. Если они разные. Дерево не является поддеревом. Выход.
  3. если они одинаковы или P1 содержится в P2. Вы можете решить, какой из поддерева.

Я думаю, что нам нужно идти грубой силой, но зачем вам нужно сопоставлять деревья после того, как вы найдете свой корень T2 в T1. Это не то же самое, что когда мы не должны находить, идентичны ли деревья.(Тогда только нам нужно сравнить все дерево)

вам даны деревья T1 и T2,указатели, а не значения.

Если узел T2 (который является указателем), присутствует в дереве T1.

затем дерево Т2 поддерева Т1.


Edit:

только если он сказал, что T2 на самом деле является другим деревом по объекту, и нам нужно выяснить, что если в T1 есть поддерево, которое идентично T2.

тогда это не будет работать.

и у нас нет другого выбора, кроме как пойти на решения, уже обсужденные здесь.


предположим, что у нас есть T1 как родительское дерево и T2 как дерево, которое может быть поддеревом T1. Сделать следующее. Предположение, сделанное T1 и T2, является двоичным деревом без какого-либо балансирующего фактора.

1) Поиск корня T2 в T1. Если не найдено, T2 не является поддеревом. Поиск элемента в BT займет O (n) время.

2)Если элемент найден, выполните предварительный обход T1 из корневого элемента узла T2. Это займет o(n) времени. Сделать предварительный обход T2 как что ж. Займет O (n) время. Результат обхода предварительного заказа может храниться в стеке. Вставка в стек займет только O (1).

3) Если размер двух стеков не равен, T2 не является поддеревом.

4) Поп один элемент из каждого стека и проверить на равенство. Если возникает несоответствие, T2 не является поддеревом.

5) Если все элмента соответствуют Т2 поддерева.


я предполагаю, что ваше дерево неизменяемые деревья таким образом, вы никогда не меняете поддерево (вы не делаете set-car! на языке схемы), но просто вы строите новые деревья из листьев или из существующих деревьев.

тогда я бы посоветовал хранить в каждом узле (или поддерево) хеш-код этого узла. На языке с объявите дерево-s

 struct tree_st {
   const unsigned hash;
   const bool isleaf;
   union {
     const char*leafstring; // when isleaf is true
     struct { // when isleaf is false
        const struct tree_st* left;
        const struct tree_st* right;
     };
   };
 };

затем вычислить хэш во время строительства, и при сравнении узлы для равенства сначала сравнивают свой хэш для равенства; большую часть времени хэш-код будет отличаться (и вы не будете беспокоиться о сравнении контента).

здесь возможная функция конструкции листьев:

struct tree_st* make_leaf (const char*string) {
   assert (string != NULL);
   struct tree_st* t = malloc(sizeof(struct tree_st));
   if (!t) { perror("malloc"); exit(EXIT_FAILURE); };
   t->hash = hash_of_string(string);
   t->isleaf = true;
   t->leafstring = string;
   return t;
}

функция вычисления хэш-кода -

unsigned tree_hash(const struct tree_st *t) {
  return (t==NULL)?0:t->hash;
}

функция для построения узла из двух поддеревьев sleft & sright is

struct tree_st*make_node (const struct tree_st* sleft,
                          const struct tree_st* sright) {
   struct tree_st* t = malloc(sizeof(struct tree_st));
   if (!t) { perror("malloc"); exit(EXIT_FAILURE); };
   /// some hashing composition, e.g.
   unsigned h = (tree_hash(sleft)*313) ^ (tree_hash(sright)*617);
   t->hash = h;
   t->left = sleft;
   t->right = sright;
   return t;
 }

функция сравнения (двух деревьев tx & ty) воспользуйтесь тем, что если хэш-коды различны, сравнения различны

bool equal_tree (const struct tree_st* tx, const struct tree_st* ty) {
  if (tx==ty) return true;
  if (tree_hash(tx) != tree_hash(ty)) return false;
  if (!tx || !ty) return false;
  if (tx->isleaf != ty->isleaf) return false;
  if (tx->isleaf) return !strcmp(tx->leafstring, ty->leafstring);
  else return equal_tree(tx->left, ty->left) 
              && equal_tree(tx->right, ty->right); 

}

большую часть времени tree_hash(tx) != tree_hash(ty) тест будет успешным, и вам не придется рекурсировать.

читать про хэш используя.

после такой эффективной хэш -equal_tree функция вы можете использовать методы, упомянутые в других ответах (или в справочниках).


один из простых способов-написать метод is_equal () для дерева и сделать следующее:

bool contains_subtree(TNode*other) {
    // optimization
    if(nchildren < other->nchildren) return false;
    if(height < other->height) return false;

    // go for real check
    return is_equal(other) || (left != NULL && left->contains_subtree(other)) || (right != NULL && right->contains_subtree(other));
}

обратите внимание, что is_equal () можно оптимизировать, используя хэш-код для дерева. Это можно сделать простым способом, взяв высоту дерева или количество детей или диапазон значений хэш-кода.

bool is_equal(TNode*other) {
    if(x != other->x) return false;
    if(height != other->height) return false;
    if(nchildren != other->nchildren) return false;
    if(hashcode() != other->hashcode()) return false;
    // do other checking for example check if the children are equal ..
}

когда дерево похоже на связанный список, это займет O(n) времени. Мы также можем использовать некоторую эвристику при выборе детей для сравнивать.

bool contains_subtree(TNode*other) {
    // optimization
    if(nchildren < other->nchildren) return false;
    if(height < other->height) return false;

    // go for real check
    if(is_equal(other)) return true;
    if(left == NULL || right == NULL) {
          return (left != NULL && left->contains_subtree(other)) || (right != NULL && right->contains_subtree(other));
    }
    if(left->nchildren < right->nchildren) { // find in smaller child tree first
          return (left->contains_subtree(other)) || right->contains_subtree(other);
    } else {
          return (right->contains_subtree(other)) || left->contains_subtree(other);
    }
}

другой способ-сериализовать оба дерева как строку и найти, является ли вторая строка(сериализованная из T2) подстрокой первой строки(сериализованной из T1).

следующий код сериализуется в предварительном порядке.

   void serialize(ostream&strm) {
            strm << x << '(';
            if(left)
                    left->serialize(strm);
            strm << ',';
            if(right)
                    right->serialize(strm);
            strm << ')';
    }

и мы можем использовать некоторый оптимизированный алгоритм, например,алгоритм Кнута–Морриса–Пратта найти(возможно, в O (n) времени) существование подстроки и в конечном итоге найти, является ли дерево под-деревом другого .

снова строка может быть эффективно сжата с помощью Burrows-Wheeler_transform. И это возможно bzgrep поиск подстроки в сжатых данных.

другой способ-сортировать под-деревья в дереве по высоте и количеству детей.

bool compare(TNode*other) {
    if(height != other->height)
        return height < other->height;

    return nchildren < other->nchildren;
}

обратите внимание, что будет O(n^2) под-деревьев. Чтобы уменьшить число, мы можем использовать некоторый диапазон, основанный на высоте. Например, нас могут интересовать только под-деревья высотой 1000 до 1500.

когда генерируются отсортированные данные, в нем можно выполнить двоичный поиск и найти, является ли он подмножеством в O(lg n) времени(учитывая, что в отсортированных данных нет дубликата) .