BST · AVL · Python · tuplas con prioridad · 2-3 y Rojo-Negro
Parcial a las 10:00. Lo más valioso es repetir rotaciones hasta que salgan sin pensar, y luego el código.
En cada nodo: todo lo de la izquierda es menor, todo lo de la derecha es mayor. Se inserta bajando desde la raíz (menor → izquierda, mayor → derecha) hasta encontrar un hueco.
| Medida | Qué es | Ejemplo (árbol de abajo) |
|---|
Si el profe cuenta la altura en nodos (y no en aristas), es la misma más 1. Con raíz = nivel 0, lo normal es contar aristas: una hoja tiene altura 0.
FB (factor de balance) = altura(izquierda) − altura(derecha). Árbol vacío = −1, hoja = 0. Un AVL está bien si todos los nodos tienen FB −1, 0 o +1.
| Camino | Caso | Qué se hace |
|---|---|---|
| izquierda, izquierda | LL | Una rotación a la derecha sobre el pivote |
| derecha, derecha | RR | Una rotación a la izquierda sobre el pivote |
| izquierda, derecha | LR | Izquierda sobre el hijo, luego derecha sobre el pivote |
| derecha, izquierda | RL | Derecha sobre el hijo, luego izquierda sobre el pivote |
Los nombres cambian según el profe: a LL algunos le dicen "rotación simple a la derecha" o "II". Lo que importa es la forma.
Árboles pequeños al azar. En Hacerlo yo: ponga el nodo en su hueco y rote arrastrando un nodo encima de su padre. En Elegir respuesta: diga qué rotación toca (Enter revisa).
Inserte números uno por uno. Usted escoge el hueco donde va cada uno y hace las rotaciones arrastrando nodos. La página le dice si quedó un AVL válido.
Casi toda serie es una base + algo que le hacen. "Los primos más 1" → base pₙ, le hacen + 1 → aₙ = pₙ + 1, n ≥ 1.
| Si dice… | Escriba | Da |
|---|---|---|
| los naturales | n | 1, 2, 3, 4 |
| los pares | 2n | 2, 4, 6, 8 |
| los impares | 2n − 1 | 1, 3, 5, 7 |
| los múltiplos de 5 | 5n | 5, 10, 15, 20 |
| los cuadrados (perfectos) | n² | 1, 4, 9, 16 |
| los cubos | n³ | 1, 8, 27, 64 |
| los primos | pₙ (el n-ésimo primo) | 2, 3, 5, 7, 11 |
| las potencias de 2 | 2ⁿ | 2, 4, 8, 16 |
| los triangulares | n(n + 1)/2 | 1, 3, 6, 10 |
| los factoriales | n! | 1, 2, 6, 24 |
| Fibonacci | a₁ = 1, a₂ = 1, aₙ = aₙ₋₁ + aₙ₋₂ | 1, 1, 2, 3, 5 |
| Si dice… | Escriba | Ejemplo |
|---|---|---|
| más 3 | X + 3 | pₙ + 3 |
| menos 1 | X − 1 | n² − 1 |
| el doble de | 2·X | 2·(2n − 1) |
| la mitad de | X / 2 | 2ⁿ / 2 |
| al cuadrado | X² | (2n − 1)² |
| 100 menos… | 100 − X | 100 − 5n |
| signo alternado, empieza negativo | (−1)ⁿ · X | −1, 2, −3, 4 |
| signo alternado, empieza positivo | (−1)ⁿ⁺¹ · X | 1, −2, 3, −4 |
| el residuo de dividir X entre 7 | X mod 7 | n² mod 7 |
| cada término es … del anterior | a₁ = …, aₙ = … aₙ₋₁ | a₁ = 3, aₙ = 2·aₙ₋₁ |
| Frase | Base → le hacen | Fórmula | Verificación |
|---|---|---|---|
| Todos los primos más 1 | pₙ → + 1 | aₙ = pₙ + 1, n ≥ 1 | 3, 4, 6, 8, 12 |
| Los impares al cuadrado | 2n − 1 → ² | aₙ = (2n − 1)², n ≥ 1 | 1, 9, 25, 49 |
| Cien menos los múltiplos de 5 | 5n → 100 − | aₙ = 100 − 5n, n ≥ 1 | 95, 90, 85 |
| Los naturales con signo alternado, empezando en negativo | n → (−1)ⁿ · | aₙ = (−1)ⁿ · n, n ≥ 1 | −1, 2, −3, 4 |
| Cada término es el doble del anterior, empezando en 3 | recursiva | a₁ = 3, aₙ = 2·aₙ₋₁, n ≥ 2 | 3, 6, 12, 24 |
También vale escribirlo como conjunto: { p + 1 : p es primo }. Pero la forma aₙ = … es mejor, porque con ella puede listar "hasta n = 15" directamente.
Lea la frase, escoja la fórmula y escriba los primeros 6 términos. Enter revisa y pasa a la siguiente.
Serie en palabras → fórmula → lista → insertar en AVL → eliminar 3 nodos (predecesor) → recorridos y medidas. Hágalo en papel y vaya digitando.
Solo lo que necesita para escribir un árbol en papel. Lo de la izquierda es lo que usted ya conoce de otros lenguajes.
: al final de if, def, class, while, for, else; y la sangría (4 espacios) que reemplaza las llaves { }.Como en el parcial: hoja en blanco. Escríbalo primero en papel, luego aquí, y presione Revisar. La página revisa los errores típicos de sintaxis y los puntos clave, y le muestra la solución al lado. Lo que escriba se guarda en este navegador.
Tab = 4 espacios · Shift+Tab quita sangría · Enter mantiene la sangría y agrega 4 después de ":".
Primero escriba la función completa en papel. Luego llene los espacios y presione Revisar. No importan los espacios en blanco.
El mismo archivo arboles.py que quedó en su carpeta. Se puede correr con python arboles.py.
# ============================================================
# ARBOLES BST y AVL en Python (version para estudiar a mano)
#
# Convenciones (las de su profe):
# - profundidad de la raiz = 0
# - altura de una hoja = 0, arbol vacio = -1
# - eliminar nodo con dos hijos -> PREDECESOR
# (el mayor del subarbol izquierdo)
# - FB (factor de balance) = altura(izq) - altura(der)
# ============================================================
class Nodo:
def __init__(self, valor):
self.valor = valor
self.izq = None
self.der = None
self.altura = 0 # solo lo usa el AVL
# ======================== BST ========================
def insertar_bst(nodo, valor):
if nodo is None:
return Nodo(valor)
if valor < nodo.valor:
nodo.izq = insertar_bst(nodo.izq, valor)
elif valor > nodo.valor:
nodo.der = insertar_bst(nodo.der, valor)
return nodo
def buscar(nodo, valor):
if nodo is None:
return False
if valor == nodo.valor:
return True
if valor < nodo.valor:
return buscar(nodo.izq, valor)
return buscar(nodo.der, valor)
def maximo(nodo):
# el predecesor de X es maximo(X.izq)
while nodo.der is not None:
nodo = nodo.der
return nodo
def eliminar_bst(nodo, valor):
if nodo is None:
return None
if valor < nodo.valor:
nodo.izq = eliminar_bst(nodo.izq, valor)
elif valor > nodo.valor:
nodo.der = eliminar_bst(nodo.der, valor)
else:
# caso 1 y 2: hoja o un solo hijo
if nodo.izq is None:
return nodo.der
if nodo.der is None:
return nodo.izq
# caso 3: dos hijos -> predecesor
pred = maximo(nodo.izq)
nodo.valor = pred.valor
nodo.izq = eliminar_bst(nodo.izq, pred.valor)
return nodo
# ===================== RECORRIDOS =====================
def inorden(nodo, res):
if nodo is not None:
inorden(nodo.izq, res)
res.append(nodo.valor)
inorden(nodo.der, res)
def preorden(nodo, res):
if nodo is not None:
res.append(nodo.valor)
preorden(nodo.izq, res)
preorden(nodo.der, res)
def postorden(nodo, res):
if nodo is not None:
postorden(nodo.izq, res)
postorden(nodo.der, res)
res.append(nodo.valor)
def anchura(raiz):
# recorrido por niveles (BFS) usando una lista como cola
res = []
if raiz is None:
return res
cola = [raiz]
while len(cola) > 0:
actual = cola.pop(0)
res.append(actual.valor)
if actual.izq is not None:
cola.append(actual.izq)
if actual.der is not None:
cola.append(actual.der)
return res
def altura(nodo):
if nodo is None:
return -1
return 1 + max(altura(nodo.izq), altura(nodo.der))
def profundidad(nodo, valor):
d = 0
while nodo is not None:
if valor == nodo.valor:
return d
if valor < nodo.valor:
nodo = nodo.izq
else:
nodo = nodo.der
d += 1
return -1 # no esta
def contar_hojas(nodo):
if nodo is None:
return 0
if nodo.izq is None and nodo.der is None:
return 1
return contar_hojas(nodo.izq) + contar_hojas(nodo.der)
# ======================== AVL ========================
def h(nodo):
if nodo is None:
return -1
return nodo.altura
def actualizar(nodo):
nodo.altura = 1 + max(h(nodo.izq), h(nodo.der))
def fb(nodo):
return h(nodo.izq) - h(nodo.der)
def rotar_derecha(y):
x = y.izq
t2 = x.der
x.der = y
y.izq = t2
actualizar(y) # primero el que quedo abajo
actualizar(x)
return x # x es la nueva raiz del subarbol
def rotar_izquierda(x):
y = x.der
t2 = y.izq
y.izq = x
x.der = t2
actualizar(x)
actualizar(y)
return y
def balancear(nodo):
actualizar(nodo)
b = fb(nodo)
if b > 1: # pesa a la izquierda
if fb(nodo.izq) < 0: # zig-zag: caso LR
nodo.izq = rotar_izquierda(nodo.izq)
return rotar_derecha(nodo) # caso LL
if b < -1: # pesa a la derecha
if fb(nodo.der) > 0: # zig-zag: caso RL
nodo.der = rotar_derecha(nodo.der)
return rotar_izquierda(nodo) # caso RR
return nodo
def insertar_avl(nodo, valor):
if nodo is None:
return Nodo(valor)
if valor < nodo.valor:
nodo.izq = insertar_avl(nodo.izq, valor)
elif valor > nodo.valor:
nodo.der = insertar_avl(nodo.der, valor)
else:
return nodo # repetido: no se inserta
return balancear(nodo)
def eliminar_avl(nodo, valor):
if nodo is None:
return None
if valor < nodo.valor:
nodo.izq = eliminar_avl(nodo.izq, valor)
elif valor > nodo.valor:
nodo.der = eliminar_avl(nodo.der, valor)
else:
if nodo.izq is None:
return nodo.der
if nodo.der is None:
return nodo.izq
pred = maximo(nodo.izq)
nodo.valor = pred.valor
nodo.izq = eliminar_avl(nodo.izq, pred.valor)
return balancear(nodo)
class ArbolAVL:
def __init__(self):
self.raiz = None
def insertar(self, valor):
self.raiz = insertar_avl(self.raiz, valor)
def eliminar(self, valor):
self.raiz = eliminar_avl(self.raiz, valor)
# ============ NODOS CON TUPLAS Y PRIORIDAD ============
# Python compara tuplas campo por campo, de izquierda a derecha:
# (3, 1, 20) < (3, 2, 5) -> True (empatan en 3, decide el 1 < 2)
# Entonces: ponga primero el campo MAS importante.
# Si un campo es "mayor primero", pongale signo menos.
class Evento:
def __init__(self, prioridad, magnitud, id):
self.prioridad = prioridad
self.magnitud = magnitud
self.id = id
def clave(self):
# 1. magnitud (mayor primero) 2. prioridad (menor primero) 3. id
return (-self.magnitud, self.prioridad, self.id)
def __lt__(self, otro):
# con esto "a < b" funciona directo entre eventos
return self.clave() < otro.clave()
def __repr__(self):
return f"#{self.id}(p={self.prioridad}, m={self.magnitud})"
def insertar_evento(nodo, ev):
if nodo is None:
return Nodo(ev)
if ev.clave() < nodo.valor.clave():
nodo.izq = insertar_evento(nodo.izq, ev)
else: # el id es unico: nunca empatan
nodo.der = insertar_evento(nodo.der, ev)
return nodo
# ======================== DIBUJO ========================
def imprimir(nodo, nivel=0):
# dibuja el arbol de lado: la raiz a la izquierda, la derecha arriba
if nodo is not None:
imprimir(nodo.der, nivel + 1)
print(" " * nivel + str(nodo.valor))
imprimir(nodo.izq, nivel + 1)
# ======================== DEMO ========================
def primos(n):
res = []
x = 2
while len(res) < n:
es_primo = True
for d in range(2, x):
if x % d == 0:
es_primo = False
if es_primo:
res.append(x)
x += 1
return res
if __name__ == "__main__":
serie = [p + 1 for p in primos(15)] # "los primos mas 1"
print("Serie:", serie)
arbol = ArbolAVL()
for x in serie:
arbol.insertar(x)
imprimir(arbol.raiz)
for x in [20, 4, 32]:
arbol.eliminar(x)
print("\nDespues de eliminar 20, 4 y 32:")
imprimir(arbol.raiz)
r = []
inorden(arbol.raiz, r)
print("Inorden: ", r)
r = []
preorden(arbol.raiz, r)
print("Preorden: ", r)
r = []
postorden(arbol.raiz, r)
print("Postorden:", r)
print("Anchura: ", anchura(arbol.raiz))
print("Altura: ", altura(arbol.raiz))
print("Profundidad de 44:", profundidad(arbol.raiz, 44))
print("Hojas: ", contar_hojas(arbol.raiz))
print("\nEventos (magnitud mayor primero, luego prioridad, luego id):")
eventos = [Evento(1, 3, 25), Evento(2, 5, 11), Evento(1, 5, 40), Evento(3, 3, 7)]
raiz = None
for e in eventos:
raiz = insertar_evento(raiz, e)
r = []
inorden(raiz, r)
print(r)
Python compara tuplas campo por campo, de izquierda a derecha, y el primer campo distinto decide:
(3, 1, 20) < (3, 2, 5) # True: empatan en 3, decide 1 < 2 (el 20 y el 5 ni se miran) (5, 9, 9) < (6, 0, 0) # True: decide el primero, 5 < 6 (-5, 1) < (-3, 1) # True: -5 < -3
if ev.clave() < nodo.valor.clave(): va a la izquierda.class Evento: def __init__(self, prioridad, magnitud, id): self.prioridad = prioridad self.magnitud = magnitud self.id = id def clave(self): # 1° magnitud (mayor primero), 2° prioridad (menor primero), 3° id return (-self.magnitud, self.prioridad, self.id)
El nuevo entra ROJO. Si el padre es negro, listo. Si el padre es rojo, mire al tío (el hermano del padre):
Escriba una secuencia, haga cada inserción en papel y luego compare.