9. Árboles Una estructura de datos con una relación es una árbol si y sólo: La relación es conexa, es decir, existe una composición de relaciones entre cualquier par de nodos en el árbol. No admite ciclos. Definición: Un árbol T es un conjunto finito de uno o más nodos tal que: (i) hay un nodo especialmente llamado raı́z R; (ii) los demás nodos están particionados en n ≥ 0 conjuntos desconectados T1 . . . Tn , donde cada uno de estos conjuntos es un árbol, ası́ llamados subárboles de la raı́z. Ası́: = {R} ∪ T1 . . . ∪ Tn T Ti ∩ Tn = ∅ (2) Padre: El nodo inmediatamente antecesor Hijos: nodos inmediatamente sucesor Grado: número de hijos de un nodo Nivel: distancia a la raı́z de un nodo Profundidad: nivel máximo de un árbol Bosque: árboles desconectados Formas de visualizar un árbol son: Gráfico: A E K B C F G L D H M Listas: (A(B(E(K,L),F),C(G),D(H(M),I,J))) 51 I J Data 0 A 1 1 1 0 C 0 B 1 0 E 0 F 0 K 0 G 0 0 0 0 0 L H 0 D 1 0 M 0 0 0 I 0 J 0 La representación es esencialmente por listas encadenadas Teoremas: El árbol es conexo pero al eliminar un arco, se convierte en no conexo El árbol no tiene ciclos y es conexo, arcos = nodos − 1 Al agregar un arco a un árbol se crea un ciclo. 10. Árbol Binario Definición: Un árbol binario es un número finito de nodos que es vacı́o o consiste de una raı́z con dos árboles binarios desconectados llamados subárbol izquierdo y derecho. Se llaman los nodos que no tienen hijos, nodos hojas o terminales. Nodos con uno o dos hijos son llamados nodos internos. 52 structureBTREE(ITEM) declare N EW () → btree M AKEBT REE(btree, item, btree) → btree LCHILD(btree) → btree RCHILD(btree) → btree DAT A(btree) → item IS IN (btree, item) → boolean IS EM P T Y (btree) → boolean for all bt1 , bt2 ∈ BT REE, i, i1 , i2 ∈ item let DAT A(N EW ()) ::= error DAT A(M AKEBT REE(bt1 , i, bt2 )) ::= i IS EM P T Y (N EW ()) ::= true IS EM P T Y (M AKEBT REE(bt1 , i, bt2 )) ::= f alse IS IN (N EW (), i) ::= f alse IS IN (M AKEBT REE(bt1 , i1 , bt2 ), i2 ) ::= if i1 = i2 then true else IS IN (bt1 , i2 ) ∨ IS IN (bt2 , i2 ) LCHILD(N EW ()) ::= N EW () LCHILD(M AKEBT REE(bt1 , i, bt2 )) ::= bt1 RCHILD(N EW ()) ::= N EW () RCHILD(M AKEBT REE(bt1 , i, bt2 )) ::= bt2 Lema: El máximo número de nodos en un nivel i de un árbol binario es 2i , i ≥ 0; y el máximo número de nodos en un árbol binario de profundidad k es 2k+1 − 1, k ≥ 0. Prueba Por inducción Base inducción: La raı́z es el único nodo en el nivel 0. Por lo tanto, el máximo número de nodos en el nivel i = 0 es 20 = 2i . Hipótesis de inducción: Para todo j, 0 ≤ j ≤ i, el máximo número de nodos en el nivel j es 2j . Paso de inducción: El máximo número de nodos en el nivel i − 1 es 2i−1 , por la hipótesis de inducción. Debido a que cada nodo del árbol tiene como máximo grado 2, el número de nodos máximos en el nivel i es 2 veces el máximo número de nodos en el nivel i − 1 o 2i . El máximo número de nodos n en un árbol binario de profundidad k es la sumatoria del máximo número de nodos en cada nivel: n= k ! i=0 2i = 2k+1 − 1 Lema: Por cada árbol binario no vacı́o T , si n es el número de nodos terminales (hojas) y m es el número de nodos de grado 2, entonces n = m + 1. Prueba: Sea l el número de nodos de grado uno, entonces el número total de nodos N en el árbol es N = n + m + l. Todos los nodos en el árbol excepto por la raı́z tienen un arco de llegada. Entonces, N es igual al número de arcos B + 1. También, todo arco emana de un nodo de grado uno o de grado dos. Ası́: B = l + 2m. Por lo tanto N = 1 + l + 2m. Substrayendo N = 1 + l + 2m de N = n + m + l nos queda que n = m + 1. 53 10.1. Representación por arreglos Un árbol binario se representa en un arreglo del siguiente modo: si un nodo ocupa la posición i, entonces su hijo izquierdo ocupa la posición 2i y su hijo derecho la posición 2i + 1. Si un árbol es completo con n nodos, por cada nodo i , 1 ≤ i ≤ n, tenemos: PARENT(i) está localizado en *i/2+ si i ,= 1. Cuando i = 1, i es la raı́z y no tiene padre. LCHILD(i) está en 2i si 2i ≤ n. Si 2i > n entonces no tiene hijo izquierdo. RCHILD(i) está en 2i + 1 si 2i + 1 ≤ n. Si 2i + 1 > n, entonces no tiene hijo derecho. Esta forma de representación no es muy usada por problemas de pérdida de espacio. 10.2. Representación por punteros Usando punteros, cada nodo es un registro con tres campos: el dato, el puntero al hijo izquierdo y el puntero al hijo derecho. Caminamiento en un árboles binario T : Procedure IN ORDER(T ) if T ,= nil do [ call IN ORDER(LCHILD(T )) print(DATA(T)) call IN ORDER(RCHILD(T )) ] end IN ORDER Procedure P REORDER(T ) if T ,= nil do [ print(DATA(T)) call P REORDER(LCHILD(T )) call P REORDER(RCHILD(T )) ] end P REORDER Procedure P OST ORDER(T ) if T ,= nil do [ call P OST ORDER(LCHILD(T )) call P OST ORDER(RCHILD(T )) print(DATA(T)) ] end P OST ORDER Si T es un árbol binario con raı́z de n nodos, el caminamiento en el árbol toma Θ(n). Sea T (n) el tiempo que toma el recorrido de un árbol binario de n nodos. El menor tiempo posible es el de un árbol vacı́o T (0) = c para alguna constante positiva c. Para n > 0, suponga que la llamada al recorrido ocurrió en un nodo x raı́z con k nodos en el subárbol izquierdo y n − k − 1 hijos en el subárbol derecho. El tiempo de recorrido es T (n) = T (k) + T (n − k − 1) + d para alguna constante positiva d que refleja el tiempo de ejecución en el nodo raı́z (o sea, el tiempo de las llamadas recursivas esencialmente). Provemos que T (n) = (c + d)n + c. Para n = 0, nosotros tenemos que 54 (c + d)0 + c = c = T (0). Para n > 0 tenemos que: T (n) = T (k) + T (n − k − 1) + d = ((c + d)k + c) + ((c + d)(n − k − 1) + c) + d = (c + d)n + c − (c + d) + c + d = (c + d)n + c (3) Esto demuestra que T (n) = (c + d)n + c y esto es Θ(n). 10.3. Árboles Binarios de Búsqueda Un árbol binario de búsqueda es un árbol binario donde la raı́z es siempre mayor que los nodos del subárbol izquierdo y menor que todos los nodos del subárbol derecho. structureBBTREE(ITEM) declare N EW () → bbtree M BT REE(bbtree, item, bbtree) → btree IN SERT (item, bbtree) → bbtree IS EM P T Y (btree) → boolean IS IN (bbtree, item) → boolean M IN IM U M (bbtree) → item M AXIM U M (bbtree) → item for all bt1 , bt2 ∈ BT REE, i, i1 , i2 ∈ item let IN SERT (i, N EW ()) ::= M BT REE(N EW (), i, N EW ()) IN SERT (i, M BT REE(bbt1 , i, bbt2 )) ::= M BT REE(bbt1 , i, bbt2 ) IN SERT (i, M BT REE(bbt1 , i2 , N EW ())) ::= if i > i2 then M BT REE(bbt1 , i2 , M BT REE(N EW (), i, N EW ())) else M BT REE(IN SERT (i, bbt1 ), i2 , N EW ()) IN SERT (i, M BT REE(N EW (), i2 , bbt2 )) ::= if i < i2 then M BT REE(M BT REE(N EW (), i, N EW ()), i2 , bbt2 ) else M BT REE(N EW (), i2 , IN SERT (i, bbt1 )) IN SERT (i, M BT REE(bbt1 , i2 , bbt2 )) ::= if i < i2 then M BT REE(IN SERT (i, bbt1 ), i2 , bbt2 ) else M BT REE(bbt1 , i2 , IN SERT (i, bbt1 )) IS EM P T Y (N EW ()) ::= true IS EM P T Y (M BT REE(bbt1 , i, bbt2 )) ::= f alse IS IN (N EW (), i) ::= f alse IS IN (M BT REE(bbt1 , i1 , bbt2 ), i2 ) ::= if i1 = i2 then true else if i2 < i1 then IS IN (bt1 , i2 ) else IS IN (bt2 , i2 ) M IN IM U M (N EW ()) ::= error M IN IM U M (M BT REE(N EW (), i, bbt2 )) ::= i M IN IM U M (M BT REE(bbt1 , i, bbt2 )) ::= M IN IM U M (bbt1 ) M AXIM U M (N EW ()) ::= error M AXIM U M (M BT REE(bbt1 , i, N EW ())) ::= i M AXIM U M (M BT REE(bbt1 , i, bbt2 )) ::= M AXIM U M (bbt2 ) 55 Los algoritmos de árboles binarios de búsqeuda son: Búsqueda: Procedure BU SCAR(N, T ) // N es el valor buscado // T es la raı́z del árbol binario de búsqueda if T ,= nil do [ if N = DAT A(T ) then return T else if N < DAT A(T ) then return BU SCAR(N, LCHILD(T )) else BU SCAR(N, RCHILD(T )) ] else return ”no se encontró” end BU SCAR Procedure BU SCAR(N, T ) // no recursivo // N es el valor buscado // T es la raı́z del árbol binario de búsqueda p := T while p ,= nil do [ if N = DAT A(p) then return p else if N < DAT A(p) then p := LCHILD(p) else p := RCHILD(p) ] return ”no se encontró” end BU SCAR En el peor caso, la búsqueda llega hasta la hoja de unárbol pasando por un camino desde la raı́z hata la hoja, es decir, es un camino de largo h, con h siendo la profundidad. Mı́nimo y Máximo: Procedure M IN IM U M (x) // x nodo del árbol binario de búsqueda while LCHILD(x) ,= nil do [ x := LCHILD(x) ] return x end M IN IM U M Procedure M AXIM U M (x) // x es un nodo del árbol binario de búsqueda while RCHILD(x) ,= nil do [ x := RCHILD(x) ] return x end M AXIM U M Ambos Algoritmos tienen complejidad O(h) ya que ambos siguen el camino desde la raı́z a la hoja más izquierda o derecha del árbol. Sucesor y Predecesor: 56 Procedure SU CESSOR(x) // x es un nodo en el árbol //P AREN T (x) obtiene el padre de x if RCHILD(x) ,= nil then return M IN IM U M (RCHILD(x)) y := P AREN T (x) while y ,= nil and x = RCHILD(y) do [ x := y y := P AREN T (y) ] return y end SU CESSOR Si un nodo x tiene un subárbol derecho, el sucesor y es el mı́nimo valor del subárbol derecho. Si un nodo no tiene subárbol derecho y tiene sucesor y, entonces, el sucesor y es el menor ancestor de x cuyo hijo izquierdo es también un ancestor de x. La complejidad del algoritmo es O(h) porque sigue el camino hacia una hoja del subárbol derecho o sigue hacia arriba el camino hasta la raı́z El algoritmo P redecessor(x) es simétrico y con el mismas complejidad. Insertar y Borrar: Procedure IN SERT AR(P, T ) // P es el nodo a insertar // T es la raı́z del árbol binario de búsqueda if DAT A(T ) = DAT A(P ) then return existe else if DAT A(P ) < DAT A(T ) then [ if LCHILD(T ) = nil then [ LCHILD(T):= P return ] else IN SERT AR(P, LCHILD(T ))] else if RCHILD(T ) = nil then[ RCHILD(T):= P return ] else IN SERT AR(P, RCHILD(T )) end IN SERT AR En el peor caso, la inserción en un árbol se hace en un nodo hoja, lo que queda dado por la profundidad del árbol O(h)antes de insertar. Ojo, al insertar, esta profundidad puede variar. 57 Procedure DELET E(P, T ) // P es el nodo a eliminar // T es la raı́z del árbol binario de búsqueda if LCHILD(P ) = nil and RCHILD(P ) = nil then y := P else y := SU CCESSOR(P ) if LCHILD(y) ,= nil then x := LCHILD(y) else x := RCHILD(y) if x ,= nil then [ P AREN T (x) := P AREN T (y) if P AREN T (y) = nil then T := x else if y = LCHILD(P AREN T (y)) then LCHILD(P AREN T (y)) := x else RCHILD(P AREN T (y)) := x if y ,= x then DATA(P) := DATA(y) copia de información de punteros return y end DELET E Para borrar se consideran 3 casos. Si P no tiene hijos, modifique el padre de P para reemplazar P por nil como su hijo. N N H T B A L D K R M H U T B P A I L D K I J J 58 R U Si P tiene sólo un hijo, se divide P haciendo un nuevo enlace entre su hijo y su padre. N N H T B A L D H R K M U T B P A L D R I M I U P J J Finalmente, si el nodo tiene dos hijos, se divide el sucesor de P , y, el cual no tiene hijo izquierdo y se reemplaza la clave de P y sus punteros con la clave de y y sus información de punteros. N N H B A L D I T K R M B U A P T L D K R M U P J I J Problema: La eficiencia de un método depende de que el árbol de búsqueda esté balanceado. Entonces, ¿ cómo mantener el árbol balanceado? Definición: en un árbol de búsqueda balanceado (AVL) la profundidad del subárbol izquierdo es igual a la profundidad del subárbol derecho ±1. A su vez, esta profundidad es satisfecha recursivamente por los subárboles. Por ejemplo: 59 M H T B A J R D U P Supongamos que tenemos un árbol binario de búsqueda balanceado y le insertamos un nuevo nodo. El caso simple es cuando el árbol continua siendo balanceado. B B C C A El problema sucede cuando al insertar el nodo, se desbalancea el árbol. Eso sucede en dos caso posibles: B B C C La solución es rotar el árbol una vez insertado el nodo. En el primer caso, la rotación es simple y se hace en el sentido contrario al desbalanceo. El segundo caso, es necesario hacer dos rotaciones ya que el desbalanceo se produce en el sentido contrario a la inserción: 60 B C C + D B D A B C C A + B En ambos casos, los árboles resultantes mantienen la profundidad inicial. Otra situación a considerar es al borrar un nodo de un árbol que está balanceado y que se desbalancea. B B A C D A D C Al igual que para inserción se deben realizar rotaciones. A diferencia de cuando se inserta, sin embargo, los árboles resultantes pueden cambiar su profundidad y por lo tanto, no se puede garantizar que el balanceo del subárbol mantenga el rbol que lo contenga balanceado. En resumen: Al insertar: 1. usar búsqueda binaria para ver donde insertar, e insertar el nodo 2. desde allı́ hacia arriba, chequear donde ocurre el desbalanceo. 3. hacer la rotación adecuada. Al eliminar: 1. borrar el nodo 2. ubicar punto de desbalanceo (único si existe) 3. hacer la rotactión correspondiente 61 4. si cambió profundidad volver al punto 2 Por ejemplo, en la siguiente figura, al ingresar la clave 20 se producen dos desbalances. La rotación sobre el nodo con valor 11 soluciona sólo un debalance. 7 4 3 11 6 9 2 8 18 14 12 19 17 22 20 rotación izquierda 7 4 3 18 6 11 2 8 9 19 14 12 22 17 20 Sin embargo, al tomar el desbalance mas profundo (más cercano a la hoja donde se inserta el elemento) y su balanceo, el árbol vuelve a estar balanceado. 62 7 4 11 3 6 9 2 18 8 14 19 12 17 22 20 rotación doble 7 4 18 3 6 11 2 8 9 20 14 12 19 22 17 Algoritmos de rotación: Asume que X tiene hijo derecho. right rotation(T,y) Y γ X α left rotation(T,y) β X α Y β 63 γ Procedure LEF T ROT AT ION (T, x) // x nodo de rotatción // T es la raı́z del árbol binario de búsqueda y := RCHILD(x) RCHILD(x) := LCHILD(y) if LCHILD(y) ,= nil then P AREN T (LCHILD(y)) := x P AREN T (y) := P AREN T (x) if P AREN T (x) = nil then T := y else if x = LCHILD(P AREN T (x)) then LCHILD(P AREN T (x)) := y else RCHILD(P AREN T (x)) := y LCHILD(y) := x PARENT(x) := y end LEF T ROT AT ION (T, x) 64 Ejercicios de árboles binarios y árboles binarios de búsqueda 1. Combinando preorden con inorder. Dado una secuencia de un árbol binario en preorden y otra en inorden, es posible definir el único árbol que las define. Suponga la siguiente secuencia en preorden: A B C D E F G H I y la secuencia en inorden: B C A E D G H F I, se puede construir el árbol binario haciendo que la primera letra en preorden sea la raı́z y al consider la definición en inorden de que todos los antecesores de A sean los hijos izquierdo y los restantes en el lado derecho. Eso nos queda: A B,C D,E,F,G,H,I Moviéndose a continuación de la secuencia en preorden está B como la próxima raı́z y por la secuencia en inorden se tiene que B no tiene hijo izquierdo y un subárbol derecho con C. A B D,E,F,G,H,I C Continuando de esta manera, se llega a: A B D C E F G J H Se puede decir que cada árbol binario tiene un único par de secuencias pre-orden-inorden. 2. Contando árboles binarios. Para 0 o 1 nodo , se tiene 1 posible árbol binario. 65 Para 2 nodos, se tiene 2 posibles árboles binarios. Para 3 nodos, se tienen 5.. Para ¿n? Si los nodos de un árbol son enumerados en una secuencia de preorden de 1 . . . n, entonces por el ejercicio anterior, distintos árbol binarios definen distintas secuencias en inorden (permutaciones en inorden). Usando el concepto de permutación en inorden, es posible mostrar que el número total de distintas permutaciones pasando los 1 . . . n nodes a través de una pila y borrándolas en todas las formas posibles es igual al número total de árboles binarios con n nodos. Comenzemos con 3 nodos (1 . . . 3), las posibles secuencias son: 1,2,3;3,2,1;2,1,3;2,3,1;3,2,1 Cada una de estas permutaciones corresponde a uno de los 5 árboles posibles: 1 1 1 1 2 2 2 2 2 3 3 3 1 3 3 En forma más general, queremos una expresión bn de los números de árboles binarios distintos con n nodos. Se puede deducir que bn es la suma de todos los posibles árboles binarios de la siguiente forma, una raı́z y dos subrboles bi , bn−i−1 : bn = ! 0≤i≤n−1 bi bn−i−1 , n ≥ 1 ∧ b0 = 1 Con recurrencia B(x) = ! bi xi i≥0 con B(0) = b0 = 1 √ 1 − 1 − 4x B(x) = 2x Por teorema binomial # ! " 1/2 B(x) = (−1)m 22m+1 xm m+1 m≥0 66 Comparando las ecuaciones anteriores, bn es el coeficiente de xn en B(x): " # 2n n 4n bn = O( 3/2 ) n 1 bn ) = n+1 3. Extensión de la definición algebraica de un árbol binario. Defina las funciones: P REORDER(btree) → queue IN ORDER(btree) → queue P OST ORDER(btree) → queue usando la definición AP P EN D(queue, queue) → queue Defina, además, una función que realice el SW AP BT REE(btree) → btree que invierte los hijos izquierdos y derechos en forma recursiva y la función que entrega el número de nodos hojas de un btree N U M LEAV ES(btree) → int. P REORDER(CREAT E) ::= EM P T Y QU EU E P REORDER(M AKEBT (l, d, r)) ::= AP P EN D(AP P EN D(ADDQ(EM P T Y QU EU E, d), (P REORDER(l))), (P REORDER(r))) IN ORDER(CREAT E) ::= EM P T Y QU EU E IN ORDER(M AKEBT (l, d, r)) ::= AP P EN D(AP P EN D((IN ORDER(l)), ADDQ(EM P T Y QU EU E, d)), (IN ORDER(l))) P OST ORDER(CREAT E) ::= EM P T Y QU EU E P OST ORDER(M AKEBT (l, d, r)) ::= AP P EN D(AP P EN D((P OST ORDER(l)), (P OST ORDER(l))), ADDQ(EM P T Y QU EU E, d)) SW AP BT REE(CREAT E) ::= CREAT E SW AP BT REE(M AKEBT (l, d, r)) ::= M AKEBT (SW AP BT REE(r), d, SW AP BT REE(l)) N U M LEAV ES(CREAT E) ::= 0 N U M LEAV ES(M AKEBT (l, d, r)) ::= if ISEM T BT (l) and ISEM T BT (r) then 1 else N U M LEAV ES(l) + N U M LEAV ES(r) 4. Árboles Completos: Un árbol con altura h es completo si tiene todos los niveles llenos hasta el nivel h (ojo, se asume aquı́ que a la altura h, el nivel es h + 1) . El nivel h + 1 está lleno de izquierda a derecha y a partir de un punto en adelante está vacı́o. Escriba un algoritmo que indique si el árbol está o no lleno. 5. Árboles atados: Árboles atados son aquellos donde los nodos nulos de un árbol binario tradicional son usados para asociar el nodo al nodo sucesor o antecesor en el recorrido del árbol en un determinado orden (INORDER, POSTORDER, PREORDER). En la siguiente figura se muestra un árbol atado para un recorrido en inorder que apoya los algoritmos de sucesor o predecesor. Algunos algoritmos para estos árboles son: 67 I 0 D A H C B E punteros adicional punteros originales F G Figura 20: Árboles Atados Encontrar el sucesor en inorder de un nodo N en un árbol atado. Procedure SU CESSOR(var N odeN ) //Sea N el nodo en un árbol atado en inorder para el cual se desea encontrar el sucesor. // N retornará el valor del sucesor N := RLIN K(N ) if N es un puntero de atadura o N = nil then termina while LLIN K(N ) no sea un puntero de atadura do N := LCHILD(N ) endSU CESSOR Recorrer en inorder un árbo atado en inorder Procedure W ALK(N odeR) //Sea R la raı́z del árbol atado N := R while LLIN K(N ) ,= nil do N := LCHILD(N ) Visite N N := SU CCESOR(N ) while N ,= nil do [ visite N N := SU CCESOR(N )] endW ALK Insertar un subárbol atado T1 como subárbol derecho de un nodo N en otro subárbol atado T2 . Procedure IN SERT AR(var nodeN, nodeRT1 ) // RT1 es la raı́z de T1 R := RT1 S := N repeat S := RCHILD(S) until S es una atadura o S = nil RLIN K(N ) := R Q := R while RLIN K(Q) no sea una atadura y RLIN K(Q) ,= nil do Q := RLIN K(Q) RLIN K(Q) := S Q := R while LLIN K(Q) no sea una atadura y LLIN K(Q) ,= nil do Q := LLIN K(Q) LLIN K(Q) := N endIN SERT AR 68