Ниже приведен алгоритм O (n log n), который работает для графиков, в которых каждый дочерний элемент имеет не более одного родителя (EDIT: этот алгоритм не распространяется на случай с двумя родителями с производительностью O (n log n)) , Стоит отметить, что я считаю, что производительность может быть улучшена до O (n log (метка максимального уровня)) с дополнительной работой.
Один родительский случай:
Для каждого узла x в обратном топологическом порядке создайте двоичное дерево поиска T_x, которое строго увеличивается как по дате рождения, так и по числу поколений, удаленных из x. (T_x содержит первенца c1 в подграфе графа предков, укорененного в x, вместе со следующим самым ранним родителем c2 в этом подграфе, так что «уровень прародителя» c2 строго больше, чем у c1, вместе с следующий самый ранний ребенок c3 в этом подграфе, так что уровень c3 строго больше, чем уровень c2 и т. д.) Чтобы создать T_x, мы объединяем ранее построенные деревья T_w, где w - дочерний элемент x (они построены ранее, потому что мы итерации в обратном топологическом порядке).
Если мы будем осторожны с тем, как мы выполняем слияния, мы можем показать, что общая стоимость таких слияний составляет O (n log n) для всего графа предков. Основная идея заключается в том, чтобы заметить, что после каждого объединения в объединенном дереве выживает не более одного узла каждого уровня. Мы связываем с каждым деревом T_w потенциал h (w) log n, где h (w) равен длине самого длинного пути от w до листа.
Когда мы объединяем дочерние деревья T_w для создания T_x, мы «уничтожаем» все деревья T_w, высвобождая весь потенциал, который они хранят для использования при построении дерева T_x; и мы создаем новое дерево T_x с потенциалом (log n) (h (x)). Таким образом, наша цель состоит в том, чтобы потратить не более O ((log n) (sum_w (h (w)) - h (x) + constant)) время на создание T_x из деревьев T_w, чтобы амортизированная стоимость слияния была только O (log n). Это может быть достигнуто путем выбора дерева T_w таким образом, чтобы h (w) было максимальным в качестве начальной точки для T_x, а затем путем изменения T_w для создания T_x. После того, как сделан такой выбор для T_x, мы объединяем каждое из других деревьев, одно за другим, в T_x с помощью алгоритма, аналогичного стандартному алгоритму объединения двух двоичных деревьев поиска.
По сути, объединение выполняется путем итерации по каждому узлу y в T_w, поиска предшественника y по дате рождения, а затем вставки y в T_x, если больше уровней удалено из x, чем z; затем, если z был вставлен в T_x, мы ищем узел в T_x самого низкого уровня, который строго больше уровня z, и склеиваем промежуточные узлы, чтобы сохранить инвариант, согласно которому T_x упорядочен строго как по дате рождения, так и по уровню. Это стоит O (log n) для каждого узла в T_w, и в T_w есть не более O (h (w)) узлов, поэтому общая стоимость объединения всех деревьев составляет O ((log n) (sum_w (h (w) ))), суммируя по всем дочерним элементам w, кроме дочернего элемента w ', чтобы h (w') было максимальным.
Мы храним уровень, связанный с каждым элементом T_x, во вспомогательном поле каждого узла дерева. Нам нужно это значение, чтобы мы могли выяснить фактический уровень x после того, как мы построили T_x. (В качестве технической детали мы фактически сохраняем разницу уровня каждого узла с уровнем его родителя в T_x, чтобы мы могли быстро увеличивать значения для всех узлов в дереве. Это стандартный прием BST.)
Вот и все. Мы просто отмечаем, что начальный потенциал равен 0, а конечный потенциал положительный, поэтому сумма амортизированных границ является верхней границей общей стоимости всех слияний по всему дереву. Мы находим метку каждого узла x, как только мы создаем BST T_x с помощью бинарного поиска последнего элемента в T_x, который был рожден до того, как x умер, по стоимости O (log n).
Чтобы улучшить привязку к O (n log (метка максимального уровня)), вы можете лениво объединять деревья, объединяя только первые несколько элементов дерева по мере необходимости, чтобы обеспечить решение для текущего узла.Если вы используете BST, в котором используется эталонное местоположение, такое как сплайс-дерево, то вы можете достичь вышеуказанной границы.
Надеемся, что приведенный выше алгоритм и анализ, по крайней мере, достаточно ясны, чтобы им следовать.Просто прокомментируйте, если вам нужны какие-либо разъяснения.