Un árbol de búsqueda guarda datos ordenados
Un árbol binario de búsqueda es un conjunto de nodos con como mucho dos hijos cada uno. Los menores van a su izquierda y los mayores a su derecha. Gracias a esa regla, buscar un valor descarta la mitad del árbol en cada paso.
Las carpetas de tu ordenador también forman un árbol: de una carpeta cuelgan otras.
Al final de esta lección
Nota
Puedo construir un árbol binario de búsqueda y buscar en él.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Árbol | nodos unidos de arriba abajo, sin ciclos |
| Raíz | el nodo de arriba, del que cuelga todo |
| Hijo | un nodo que cuelga directamente de otro |
| Hoja | un nodo sin hijos |
| Recorrido en orden | visitar izquierda, nodo y derecha, en ese orden |
| Altura | cuántos niveles tiene el árbol |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un árbol tiene raíz, hijos y hojas
Un árbol empieza en un nodo de arriba, la raíz. De cada nodo cuelgan sus hijos, y un nodo sin hijos es una hoja.
En un árbol binario cada nodo tiene como mucho dos hijos: el izquierdo y el derecho. Un camino de rutas como /home/student/arch es un recorrido de la raíz hasta una hoja.
Una ruta es un camino del árbol
python3 -c "print('/home/student/arch'.split('/'))"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Menores a la izquierda, mayores a la derecha
En un árbol binario de búsqueda, lo que cuelga a la izquierda de un nodo es menor que él. Lo que cuelga a la derecha es mayor.
Para insertar un valor, empiezas en la raíz y bajas: a la izquierda si es menor, a la derecha si es mayor. Cuando llegas a un hueco, ahí va el nodo nuevo.
Insertar 50, 30, 70, 20 y 40
| Valor | Camino desde la raíz | Dónde queda |
|---|---|---|
| 50 | Ninguno: el árbol está vacío | Raíz |
| 30 | 30 es menor que 50: izquierda | Hijo izquierdo de 50 |
| 70 | 70 es mayor que 50: derecha | Hijo derecho de 50 |
| 20 | Izquierda de 50, izquierda de 30 | Hijo izquierdo de 30 |
| 40 | Izquierda de 50, derecha de 30 | Hijo derecho de 30 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Una función puede llamarse a sí misma
Un árbol está hecho de árboles más pequeños: cada hijo es la raíz de su propio árbol. Por eso las funciones sobre árboles se escriben bien con recursión, una función que se llama a sí misma con un árbol más pequeño.
Siempre necesita un caso base que no se llame: aquí, un árbol vacío (None). Sin él, la función no acabaría nunca.
Insertar valores en un árbol
class Node:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def insert(root, value):
# Devuelve la raíz del árbol, con el valor ya dentro.
if root is None:
return Node(value)
if value < root.value:
root.left = insert(root.left, value)
elif value > root.value:
root.right = insert(root.right, value)
return root
root = None
for number in [50, 30, 70, 20, 40]:
root = insert(root, number)
print(root.value, root.left.value, root.right.value)
print(root.left.left.value, root.left.right.value)
# Inserta 60 y 80, y mira dónde caen.
Buscar descarta la mitad en cada paso
Para buscar un valor haces el mismo camino que al insertar: comparas con el nodo, y bajas a la izquierda o a la derecha. Si llegas a un hueco, el valor no está.
En un árbol equilibrado de 1 000 nodos, bajas unos diez niveles, no mil. Pero si insertas 1, 2, 3, 4 en ese orden, el árbol es una cadena y buscar cuesta tantos pasos como nodos.
El recorrido en orden da los valores ordenados
Para recorrer el árbol en orden, visitas primero el hijo izquierdo, luego el nodo y por último el hijo derecho. Haces lo mismo en cada hijo.
Como los menores están siempre a la izquierda, el resultado sale ordenado de menor a mayor. Es una recursión de tres líneas.
Recorrer el árbol en orden
class Node:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def insert(root, value):
if root is None:
return Node(value)
if value < root.value:
root.left = insert(root.left, value)
elif value > root.value:
root.right = insert(root.right, value)
return root
def in_order(root):
# Primero la izquierda, luego el nodo, luego la derecha.
if root is None:
return []
return in_order(root.left) + [root.value] + in_order(root.right)
root = None
for number in [50, 30, 70, 20, 40, 60, 80, 35]:
root = insert(root, number)
print(in_order(root))
El mismo resultado, ordenando números
printf '5\n3\n8\n1\n4\n' | sort -nSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Un error frecuente
Advertencia
Mucha gente cree que un árbol de búsqueda siempre es rápido. Depende del orden de inserción. Si los valores llegan ya ordenados, cada uno va a la derecha del anterior y el árbol es una lista disfrazada, con búsquedas lentas.
Para ir más lejos
Escribe contains(root, value), que devuelve True si el valor está. Sigue el mismo camino que insert y para en cuanto llegues a un hueco.
Después piensa en qué orden de inserción de 1 a 7 da la altura menor posible.