arboles binarios

Anuncio
INTRODUCCION ÁRBOLES:
Definiciones:
 Un árbol es una colección de nodos (del tipo que se
quiera), uno de los cuales se llama raíz junto con una
relación de “paternidad” que impone una estruc tura
jerárquica sobre los nodos.
 Una manera natural de definirl o es de forma
recursiva:
- Caso base: o el árbol esta vacío o tiene
un solo nodo.
- Caso general: un nodo raíz ( r ) y cero o
más subárboles A1, A2,..., An cada uno
de ellos tiene su raíz conectada a r
mediante una arista.
Ejemplo (índice de un libro):
libro
C1
1.1
C2
1.2
2.1.1
2.1
C3
2.2
2.1.2
Libro seria el nodo raíz (r).
Cada subárbol tendría raíz C1, C2, C3.
El subárbol con raíz C2 tendría como subárboles 2.1
y 2.2.
Partes de un árbol:
 Raíz: el nodo inicial o base de una estructura
en árbol.
 Nodo hoja: el nodo de un árbol que no tiene
hijos.
 Dos nodos son hermanos si tienen el mismo
padre.
 Un camino de un nodo N1 a otro Nk se define
como la secuencia de nodos N1, N2,..., Nk tal
que Ni es el padre de Ni+1 para 1 <= i < k.
 La longitud de este camino es el número de
aristas que lo forman, k -1.
 Existe un camino de longitud cero entre cada
nodo y él mismo. En un árbol hay exactamente
un camino entre la raíz y cada nodo.
 Un nodo es antecesor de un segundo si se
encuentra en el camino de la raíz a este.
 Un nodo es descendiente de un segundo si se
encuentra en el camino de este a algún nodo
hoja.
 La profundidad de un nodo es la longitud del
camino único entre la raíz y este.
 La altura de un nodo es el camino más largo
del nodo a una hoja. La altura de un árbol es
la altura de la raíz.
Ejemplo:
A
B
E
C
F
H
D
G
I
Raíz: A
Hojas: E, H, I, G, D.
B es padre de E, F.
F es hijo de B y hermano de E.
Camino de B a I, B—F—I.
Longitud 2.
Profundidad de F 2 y altura 1.
Recorridos:
Para recorrer un árbol se empieza por el nodo raíz y
se continúa por los hijos, de izquierda a derecha, de tal
manera que en cada nodo, antes de pasar al siguiente
hermano se recorren primero todos sus hijos, también de
izquierda a derecha.
 Recorrido en orden previo o preorden: en este
modo de recorrido, se trabaja en el nodo antes
de pasar al siguiente.
 Recorrido en orden posterior o postorden: en
este, se trabaja en cada nodo una vez que ya
se ha trabajado con todos sus hijos y no
antes.
 Recorrido en orden simétric o o inorden: en
esta forma de recorrido se trabaja en los
nodos hoja la primera y única vez que se pasa
por ellos y en el resto de nodos la segunda
vez.
Ejemplo:
1
2
3
5
8
4
6
9
Recorrido preorden:
1-2-3-5-8-9-6-10-4-7
Recorrido postorden:
2-8-9-5-10-6-3-7-4-1
Recorrido inorden:
2-1-8-5-9-3-10-6-7-4
7
10
RELACIÓN CON GRAFOS:
 Un grafo se define como sigue:
G = (V, A)
Donde
-V (G) es un conjunto finito, no
vacío, de vértices.
-A (G) es un conjunto de aristas (pares
de vértices)
 Un grafo dirigido es un grafo en el cual solo
puedes ir de un vértice a otro en una
dirección.
 Un grafo tiene un ciclo si posee una
trayectoria sin vértices repetidos, excepto el
primero y el último. Un grafo diremos que es
acíclico si no posee ciclos.
 Un grafo es conexo si todo par de vértices
esta unido por una trayectoria.
 Un árbol es un grafo dirigido, ací clico y
