Parte IIIb:Árboles, binario, y binarios de búsqueda

Anuncio
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
Descargar