Cap. 14. Árboles enhebrados

Anuncio
1
Capítulo 14.
Árboles enhebrados. Threaded tree.
Las operaciones de recorrido en un árbol binario de búsqueda, implementadas mediante
funciones recursivas o con un stack de los nodos a revisar, son generalmente costosas en tiempo
de ejecución.
Para lograr recorridos eficientes en un árbol puede modificarse la estructura del nodo agregando
un puntero al padre, o bien agregando un par de punteros al sucesor y predecesor formando de
este modo listas doblemente enlazadas.
Una alternativa, que demanda menos bits en cada nodo, es emplear un puntero derecho con
valor nulo, para apuntar al nodo sucesor de éste, y similarmente emplear un puntero izquierdo
nulo para apuntar a su predecesor; obviamente esto requiere dos bits adicionales por nodo para
indicar si el puntero referencia a un nodo hijo o al nodo sucesor o antecesor. Estos árboles se
denominan enhebrados (threaded). La idea es utilizar de mejor forma los (n+1) punteros que
tienen almacenados valores nulos en un árbol con n elementos.
Si sólo interesa acelerar la operación de encontrar al sucesor se emplean hebras solamente en los
punteros derechos de las hojas, para hilvanar los nodos con sus sucesores, dando origen a
árboles enhebrados por la derecha (right-threaded tree). Situación que será analizada en este
capítulo.
Los recorridos en árboles enhebrados siguen siendo de costo O(n) pero existirá un considerable
ahorro en tiempo al poder diseñar rutinas iterativas.
3
2
1
6
8
4
5
7
9
Figura 14.1 Árbol con hebras por la derecha.
Profesor Leopoldo Silva Bijit
20-01-2010
2
Estructuras de Datos y Algoritmos
La Figura 14.1 muestra los punteros que son hebras a los sucesores. La operación sucesor de un
nodo tiene en muchos casos ahora costo O(1); salvo los nodos con hijo derecho que son de costo
O(log(n)). La función predecesor es de costo O(log(n)), ya que debe efectuarse un recorrido
desde la raíz, si el nodo no tiene descendiente izquierdo; es el caso de los nodos con valores 1,
4, 5, 7, y 9 en la Figura 14.1. El nodo con mayor valor en el árbol tiene hebra con valor nulo, lo
que indica que no tiene sucesor.
Debido a que ahora cada nodo puede tener dos punteros que lo referencian, el del padre y el de
su antecesor, las operaciones de inserción y descarte resultan levemente más complejas, para
mantener el árbol correctamente hilvanado.
Si un nodo tiene hijo derecho, su sucesor pertenecerá al subárbol derecho y será un nodo sin hijo
izquierdo; es decir el sucesor será el menor descendiente del subárbol derecho. Un nodo tiene
una hebra derecha apuntándolo si y sólo si tiene hijo izquierdo, es el caso de los nodos con
valores 2, 3, 6, y 8 en la Figura 14.1.
14.1. Análisis de las operaciones.
14.1.1. Buscar.
La búsqueda resulta una operación asimétrica, ya que debemos distinguir si el puntero derecho
referencia a un hijo o es una hebra.
La búsqueda falla si al descender por la izquierda se llega a un puntero nulo, o si se llega a una
hebra descendiendo por la derecha. En forma excepcional la búsqueda falla si el árbol está
vacío.
14.1.2. Inserción.
Como es usual se inserta en una hoja. Se denomina ndes al campo que describe si el puntero
derecho apunta a un HIJO o a una HEBRA. El nombre de la variable abrevia: el nodo derecho
es.
Si p apunta a la hoja donde debe insertarse el nuevo nodo, y t es el puntero que indica la
posición para insertar, pueden ocurrir dos situaciones.
a) Inserción en hoja, descendiendo por la izquierda.
Se inserta el nuevo nodo y su puntero derecho se marca como hebra, apuntando a p.
p->left=t; t->ndes=HEBRA; t->right=p;
p
p
t
t
Figura 14.2 Inserción en hoja, descendiendo por la izquierda.
Profesor Leopoldo Silva Bijit
20-01-2010
Árboles enhebrados por la derecha
3
El nuevo nodo tendrá como sucesor al apuntado por p.
b) Inserción en hoja, con hebra derecha.
Es preciso marcar como hebra el nuevo nodo, y copiar el valor del puntero derecho de p, en el
nuevo nodo, hilvanándole el sucesor. Además debe marcarse que p ahora apunta a un hijo
derecho, y enlazar p con el nuevo nodo.
t->right=p->right; t->ndes=HEBRA; p->right=t; p->ndes=HIJO;
p
p
t
t
Figura 14.3 Inserción en hoja, descendiendo por la derecha.
El nuevo nodo tendrá clave mayor que la del apuntado por p, y menor que la apuntada por la
hebra de p; por esta razón el nuevo nodo tiene como sucesor al sucesor de p, antes de la
inserción.
14.1.3. Descarte.
En el descarte de un nodo en un árbol binario de búsqueda suelen analizarse los tres siguientes
casos: nodo que será descartado es hoja, el nodo tiene un hijo o tiene dos hijos. Sin embargo en
estos árboles enhebrados la clasificación de los casos conviene efectuarla considerando si el
nodo que será descartado tiene o no hijo izquierdo.
Esto es así, ya que si no hay hijo izquierdo no puede existir hebra que lo apunte, y no será
necesario efectuar escrituras en punteros para mantener el árbol correctamente hilvanado.
Si existe hijo izquierdo, existirá un puntero, el del antecesor, que referencia al que será
descartado y que debe ser actualizado.
Sea t el nodo que será descartado y p su padre.
Primero analizamos los casos a) y b) en los cuales t no tiene hijo izquierdo. Los casos c) y d)
serán para un nodo t que tiene hijo izquierdo. Los comentarios de fin de línea asocian los casos
con los de descarte en un árbol binario de búsqueda.
if(t->left==NULL)
if(t->ndes==HIJO) caso a) //un hijo
else caso b) //hoja
else
if(t->left->ndes==HEBRA) caso c) //dos hijos o hijo izquierdo y hebra derecha
else caso d) //dos hijos o hijo izquierdo y hebra derecha
Si el nodo que debe descartarse es la raíz, será preciso actualizar el árbol, para que apunte a la
nueva raíz. En este caso no existe p, el padre de t; sin embargo para evitar escribir códigos para
tratar el caso particular, se inicia p, apuntando a un nodo, denominado centinela, cuyo hijo
derecho apunta a t. Esto se desarrolla en 14.4.
Profesor Leopoldo Silva Bijit
20-01-2010
4
Estructuras de Datos y Algoritmos
a) Nodo t con hijo derecho que tiene hijo izquierdo nulo.
Basta escribir en p el descendiente derecho de t. En la Figura 14.4 se ilustra el caso en que t es
descendiente derecho de p.
if ( t==p->right) p->right = t->right; else p->left=t->right; free(t);
p
p
t
t
x
x
Figura 14.4 Descarte de nodo t con un hijo derecho x.
Ejemplo de este caso es el descarte del nodo con valor 4 en la Figura 14.1.
b) Nodo t con hebra derecha e hijo izquierdo nulo.
En este caso t es hoja con hebra derecha.
b1) Si t es descendiente derecho de su padre p, se debe marcar que p tiene hebra derecha, y
copiar en el puntero derecho de p, la dirección del sucesor de t.
p
p
s
s
t
t
Figura 14.5 Descarte de hoja t con un hebra derecha. Descendiente derecho de p.
b2) Si t es descendiente izquierdo de su padre p, se debe apuntar a nulo en el puntero izquierdo
de p.
p
t
p
s
t
s
Figura 14.6 Descarte de hoja t con hebra derecha. Descendiente izquierdo de p.
Profesor Leopoldo Silva Bijit
20-01-2010
Árboles enhebrados por la derecha
5
Las dos situaciones se resumen en las acciones:
if (t==p->right) {p->right=t->right; p->ndes=HEBRA} else p->left=t->left; free(t);
c) Nodo t tiene hijo izquierdo l, el que a su vez tiene hebra derecha.
Se copia en el hijo derecho de l, el hijo derecho de t. Se copia la marca del tipo de puntero
derecho del nodo t en el nodo l. Finalmente se pega l como descendiente de p.
p
p
t
t
b
l
s
l
a
b
a
Figura 14.7 Descarte de nodo t con hijo izquierdo. Descendiente izquierdo con hebra.
l->right = t->right; l->ndes = t->ndes;
if (t==p->right) p->right = l; else p->left=l; free(t);
Cualquier hebra del subárbol izquierdo de l apunta dentro del subárbol o a l.
La Figura 14.7 muestra a t como descendiente izquierdo de p. Si t es descendiente derecho de p,
se trata igual.
d) Nodo t tiene hijo izquierdo l, el que a su vez tiene hijo derecho.
Este caso se trata en forma similar al del descarte de un nodo con dos hijos, eligiendo la
solución de encontrar el nodo a, el mayor descendiente del subárbol izquierdo de t, que en este
caso es su antecesor. Como puede comprobarse mediante un análisis similar, la solución
alternativa de encontrar el menor descendiente del subárbol derecho de t, tiene un costo mayor.
p
p
t
c
t
c
l
b
l
b
a
a
Figura 14.8 Descarte de nodo t con hijo izquierdo. Descendiente izquierdo con hijo.
Profesor Leopoldo Silva Bijit
20-01-2010
6
Estructuras de Datos y Algoritmos
El primer paso es encontrar el nodo a, el antecesor de t. Para lo cual se desciende por el hijo
derecho de l, siempre por la derecha hasta encontrar un nodo que tenga hebra derecha. Se
desplaza el puntero l, apuntando siempre al padre de a.
for (;;) { a = l->right; if (a->ndes == HEBRA) break; l = a; }
Se actualiza el nodo l, dependiendo de si a tiene o no hijo izquierdo.
d1) Nodo antecesor a, tiene hijo izquierdo.
p
p
t
c
t
c
b
a
l
b
l
a
Figura 14.8 Nodo antecesor a con hijo izquierdo.
Se pega subárbol izquierdo de a, como hijo derecho de l.
d2) Nodo antecesor a, no tiene hijo izquierdo.
p
p
t
c
t
c
b
a
l
b
l
a
Figura 14.9 Nodo antecesor a sin hijo izquierdo.
Se escribe en el hijo derecho de l la dirección de a, y se marca como hebra.
if (a->left != NULL) l->right = a->left;
else { l->right = a; l->ndes = HEBRA; }
Profesor Leopoldo Silva Bijit
20-01-2010
Árboles enhebrados por la derecha
7
Luego se copian los punteros de t en los punteros de a, además se copia la marca del tipo de
puntero derecho del nodo t en el nodo a.
p
p
t
t
a
a
c
b
c
b
l
l
Figura 14.10 Nodo antecesor a reemplaza al nodo t.
a->left = t->left; a->right = t->right; a->ndes = t->ndes;
Finalmente se pega, según si t es descendiente izquierdo o derecho de su padre p, la dirección
del antecesor a.
if ( t==p->right) p->right = a ; else p->left=a; free(t);
Esta solución no modifica las hebras de los descendientes del subárbol c, ni del subárbol b.
Si el nodo t es la raíz, debe modificarse ésta para apuntar ahora al descendiente derecho del
centinela, que es apuntado por p.
El descarte en un árbol enhebrado por la derecha es muy similar al descarte en árboles binarios
de búsqueda, difieren en las instrucciones que escriben el tipo de enlace derecho y en la
determinación de uno de los casos, por esta razón suele implementarse árboles enhebrados por
la derecha y no los árboles hilvanados con sucesores y antecesores.
14.1.4. Recorrido en orden.
a) Se inicia en el nodo ubicado más a la izquierda del árbol, se muestra el valor de la clave y se
sigue su hebra derecha.
b) Si se sigue una hebra a la derecha se muestra el valor de la clave del sucesor y se continúa
por el enlace derecho de éste.
c) Si se sigue un enlace a un hijo a la derecha, primero se desciende hasta el nodo ubicado más a
la izquierda, se imprime su clave y se continúa en b).
La impresión en orden muestra las claves en forma ascendente.
Si se tiene un arreglo y se insertan las claves en un árbol enhebrado por la derecha, luego con
un recorrido en orden puede generarse el arreglo ordenado ascendentemente.
Profesor Leopoldo Silva Bijit
20-01-2010
8
Estructuras de Datos y Algoritmos
14.2. Estructura de datos y funciones básicas.
14.2.1. Estructura de datos.
#define HEBRA 1
#define HIJO
0
typedef struct tnode
{ int clave;
int ndes; //nodo derecho es: Hebra o Hijo
struct tnode *left; struct tnode *right;
} nodo, * pnodo;
14.2.2. Creación de un nodo.
pnodo getnodo(int dato)
{
pnodo p=NULL;
if ( (p= (pnodo) malloc(sizeof(nodo))) == NULL) return(NULL);
else
{ p->clave=dato; p->left=NULL; p->right=NULL; p->ndes=HIJO; }
return(p);
}
14.2.3. Mostrar nodo y el árbol enhebrado en niveles.
Se muestra la letra H, luego de la clave del nodo, si éste tiene hebra.
void printNodo(pnodo t, int h)
{ int i;
for(i=0; i<h; i++) putchar('\t'); //se emplean tabs para desplegar niveles.
if (t==NULL) {putchar('*') ; putchar('\n') ;}
else printf("%d", t->clave);
if (t->ndes==HEBRA) printf("H\n"); else putchar('\n') ;
}
void Mostrar(pnodo t, int h)
{
if (t==NULL) printNodo(t, h);
else
{if (t->ndes==HIJO) Mostrar(t->right, h+1) ;
printNodo(t, h);
Mostrar(t->left, h+1);}
}
Profesor Leopoldo Silva Bijit
20-01-2010
Árboles enhebrados por la derecha
9
14.2.4. Recorrido en orden.
pnodo MasIzquierdista(pnodo t)
{
if (t == NULL) { return NULL;}
while (t->left != NULL) t = t->left;
return t;
}
void InOrden( pnodo t)
{ pnodo p = MasIzquierdista(t);
while (p != NULL)
{ printf(" %d ", p->clave);
if (p->ndes==HEBRA) p = p->right;
else p = MasIzquierdista(p->right);
}
putchar('\n');
}
14.2.5. Sucesor.
pnodo sucesor(pnodo t)
{
If (t==NULL) return NULL;
if (t->ndes==HIJO) return( MasIzquierdista(t) );
else if (t->right ==NULL) return NULL; //no hay sucesor
else return(t->right);
}
14.2.6. Buscar.
pnodo buscar(pnodo t, int valor) /* algoritmo iterativo */
{
while ( t != NULL)
{
if ( t->clave == valor ) return (t);
else if (t->clave > valor) t = t->left;
else if (t->ndes==HIJO) t = t->right; //asimétrico por la derecha
else return (NULL);
}
return (t); /* NULL No lo encontró*/
}
14.3. Insertar.
typedef enum {left, right, vacio} modo; //modos de descenso
Profesor Leopoldo Silva Bijit
20-01-2010
10
Estructuras de Datos y Algoritmos
pnodo Insertar(pnodo *praiz, int valor)
{ pnodo t= *praiz; //se pasa la raíz por referencia, para modificarla si el árbol está vacío.
pnodo p; //mantiene un puntero al padre del que será insertado
modo porlado=vacio; //dirección en el descenso.
if (t==NULL) //inserta en la raíz
{ p=getnodo(valor);
if (p!=NULL) {*praiz=p; p->ndes=HEBRA;}
return(p);
}
while ( t != NULL)
{
if ( t->clave == valor )
{/*lo encontró, no inserta. No se aceptan claves repetidas en conjuntos*/
return (t); //devuelve el encontrado.
}
else
{ p=t ;
if (t->clave > valor) {t = t->left; porlado=left;}
else
{ porlado=right;
if(t->ndes==HIJO) t = t->right;
else break;
}
}
}
/*Al salir del while p apunta al nodo donde se insertará el nuevo, y porlado la dirección */
/* El argumento t apunta a NULL o al sucesor de p */
t = getnodo(valor); //se pega el nuevo nodo en t.
if(t==NULL) return(NULL);
if (porlado==left)
{p->left=t; t->ndes=HEBRA; t->right=p;}
else if(porlado==right)
{t->right=p->right; t->ndes=HEBRA; p->right=t;p->ndes=HIJO;}
return (t); /* Apunta al recién insertado. Null si no se pudo insertar*/
}
14.4. Descartar.
En la Figura 14.11, se muestran los argumentos y variables locales de la función Descartar,
luego de iniciadas. Raíz es una variable estática que referencia al árbol. Se ha dibujado sólo el
nodo raíz del árbol.
Profesor Leopoldo Silva Bijit
20-01-2010
Árboles enhebrados por la derecha
11
praiz
p
raiz
centinela
t
Figura 14.11 Argumentos y variables locales luego de ser iniciadas.
int Descartar(pnodo *praiz, int valor)
{ pnodo t= *praiz; //se pasa la raíz por referencia, para modificarla si el árbol queda vacío.
nodo centinela;
pnodo p=&centinela; //mantiene un puntero al padre del que será descartado
p->ndes=HIJO; p->left=NULL; p->right=t; //inicia centinela
if (t==NULL) return(0); //no puede descartar en árbol vacío
for ( ;; )
{
if (t->clave > valor)
{ if(t->left==NULL) return(0); else {p=t;t = t->left;}
//porlado=left;
}
else if (t->clave < valor)
{ if(t->ndes==HIJO) {p=t; t = t->right;} else return(0);
//porlado=right;
}
else break; //lo encontró
}
/*Al salir del for p apunta al padre de t */
/* t apunta al nodo que será descartado */
if(t->left==NULL)
{
if(t->ndes==HIJO) //caso a) un hijo
{ if ( t==p->right) p->right = t->right; else p->left=t->right; }
else //caso b) hoja
{ if (t==p->right) {p->right=t->right; p->ndes=HEBRA;} else p->left=t->left; }
}
else
{ pnodo l= t->left; //variable local al bloque para hijo izquierdo de t.
if(t->left->ndes==HEBRA) //caso c) dos hijos o hijo izquierdo y hebra derecha
{ l->right = t->right; l->ndes = t->ndes;
Profesor Leopoldo Silva Bijit
20-01-2010
12
Estructuras de Datos y Algoritmos
if (t==p->right) p->right = l; else p->left=l;
}
else //caso d) dos hijos o hijo izquierdo y hebra derecha
{ pnodo a; //variable local al bloque para el antecesor de t
for (;;) { a = l->right; if (a->ndes == HEBRA) break; l = a; }
if (a->left != NULL) l->right = a->left;
else { l->right = a; l->ndes = HEBRA; }
a->left = t->left; a->right = t->right; a->ndes = t->ndes;
if ( t==p->right) p->right = a ; else p->left=a;
}
}
if (t==*praiz) *praiz= p->right; //cambio de raíz
free(t);
return(1); //descarte exitoso
}
14.5 Test de estructura de árbol enhebrado por la derecha.
La siguiente función recursiva aplica las propiedades que deben cumplir los nodos de un árbol
enhebrado por la derecha y es útil para verificar el diseño de las funciones.
//retorna 1 si es rtbst
int testrtbst(pnodo t)
{ int l=1, r=1;
//Si arbol está vacío, es rtbst
if (t==NULL) return 1;
//test subarbol izq
if (t->left != NULL)
{ if (t->clave > t->left->clave) l=testrtbst(t->left);
else l=0;
}
//test subarbol der
if (t->ndes==HIJO)
{ if (t->right != NULL)
{if (t->clave < t->right->clave) r=testrtbst(t->right);
else r=0;
}
else r=0; //no puede haber hijo con puntero derecho nulo
}
else
//es hebra
{
if (t->right!=NULL)
{ if (sucesor(t) == t->right) ; //debe ser el sucesor de t
else r=0;
}
Profesor Leopoldo Silva Bijit
20-01-2010
Árboles enhebrados por la derecha
13
else
if (sucesor(t)==NULL); //debe ser el máximo
else return(0);
}
if ( (l+r)==2) return(1) ; else return(0);
}
Referencias.
Knuth, D. E., “The Art of Computer Programming, Volume 1: Fundamental Algorithms”,
section 2.3.1. 3rd ed. Addison-Wesley, 1997. ISBN 0-201-89683-4.
Profesor Leopoldo Silva Bijit
20-01-2010
14
Estructuras de Datos y Algoritmos
Índice general.
CAPÍTULO 14. ...........................................................................................................................................1
ÁRBOLES ENHEBRADOS. THREADED TREE. .................................................................................1
14.1. ANÁLISIS DE LAS OPERACIONES. ......................................................................................................2
14.1.1. Buscar. .....................................................................................................................................2
14.1.2. Inserción. .................................................................................................................................2
a) Inserción en hoja, descendiendo por la izquierda. ........................................................................................ 2
b) Inserción en hoja, con hebra derecha. .......................................................................................................... 3
14.1.3. Descarte. ..................................................................................................................................3
a) Nodo t con hijo derecho que tiene hijo izquierdo nulo. ................................................................................ 4
b) Nodo t con hebra derecha e hijo izquierdo nulo. .......................................................................................... 4
c) Nodo t tiene hijo izquierdo l, el que a su vez tiene hebra derecha. ............................................................... 5
d) Nodo t tiene hijo izquierdo l, el que a su vez tiene hijo derecho. ................................................................. 5
14.1.4. Recorrido en orden. .................................................................................................................7
14.2. ESTRUCTURA DE DATOS Y FUNCIONES BÁSICAS. ..............................................................................8
14.2.1. Estructura de datos. .................................................................................................................8
14.2.2. Creación de un nodo. ...............................................................................................................8
14.2.3. Mostrar nodo y el árbol enhebrado en niveles. ........................................................................8
14.2.4. Recorrido en orden. .................................................................................................................9
14.2.5. Sucesor. ....................................................................................................................................9
14.2.6. Buscar. .....................................................................................................................................9
14.3. INSERTAR. ........................................................................................................................................9
14.4. DESCARTAR. ..................................................................................................................................10
14.5 TEST DE ESTRUCTURA DE ÁRBOL ENHEBRADO POR LA DERECHA.....................................................12
REFERENCIAS. .........................................................................................................................................13
ÍNDICE GENERAL. ....................................................................................................................................14
ÍNDICE DE FIGURAS. ................................................................................................................................14
Índice de figuras.
FIGURA 14.1 ÁRBOL CON HEBRAS POR LA DERECHA. ....................................................................................1
FIGURA 14.2 INSERCIÓN EN HOJA, DESCENDIENDO POR LA IZQUIERDA. .........................................................2
FIGURA 14.3 INSERCIÓN EN HOJA, DESCENDIENDO POR LA DERECHA. ...........................................................3
FIGURA 14.4 DESCARTE DE NODO T CON UN HIJO DERECHO X........................................................................4
FIGURA 14.5 DESCARTE DE HOJA T CON UN HEBRA DERECHA. DESCENDIENTE DERECHO DE P. .....................4
FIGURA 14.6 DESCARTE DE HOJA T CON HEBRA DERECHA. DESCENDIENTE IZQUIERDO DE P. ........................4
FIGURA 14.7 DESCARTE DE NODO T CON HIJO IZQUIERDO. DESCENDIENTE IZQUIERDO CON HEBRA. .............5
FIGURA 14.8 DESCARTE DE NODO T CON HIJO IZQUIERDO. DESCENDIENTE IZQUIERDO CON HIJO. .................5
FIGURA 14.8 NODO ANTECESOR A CON HIJO IZQUIERDO. ...............................................................................6
FIGURA 14.9 NODO ANTECESOR A SIN HIJO IZQUIERDO..................................................................................6
FIGURA 14.10 NODO ANTECESOR A REEMPLAZA AL NODO T. .........................................................................7
FIGURA 14.11 ARGUMENTOS Y VARIABLES LOCALES LUEGO DE SER INICIADAS. .........................................11
Profesor Leopoldo Silva Bijit
20-01-2010
Descargar