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