conexo.
 Los árboles abarcadores de coste mínimo son
grafos donde cada arista tiene asociad o un
coste, de manera que al moverte de un vértice
a otro lo hagas por el camino de menor coste.
Ejemplo: Redes de comunicación.
1
6
1
5
1
2
3
5
3
6
5
Grafo
1
5
4
6
6
4
2
2
5
3
3
4
4
5
2
6
árbol abarcador de coste mínimo
TIPOS DE ÁRBOLES BINARIOS:
Un árbol binario es un árbol en el que cada nodo puede
tener dos, uno o ningún hijo.
Árboles binarios de expresión:
Las expresiones están formadas por valores sobre
los que pueden ejecutarse operaciones bin arias. Las
distintas partes de la expresión tienen distintos niveles de
procedencia de evaluación, de tal manera que se puede
escribir una expresión en un árbol binario.
Dependiendo de la forma de recorrer el árbol la
expresión habrá que interpretarla de diferente manera.
Para tenerla escrita de la forma habitual habrá que
recorrer el árbol en orden simétrico.
Ejemplo de árbol de expresión:
*
_
12
+
3
4
1
Preorden: *(- (12 3)) (+ (4 1))
Postorden: ((12 3) -) ((4 1) +) *
Inorden: (12 – 3) * (4 + 1)
Montículos:
- Un árbol binario lleno es un árbol binario en el
que todas las hojas están al mismo nivel y cada
nodo que no es una hoja tiene dos hijos.
- Un árbol binario completo es un árbol binario
que es lleno o esta lleno hasta el penúltimo nivel
tan a la izquierda como sea posible
Un montículo debe ser un árbol binario
completo y para cada nodo del montículo, el valor
almacenado en ese nodo es mayor o igual que el valor de
cada uno de sus hijos.
La característica especial de los montículos es
que siempre sabemos donde esta el valor máximo (en la
raíz).Son útiles para la ordenación.
Ejemplo:
Valor máximo es 10 y se encuentra en la raíz del
árbol.
Es un árbol binario completo ya que esta lleno hasta
el penúltimo nivel y en el último nivel tiene los nodos
a la izquierda.
10
8
4
2
9
5
3
6
7
Árbol binario de búsqueda:
Es un árbol binario en el que el hijo izquierdo, si
existe, contiene un valor más pequeño que el del nodo
padre y el hijo derecho, si existe, contiene un valo r mayor
al del nodo padre.
Aplicaciones de los árboles binarios de búsqueda y
comparación con listas:
- Facilita la búsqueda, aunque no facilita
los accesos directos como sucedía con
los arrays. Suministra un acceso más
rápido y constante, así es convenient e
para aplicaciones en las que el tiempo de
búsqueda debe minimizarse.
- Ocupa más espacio que una lista
enlazada (contiene un puntero extra).
- Los algoritmos para manipular el árbol
son más complicados que los de las
listas.
Ejemplo de árbol binario de búsq ueda:
6
2
1
8
4
3
La raíz es 6 que es mayor que 2 y menor que 8.
Así el nodo que contiene el valor 2 es mayor que el valor
del nodo de la izquierda, 1 y menor que el valor del nodo
de la derecha.
Operaciones que suelen darse en los árboles binarios d e
búsqueda:


Estructura.
{Declaración de tipos}
ELEMENTO = T;
NODO = registro de
Valor: ELEMENTO; {Genérico}
izq: puntero a NODO;
der: puntero a NODO;
fin registro;
/* Implementación en C*/
struct nodo{
int valor;
struct nodo *izq;
struct nodo *der;
};
typedef struct nodo NODO;
Crear árbol.
/*Implementación en C*/
/*separar memoria para un nodo*/
NODO *getnode()
{NODO *p;
p=(NODO*) malloc (sizeof (NODO));
return p;
}
NODO *crea_arbol (int v)
{NODO *p;
p=getnode();
p->valor=v;
p->izq=NULL;
p->der=NULL;
return p;
}

