Да, вы можете выполнить это сжатие за , но это не легко :) Сначала мы сделаем некоторые наблюдения, а затем представим алгоритм. Мы предполагаем, что дерево изначально не сжато - это на самом деле не нужно, но облегчает анализ.O(nlogn)
Во-первых, мы характеризуем «структурное равенство» индуктивно. Пусть и T ′ два (под) дерева. Если Т и Т ' являются нулевые деревья (не имеющие вершин на всех), они структурно эквивалентны. Если T и T ′ не являются нулевыми деревьями, то они структурно эквивалентны, если их левые потомки структурно эквивалентны, а их правые потомки структурно эквивалентны. «Структурная эквивалентность» является минимальной фиксированной точкой над этими определениями.TT′TT′TT′
Например, любые два конечных узла являются структурно эквивалентными, так как оба имеют нулевые деревья в качестве своих дочерних элементов, которые являются структурно эквивалентными.
Поскольку довольно неприятно говорить, что «их левые дети структурно эквивалентны, как и их правые дети», мы часто будем говорить, что «их дети структурно эквивалентны» и намерены делать то же самое. Также обратите внимание, что мы иногда говорим «эта вершина», когда имеем в виду «поддерево, корни которого находятся в этой вершине».
Приведенное выше определение немедленно дает нам подсказку, как выполнить сжатие: если мы знаем структурную эквивалентность всех поддеревьев с глубиной не более , то мы можем легко вычислить структурную эквивалентность поддеревьев с глубиной d + 1 . Мы должны сделать это вычисление разумно, чтобы избежать времени выполнения O ( n 2 ) .dd+1O(n2)
Алгоритм будет присваивать идентификаторы каждой вершине во время ее выполнения. Идентификатор - это число в наборе . Идентификаторы уникальны и никогда не меняются: поэтому мы предполагаем, что мы устанавливаем некоторую (глобальную) переменную равной 1 в начале алгоритма, и каждый раз, когда мы присваиваем идентификатор какой-либо вершине, мы присваиваем текущее значение этой переменной вершине и приращению значение этой переменной.{ 1 , 2 , 3 , … , n }
Сначала мы преобразуем входное дерево в (не более ) списков, содержащих вершины одинаковой глубины вместе с указателем на их родителя. Это легко сделать за O ( n ) раз.NO ( n )
Сначала мы сжимаем все листья (мы можем найти эти листья в списке с вершинами глубины 0) в одну вершину. Мы присваиваем этой вершине идентификатор. Сжатие двух вершин выполняется путем перенаправления родителя любой вершины, чтобы указывать на другую вершину.
Мы делаем два наблюдения: во-первых, у любой вершины есть дети строго меньшей глубины, а во-вторых, если мы выполнили сжатие на всех вершинах глубины, меньшей (и дали им идентификаторы), то две вершины глубины d структурно эквивалентны и может быть сжат, если идентификаторы их дочерних элементов совпадают. Последнее наблюдение следует из следующего аргумента: две вершины структурно эквивалентны, если их потомки структурно эквивалентны, и после сжатия это означает, что их указатели указывают на одинаковых потомков, что, в свою очередь, означает, что идентификаторы их потомков равны.dd
Мы перебираем все списки с узлами одинаковой глубины от малой до большой глубины. Для каждого уровня мы создаем список целочисленных пар, где каждая пара соответствует идентификаторам потомков некоторой вершины на этом уровне. Мы имеем, что две вершины на этом уровне структурно эквивалентны, если их соответствующие целочисленные пары равны. Используя лексикографический порядок, мы можем отсортировать их и получить наборы целочисленных пар, которые равны. Мы сжимаем эти множества в отдельные вершины, как указано выше, и присваиваем им идентификаторы.
Приведенные выше наблюдения доказывают, что этот подход работает и приводит к сжатому дереву. Общее время выполнения плюс время, необходимое для сортировки списков, которые мы создаем. Поскольку общее число целочисленных пар, которые мы создаем, равно n , это дает нам общее время работы O ( n log n ) , как требуется. Подсчет количества узлов, которые мы оставили в конце процедуры, тривиален (просто посмотрите, сколько идентификаторов мы раздали).O ( n )NO ( n logн )