Sì, è possibile eseguire questa compressione nel tempo , ma non è facile :) Facciamo prima alcune osservazioni e poi presentiamo l'algoritmo. Supponiamo che l'albero inizialmente non sia compresso - questo non è davvero necessario ma semplifica l'analisi.O(nlogn)
Innanzitutto, definiamo induttivamente la "parità strutturale". Sia e T ′ due (sotto) alberi. Se T e T ' sono entrambi gli alberi nulli (senza vertici), sono strutturalmente equivalenti. Se T e T 'non sono entrambi alberi nulli, allora sono strutturalmente equivalenti se i loro figli sinistri sono strutturalmente equivalenti e i loro figli giusti sono strutturalmente equivalenti. L '"equivalenza strutturale" è il punto fisso minimo su queste definizioni.TT′TT′TT′
Ad esempio, due nodi foglia qualsiasi sono strutturalmente equivalenti, poiché entrambi hanno gli alberi null come entrambi i loro figli, che sono strutturalmente equivalenti.
Dato che è piuttosto fastidioso dire "i loro figli sinistri sono strutturalmente equivalenti e così sono i loro figli giusti", spesso diremo "i loro figli sono strutturalmente equivalenti" e intendono lo stesso. Nota anche che a volte diciamo "questo vertice" quando intendiamo "la sottostruttura radicata in questo vertice".
La definizione sopra ci dà immediatamente un indizio su come eseguire la compressione: se conosciamo l'equivalenza strutturale di tutti i sottotitoli con profondità al massimo , allora possiamo facilmente calcolare l'equivalenza strutturale dei sottotitoli con profondità d + 1 . Dobbiamo fare questo calcolo in modo intelligente per evitare un tempo di esecuzione O ( n 2 ) .dd+1O(n2)
L'algoritmo assegnerà identificatori a ogni vertice durante la sua esecuzione. Un identificatore è un numero nell'insieme . Gli identificatori sono unici e non cambiano mai: assumiamo quindi di impostare una variabile (globale) su 1 all'inizio dell'algoritmo e ogni volta che assegniamo un identificatore a un vertice, assegniamo il valore corrente di quella variabile al vertice e all'incremento il valore di quella variabile.{1,2,3,…,n}
Trasformiamo innanzitutto l'albero di input in (al massimo ) elenchi contenenti vertici di uguale profondità, insieme a un puntatore al loro genitore. Questo è facilmente eseguibile in O ( n ) tempo.nO(n)
Per prima cosa comprimiamo tutte le foglie (possiamo trovare queste foglie nell'elenco con vertici di profondità 0) in un singolo vertice. Assegniamo a questo vertice un identificatore. La compressione di due vertici viene effettuata reindirizzando il genitore di uno dei vertici in modo che punti invece all'altro vertice.
Facciamo due osservazioni: in primo luogo, ogni vertice ha figli di profondità strettamente inferiore e, in secondo luogo, se abbiamo eseguito la compressione su tutti i vertici di profondità inferiori a (e abbiamo dato loro degli identificatori), allora due vertici di profondità d sono strutturalmente equivalenti e può essere compresso se gli identificatori dei loro figli coincidono. Quest'ultima osservazione deriva dal seguente argomento: due vertici sono strutturalmente equivalenti se i loro figli sono strutturalmente equivalenti, e dopo la compressione questo significa che i loro puntatori indicano gli stessi figli, il che a sua volta significa che gli identificatori dei loro figli sono uguali.dd
Esaminiamo tutte le liste con nodi di uguale profondità da piccola profondità a grande profondità. Per ogni livello creiamo un elenco di coppie di numeri interi, in cui ogni coppia corrisponde agli identificatori dei figli di alcuni vertici su quel livello. Abbiamo che due vertici in quel livello sono strutturalmente equivalenti se le loro coppie intere corrispondenti sono uguali. Usando l'ordinamento lessicografico, possiamo ordinarli e ottenere gli insiemi di coppie di numeri interi uguali. Comprimiamo questi set in vertici singoli come sopra e forniamo loro identificatori.
Le osservazioni di cui sopra dimostrano che questo approccio funziona e risulta nell'albero compresso. Il tempo di esecuzione totale è più il tempo necessario per ordinare gli elenchi che creiamo. Poiché il numero totale di coppie di numeri interi che creiamo è n , questo ci dà che il tempo di esecuzione totale è O ( n log n ) , come richiesto. Contare il numero di nodi rimasti al termine della procedura è banale (basta guardare quanti identificatori abbiamo distribuito).O(n)nO(nlogn)