Comprobar vació.
Vacia (A: NODO, resp: lógico)
Necesita: un árbol A y un valor lógico resp.
Modifica: resp, indicando si el árbol esta vació (
falso ) o no (cierto).
/* Implementación en C*/
int vacio (NODO *arbol)
{if (arbol==NULL) return 1;
else return 0;
}

Buscar: devuelve un putero al nodo del árbol que
tiene el valor buscado o nil si no existe el nodo. Primero
se mira si el árbol esta vacío, en tal caso el resultado
sería nil. Es importante hacer esta comprobación primero
pues sino intentarías buscar en una estructura vacía, lo
que causaría un error al ejecutarlo. Después se
comprueba si el valor buscado es la raíz del árbol y sino
se hacen llamadas recursivas a los subárboles izquie rdo y
derecho según la relación del valor buscado con la raíz.
/*Implementación en C*/
/*Buscar elementos en un arbol*/
NODO *buscar(int v, NODO *A){
if (vacio(A)==1){
printf ("\n elemento no encontrado");
return NULL;
}
else{
if(v < A->valor) buscar(v,A->izq);
else{
if(v > A->valor) buscar(v,A->der);
else return A;
}
}
}

Buscar_min y buscar_max: Devuelve punteros a los
elementos menor y mayor respectivamente. Se devuelven
punteros, en vez de los valores máximo y mínimo, para
que de esta forma las funciones sean lo más semejantes
posible a la de buscar, ya que de esta forma se simplifican
las cosas.
/*Implementación en C*/
/*buscar el minimo de un arbol*/
NODO *buscar_min (NODO *A)
{if (vacio(A)==1) return NULL;
else
{if (A->izq==NULL) return A;
else buscar_min (A->izq);
}
}
Seria muy parecido buscar el máximo.

Insertar: para insertar un nodo X en el árbol A se
llama a la función buscar y si X ya esta en el árbol no se
hace nada y sino se encuentra X se a ñade al final del
camino recorrido.
/*Implementación en C*/
/*Para insertar elementos*/
NODO *insertar (int v, NODO *A)
{
if (vacio(A)==1) {
A=crea_arbol (v);
if (vacio(A)==1) printf ("\n memoria agotada\n");
}
else
{
if (v < A->valor)
{
if(A->izq==NULL) A->izq=crea_arbol(v);
else insertar(v,A->izq);
}
if (v > A->valor)
{
if(A->der==NULL) A->der=crea_arbol(v);
else insertar(v,A->der);
}
/*Si v ya se encuentra en el arbol no se hace na da*/
}
return A;
}

