Solución de tiempo amortizado
Cuando se realiza una operación , se visitan todos los ancestros de hasta llegar a la raíz : . Para agilizar las futuras operaciones de , no nos cuesta nada colgar a todos los nodos del camino, , directamente de (por ejemplo, puede hacerse a la vuelta de la recursión). Así, las futuras operaciones tomarán tiempo . Asimismo, se agilizarán los sobre otros descendientes de algún .
--> imagen
¿Qué impacto tiene esta mejora sobre los tiempos de ? En el peor caso, ninguno, pues si bien una aplicación de mejora el tiempo de las siguientes operaciones , el primer puede costar (por ejemplo, si hacemos y luego el primer ). Necesitamos entonces realizar un análisis amortizado.
Considere una secuencia de operaciones y , y llamemos a la secuencia sin las operaciones . Definiremos el rango de un nodo , , como la altura del subárbol luego de realizar las operaciones de (o bien, de aplicar pero sin la mejora que acabamos de describir para ). Hablaremos del rango de los nodos mientras analizamos la secuencia verdadera , pero debe recordar que es fijo e independiente del punto de que estemos considerando.
Una propiedad importante es que, como vimos en la subsección anterior, un nodo de rango tiene al menos nodos en su subárbol (el que resulta de aplicar ). Como, en estos árboles, dos nodos y de rango no pueden descender uno del otro (pues entonces uno sería más alto que el otro), sus subárboles deben ser disjuntos. Por lo tanto, no puede haber más de nodos de rango .
Otra propiedad importante es que, si en algún momento de , desciende de , entonces . Esto ocurre porque sólo la operación crea nuevas descendencias (al colgar de , todo descendiente de pasa a ser también descendiente de ), mientras que sólo la operación destruye descendencias (al colgar todos los directamente de , los descendientes de dejan de ser descendientes de ). Por lo tanto, en , donde se han eliminado los , también se hará descendiente de y se mantendrá así hasta el final. Como desciende de al final de , debe ser .
Para nuestro análisis, definiremos la función como y . Esta función crece muy rápidamente:
Llamaremos a la inversa de , . La función también se llama , y es la cantidad de veces que debemos tomar logaritmo (base 2 en nuestro caso) a para que sea . En la práctica, vale para cualquier razonable:
Dividiremos a los nodos en grupos: el nodo pertenecerá al grupo . Dicho de otro modo, si observamos el bosque que resulta de aplicar , los nodos de altura 0 y 1 (hojas y padres de sólo hojas) son del grupo , los nodos de altura 2 son del grupo , los de altura 3 y 4 son del grupo , los de altura 5 a 16 son del grupo , etc.
--> imagen
Con estas definiciones ya podemos presentar el análisis amortizado que haremos. Usaremos contabilidad de costos. La operación cuesta , por lo que no necesitamos considerarla. Consideraremos que la operación cuesta 1 por cada nodo que atravesamos en el camino desde hasta la raíz . Este costo, para el análisis, lo repartiremos entre la operación Find misma y los nodos que atravesamos, de la siguiente forma:
- Si, al momento de la operación, el nodo es la raíz de su árbol, o es hijo de la raíz , le cobramos a .
- Si, al momento de la operación, el nodo tiene distinto grupo que su padre, le cobramos a .
- De otro modo, le cobramos al nodo por el que pasamos.
--> imagen
Note que, cuando recorremos , como cada desciende de , vimos que debe valer , y por lo tanto . Eso significa que cada vez que el grupo de es distinto del de su padre , el valor del grupo debe aumentar. Como el máximo rango posible es , los grupos posibles van desde 0 hasta , y entonces en el camino de a el valor del grupo puede aumentar sólo veces. Sumando que paga por la raíz y su hijo , tenemos que en cada operación nuestra contabilidad de costos le cobra a a lo más .
Debemos ver ahora cuánto les cobramos a los nodos. Note que, como hemos definido la contabilidad, un nodo que paga adquiere un nuevo padre gracias a la mejora que hace . Este nuevo padre es un ancestro del padre actual, por lo que su rango es estrictamente mayor. Por lo tanto, cada vez que un nodo paga, adquiere un padre de mayor rango. Una vez que adquiere un padre cuyo rango es de un grupo mayor al del nodo, el nodo no pagará nunca más, pues nunca volverá a tener un padre de su mismo grupo (sólo puede seguir adquiriendo padres de mayor y mayor rango).
¿Cuántas veces puede pagar un nodo hasta adquirir un padre de un grupo superior? Si está en el grupo , y su rango sube sólo de a 1 unidad por vez, puede pagar veces hasta que su padre pertenezca al grupo . Digamos para simplificar que los nodos de grupo pueden pagar unidades en total. ¿Cuántos nodos hay de grupo ? Digamos que son , con , donde hay nodos de rango . Como vimos que , tenemos que
Es decir, tenemos nodos del grupo , y cada uno de ellos paga a lo más a lo largo de su vida. En total, entre todos los nodos del grupo pagan a lo más . Como existen grupos distintos, entre todos los nodos pagan . Por lo tanto, si se realizan operaciones de , el costo amortizado de es , mientras que su costo de peor caso es . El costo de los es siempre .