2. Trees
Los trees o árboles son estructuras de datos
utilizados en Ciencias de Cómputos para
organizar objetos de manera jerárquica. Al
igual que un árbol biológico, contiene
raíces, ramas y hojas. La diferencia radica en
que, la raíz del árbol de estructura de
datos, está en la parte de arriba y las ramas
van hacía abajo junto con las hojas. Hay 3
propiedades del árbol:
• Los árboles son jerárquicos.
• Todos los children de un nodo, son
independientes de los children de otros
nodos.
• Las hojas de lo nodos (leaf node) son
únicas.
Por ejemplo podemos ver el reino animal
como lo vemos en la imagen.
3. Trees
La raíz o el root es la parte más general y las hojas son más particulares. En otras
palabras, los árboles en estructuras de datos van de los general a lo particular.
Tambien existe el subtree que es una sección del árbol que está en diferente posición
y que no afecta los niveles más bajos de las jerarquías del árbol. Veamos un ejemplo de
código HTML(HyperText Markup Language).
4. Trees de vocabulario que se utilizará en los árboles. Los mismos serán
Veamos un poco
expresados en inglés y explicados en español.
1. Node. Es la parte primordial del tree. Son los que llevan información de su
contenido como los children que tiene.
2. Edge. Conecta dos nodes, lo que muestra que hay una relación entre ellos.
3. Root. Es el único e inicial node que no tiene relación con ningún otro.
4. Path. Es un listado de nodes que están conectados por el edge.
5. Children. Son una serie de nodes que están conectados por edge al node que
reclama que son children de él.
6. Parent. Es el node parent de los nodos que están conectados a él.
7. Sibling. Son los nodes en el tree que son children de un node parent.
8. Subtree. Es una serie de nodes y edges que están relacionados a un parent y todos
los descendientes del mismo.
9. Leaf Node. Es un node que no tiene children.
10. Level. Es el número de edges en el path que tiene un node n desde el root.
11. Height. Es el máximo nivel (level) que tiene cualquier node en el tree.
5. Trees
Resumiendo lo que es un Tree.
1. Son una serie de nodes y de edges que
conecta a otro nodes.
2. Consiste en un root con otros subtrees. El
root de cada subtree es conectado por el
edge al root parent. Es un tree recursivo.
6. Trees
Nodes y referencias.
Cuando pensamos en nodes y referencias podemos imaginarla así.
Entramos a la implementación en Python del Tree. Utilizaremos una clase BinaryTree y
lo importante de la clase BinaryTree es que los atributos left y right se convertirán en
referencia de otras instancias en la clase. En otras palabras, cada nodo creado a partir
de insert de un nuevo child, por ejemplo, LeftChild se creará un nuevo BinaryTree y
modificará el self.left.
7. Trees
Nodes y referencias.
En nuestra clase BinaryTree tendremos nuestro leftchild y nuestro rightchild. Así mismo
tendremos dos funciones de inserción InsertLeft e InsertRight las cuales crearán una
nueva instancia de BinaryTree. Finalmente tendremos 4 funciones adicionales:
1.
2.
3.
4.
getRightChild: nos devolverá el valor Right Child
getLeftChild: nos devolverá el valor Left Child
setRootVal: crea el valor del Root
getRootVal: devuelve el valor del Root
8. Trees
Veamos la clase en acción. Nuestro
valor Root será a y hasta el momento
no habrá Left Child ni Right Child.
Después insertamos al leftchild b y al
rightchild c, lo mostramos en
pantalla y finalmente remplazamos
el valor del rightchild (c) por hello y lo
mostramos en pantalla.
9. Trees
BinaryTree Applications
Veremos ahora la funcionalidad del tree y cómo
puede utilizarse para resolver problemas. Parse
Tree (Analizar el árbol) se puede utilizar para
construir enunciados o expresiones matemáticas.
En este ejemplo el enunciado es el root, el
leftchild es el sujeto, el parent del leaf node es el
nombre propio y el leaf node es homero. El
rightchild es el verbo que tiene dos childs
nodes, left child es el verbo que es parent de
hit(leaf node) y el right child que es el sujeto que
tiene el parent nombre propio del leaf child bart.
10. Trees
También podemos representar expresiones
matemáticas por ejemplo ((7 + 3) * (5-2)). En
nuestra jerarquía de operaciones
matemáticas, sabemos que la multiplicación se
debe resolver primero antes que la suma y la
resta. Con ésto en mente la construción de este
árbol sería como se ve en la imagen de arriba.
Si fuera de manera simplificada, o sea, resolver la
suma y la resta primero para después
multiplicarla, se vería como en la imagen de
abajo.
11. Trees
Para evaluar un expresión matemática en Python debemos tener en consideración lo
siguiente:
• Si el token actual es un “(“ debemos añadir un nuevo nodo como un left child y
decender al left child.
• Si el token actual es un listado [“+”,”-”,”/”,”*”] colocar como set root value al nodo
actual con el operador que se representa. Añadir un nuevo nodo como right child al
nodo actual y descender al mismo.
• Si el token actual es un número, colocar como set root value al número y regresar al
parent.
• Si el token actual es “)” ir al parent node del nodo actual.
12. Trees
Para evaluar la siguiente expresión (3+(4*5)) seguiremos los puntos anteriormente
mencionados. Ver las siguientes imagenes:
Root
Paréntesis. Añadir
left child y bajar a él
Número. Colocarlo set
root value y regresar
al parent
Operación. Colocarlo
en el nodo actual y
crear un right
child, bajar a el.
Paréntesis. Añadir left
child y bajar a él
13. Trees
Continuación de representación de (3+(4*5))
Número. Colocarlo set
root value y regresar
al parent
Número. Colocarlo set
root value y regresar
al parent
Operación. Colocarlo
en el nodo actual y
crear un right
child, bajar a el.
14. Trees
Ahora apliquemos expresado en los
pasados slides pero en Python. En el
código estaremos haciendo uso
nuevamente del stack para los
operadores y números.
Posteriormente evaluaremos la
expresión matemática para que nos
dé un resultado. La expresión que
evaluaremos es: ( ( 10 + 5 ) * 3
), primero sumará 10 + 5 = 15 y luego
multiplicará por 3 = 45
15. Trees
Tree Traversals
Podemos dividir en tres la forma que llegamos o accesamos a los nodes de un árbol:
preorder, inorder y postorder.
1. Preorder: primero se visita al root node, después recursivamente preordena el left
subtree y luego el right subtree.
2. Inorder: recursivamente realiza un inorder en el left subtree, visita el root node y
finalmente realiza un inorder en el right subtree.
3. Postorder: recursivamente realiza un postorder en al left subtree, luego en el right
subtree y finalmente visita el root node.
16. Trees
Veamos un ejemplo para explicar un poco más el preorder, inorder y el postorder.
Nuestro ejemplo será el árbol de un libro donde el libro es el root node, cada capítulo es
child node del root, cada sección child node del capítulo y cada subsección será el child
node la sección. Veamos la siguiente imagen.
17. Trees
Regresando a nuestro ejemplo
anterior de la expresión matemática (
( 10 + 5 ) * 3 ) el preorder lo ordenará
visitando el root “*” , irá al left child
“+”, seguirá con los nodos del parent
“+” que son “10” y “5” y finalmente
llegará al right child que es “3”.
Veamos en la imagen.
18. Trees
Hagamos lo mismo para el inorder .
Visitará left child del parent “+” que
es “10” , seguirá left child del root “*”
que es “+”, continuará con el right
child de parent “+” que es “5”, visitará
el root “*” y finalmente llegará al
right child del root que es “3”. Veamos
en la imagen.
19. Trees
El postorder comenzará visitando el
left child y el right child del
rootparent “+” siguiendon con el left
child del root “*” que es “+”, pasará al
right child del root “*” que es “3” y
finalmente llegará al root “*”. Veamos
en la imagen.
20. Trees
Evaluación del tree la podemos hacer
con la función printexp. En el
siguiente ejemplo utilizaremos el root
“*” con left child “+”, tomaremos el
“+” como parent del left child “4” y del
right child “5” y finalmente el right
child del root “*” será “7”. El resultado
de esta evaluación será ((4)+(5))*(7)).
Finalmente evaluaremos la expresión
para que nos dé un resultado, para
esto utilizaremos la función
postordereval que nos devolverá 63 ya
que 4 + 5 = 9 y multiplicado por 7
devolverá 63. Veamos la imagen.
21. Trees
Binary Search Trees
Ahora que tenemos la estructura, la creación y la evaluación del ábol o del
tree, quisieramos tener una búsqueda eficiente del Binary Tree.
Veamos la interface que tendremos en el Binary Search Tree.
BinaryTree(): crea un nuevo y vacio binarytree
put(key,val): agrega un Nuevo key value al ábol
Get(key): dado el key nos devuelve el valor almacenado en key
Delete_key(key): elimina el key value del árbol
Lenght(): devuelve el número de key values almacenado en el tree
Has_key(key): devuelve True si el key dado está en el diccionario
Operators: podemos utilizar los operadores matemáticos con overload
22. Trees
Implementación de Search Tree
Como hemos visto, para que un binary search funcione, el listado debe estar ordenado de
menor a mayor. Lo mismo sucede en un tree binary search donde dado un item como
root, a su left child sea menor que el root y el right child mayors y así sucesivamente.
Veamos la imagen.
23. Trees
En un binary tree search debemos tener la posibilidad de inserter un nuevo node sin
perder el orden ya establecido. El algoritmo hará lo siguiente:
Primeramente, el nuevo nodo a insertar es comparado con el root.
Una vez verifica si es mayor, formará parte del left child y elimina la búsqueda en el
right child y viceversa
Finalmente, busca en los nodes existentes, dónde insertarlo lo cual será en el nodo
mayor al él mismo (nuevo nodo) más cercano.
En este ejemplo se inserta el 19. Compara que es
mayor al root y se va por el right child buscando
un número mayor a él y que sea el más cercano
a él también. En este caso el 29.
24. Trees
Ahora tenemos que poner nuestra atención a uno de los aspectos más complicados que
se pueden presentar en el binary search tree que es la eliminación de un key o de un nodo.
Tenemos tres elementos a analizar.
1. Eliminar un node que no tiene child
2. Eliminar un node que tiene un solo child
3. Eliminar un node que tiene más de un child
En nuestro primer elemento, recordemos que un node que no tiene child se llama leaf.
Elminar un leaf es un simple binary search que llegará a encontrar el node rápidamente y
lo eliminará
25. Trees
En el siguiente ejemplo queremos elimar el 16. Al ser el root 17 lo encontraremos en el left
child del root así que eliminaremos nuestra búsqueda por el right child y nos
concentraremos solo en el left child. Tenemos el 5 que tiene el 2 de left child y el 11 de
right child. 16 es mayor que 2 y 11 pero el 11 está más cerca del 16 así que seguimos
buscando por el right child del 5. el 11 tiene left child 9 y right child 16. Ahí encontramos el
16 y lo eliminamos. Veamos la imagen.
26. Trees
El segundo caso es más complicado ya que, al eliminar el node que tiene un solo child, hay
que saber qué tenemos que hacer con ese child, lo cual jocosamente podríamos decir que
quedó huérfano ya que su parent fue eliminado.
Lo que básicamente se hace es que ese child (huérfano) suba un nivel y se actualice el
estatus de los niveles superiores. En el siguiente ejemplo se eliminará el node 25 que tiene
un child que es el 35. La búsqueda según lo hemos visto comparando o validando por el
root que es 17 y en este caso será por el right child. El node 25 es eliminado y el 35 con sus
children toma su lugar.
27. Trees
Nuestro tercer caso es más complicado todavía, ya que no podemos simplemente elevar
de nivel a cualquiera de los child huérfanos. Lo que debemos hacer es buscar un node que
pueda ser utilizado para reemplazar el node a eliminar. El node que remplazará el node a
ser eliminado se llamara successor. Este node debe tener unas condiciones para ser
tomado en cuenta como successor.
I. Para ser considerado como successor el node sólo debe tener un child.
II. Si el node tiene un right child, el successor es el valor más pequeño del right subtree.
III. Si el node no tiene un right child y es el left child de su parent, entonces el parent es el
successor.
IV. Si el node es el right child de su parent y este mismo no tiene right child entonces el
successor de este node es el successor de su parent excluyendo a este node.
28. Trees
Analicemos el siguiente ejemplo.
Nuestro root es el 17 y queremos
eliminar el node 5.
Rápidamente, vemos que no iremos
por el left child del 17 ya que el 5 es
menor que el 17. El node 5 tiene un
leaft child que es 2 y un right child que
es 11. El 11 tiene un leaft right que es
16 y un left child que es 9, el 9 tiene a
su vez al 7 y el 7 al 8. Ahora falta buscar
el successor del 5. Si vemos nuestras
opciones para considerar al
successor, la opción 1 y 2 aplican, por
que el nodo (5) tiene un right child y
hay que buscar el menor de ese right
subtree y que tenga no más de un
child. El menor es el 7 y solo tiene 8
como child así que el 7 será nuestro
successor. Veamos la imagen.
29. Trees
Analizando el Search Tree
Una de las limitaciones al impletar un insert en el tree es el height. Siendo el height el
número de edges entre el root y el más profundo leaft node se tiene que hacer una
comparación en cada nivel del tree.
Un tree balanceado es aquel que tiene la misma cantidad de nodes en el left subtree y en
el right subtree.
El height puede ser una limitante al querer insertar nodes y aumentamos la complejidad
al escoger números random, aquí la complejidad sería logaritmica. Si tenemos un tree
ordenado la búsqueda e inserción la complejidad sería línea O(n)