Eliminar: Hay que tener en cuenta varias
posibilidades:
- Si el nodo es una hoja se puede eliminar
sin más.
- Si el nodo tiene un hijo hay que ajustar el
puntero del padre al hijo del nodo a
eliminar. Si se quiere liberar el nodo
eliminado hay que conservar un puntero
a este.
- Cuando el nodo a eliminar tiene dos hijos
lo que se hace es sustituir el nodo por el
menor del subárbol derecho y luego se
elimina este último, ya de manera
sencilla dado que no tendrá nunca hijo
izquierdo puesto que es el menor del
subárbol.
Si el número de eliminaciones esperadas no es
demasiado grande se usa también la eliminación perezosa,
en la que en vez de eliminar realmente un nodo, solo se
marca como eliminado. La penalización de tiempo es muy
pequeña y si luego se quiere volver a insertar el nodo
eliminado no hace falta tener que crear una celda nueva,
que requiere mucho trabajo, sino sólo quitar la marca.
Árboles AVL ( Adelson-Velskii y Landis ):
Son árboles binarios de búsqueda con una condición
de equilibrio, la cual debe ser fácil de mantener. Dicha
propiedad asegura que la profundidad del árbol sea O (log
n).
La idea de equilibrio más sencilla sería que los
subárboles izquierdo y derecho fuesen igual de profundos,
pero esto no evita que el árb ol sea demasiado profundo.
Otra idea seria que todos los nodos tuviesen los dos
subárboles a la misma altura, pero esto, teniendo en
cuenta que la altura de un árbol vacío se define como –1,
se restringe a los árboles de (2^k) -1 nodos, con lo cual no
es muy útil.
Los árboles AVL son árboles binarios en los que la
diferencia de altura entre los subárboles de cada nodo no
puede ser superior a uno. Hay que mantener en la
estructura de nodo información sobre la altura.
6
2
1
8
4
7
3
Todas las operaciones son iguales que en los
árboles binarios de búsqueda, excepto la inserción y la
eliminación, a no ser que esta sea perezosa, dado que
estas pueden alterar la condición de equilibrio en el árbol.
Para poder hacer siempre estas operaciones hay
que introducir una modificación al árbol, la rotación.
- Rotación sencilla:
K2
k1
Z
K2
X
Y
K1
X
Y
Z
Los dos son árboles binarios de búsqueda. Hay
que tener en cuenta que k1 es menor que k2 en ambos
árboles, que todos los elementos del subárbol X son
menores que k1, todos los elementos del subárbol Z son
mayores que k2 y todos los elementos del subárbol Y
están entre k1 y k2. La transformación del uno al otro se
llama rotación. La rotación cambia la estructura del árbol,
pero esta no deja de ser un árbol binario de búsqueda.
La rotación se puede hacer en cualquier nodo
del árbol. Para mantener la condición de equilibrio de los
AVL lo que se hace cuando se inserta un elemento es ir
recorriendo el árbol desde el nodo insertado hasta la raíz,
comprobando a cada paso si se conserva la condición de
equilibrio y si no es así se hace una rotación, que en
muchos casos sirve para mantener la condición de
equilibrio.
6
6
2
8
1
4
2
7
3
1
7
4
6,5
8
6,5
3
- Rotación doble:
Existe un caso en el que la rotación simple no re compone
el árbol, ejemplo.
4
4
2
1
6
3
2
5
7
1
6
3
5
5
15
15
14
7
14
El problema anterior se soluciona con la rotación doble como se
indica a continuación:
K3
K2
K1
K3
K1
A
K2
D
B
A
B
C
C
Aplicando la rotación doble al ejemplo nos queda el árbol siguiente:
4
2
1
6
3
5
14
7
15
Árboles desplegados:
En los árboles desplegados, después de tener
acceso a un nodo, este se lleva hasta la raíz.
el
Si el nodo que se mueve es muy profundo, por
camino se cambian muchos nodos que son
D
relativamente profundos, y de esta manera cabe esperar
que con la reestructuración se hagan también más rápidos
los futuros accesos a estos nodos.
Algunos estudios han demostrado que los
accesos a un nodo al que ya se ha accedido con
anterioridad son mucho más frecuentes de lo que cabría
esperar, lo cual hace que estos árboles, además de tener
una buena cota de tiempo en teoría sean buenos en la
práctica. Tampoco hace falta mantener la información
relativa a la altura o equilibrio, lo que puede ahorrar
espacio y simplificar el código en cierta medida.
Despliegue: rotaremos en el camino de acceso
al nodo de abajo hacia arriba. Si X es un nodo, distinto de
la raíz, en el camino de acceso en el que estamos rotando:
rotamos.
-
Si el padre de X es la raíz, simplemente
Sino; X tiene un padre ( P ) y un abuelo ( A ):
 Zig-zag; X es un hijo izquierdo y P
un hijo derecho ( o al revés ). En
este caso se hace una rotación
doble como la de los AVL
a
x
p
D
p
a
x
A
A
B
C
B
C
D
 Zig-zig: ambos, X y P, son hijos
izquierdos ( o derechos )
a
x
p
p
D
C
x
A
B
A
B
a
C
D
Descargar