Información de la Tarea
Estudiante: Andrés Cruz Chipol
Curso: Análisis y Diseño de Algoritmos
Fecha de entrega: Martes 9 de diciembre, 2025
Descripción de la Tarea
Realizar el árbol de intervalos usando un árbol rojo y negro. Llenar el árbol con 10 intervalos. Quitar y poner 5 intervalos. Repetir tres veces el punto anterior y visualizar los árboles resultantes. También checar en cada árbol la búsqueda de algún intervalo y reportarlo.
Árbol de Intervalos
Introducción
Un árbol de intervalos es una estructura de datos que mantiene un conjunto dinámico de intervalos cerrados, permitiendo operaciones eficientes de inserción, borrado y búsqueda de intervalos que se solapan con un intervalo dado.
El árbol de intervalos es una extensión de los árboles de búsqueda binaria balanceados (específicamente árboles rojo-negro) donde cada nodo almacena un intervalo [low, high] y un valor adicional max que representa el máximo extremo derecho de todos los intervalos en su subárbol.
Propiedades del Árbol de Intervalos
Un árbol de intervalos tiene las siguientes características:
- Basado en BST: Los nodos se ordenan por el extremo inferior (
low) del intervalo. - Atributo max: Cada nodo mantiene el máximo de todos los extremos superiores (
high) en su subárbol. - Balance: Utiliza las propiedades de un árbol rojo-negro para mantener el balance.
- Solapamiento: Dos intervalos
[a, b]y[c, d]se solapan sia ≤ dyc ≤ b.
El atributo max es crucial para la eficiencia de la búsqueda, ya que permite podar ramas del árbol que no pueden contener intervalos solapados.
Operaciones del Árbol de Intervalos
Las operaciones principales son:
- interval_insert: Inserta un nuevo intervalo manteniendo las propiedades del árbol rojo-negro y actualizando los valores
max. - interval_delete: Elimina un intervalo y rebalancea el árbol.
- interval_search: Busca un intervalo que se solape con el intervalo dado.
- interval_search_all: Encuentra todos los intervalos que se solapan con el intervalo dado.
Todas las operaciones se realizan en tiempo O(log n), excepto interval_search_all que es O(k log n) donde k es el número de solapamientos.
Pseudocódigo
Estructura del Nodo de Intervalo
INTERVAL-NODE int : intervalo [low, high] max : máximo extremo derecho en el subárbol left : hijo izquierdo right : hijo derecho p : padre color : ROJO o NEGROVerificación de Solapamiento
OVERLAP(i1, i2)1 return i1.low ≤ i2.high and i2.low ≤ i1.highActualizar Max
UPDATE-MAX(x)1 if x ≠ T.nil2 m = x.int.high3 if x.left ≠ T.nil4 m = max(m, x.left.max)5 if x.right ≠ T.nil6 m = max(m, x.right.max)7 x.max = mRotación Izquierda (con actualización de max)
LEFT-ROTATE(T, x)1 y = x.right2 x.right = y.left3 if y.left ≠ T.nil4 y.left.p = x5 y.p = x.p6 if x.p == T.nil7 T.root = y8 elseif x == x.p.left9 x.p.left = y10 else x.p.right = y11 y.left = x12 x.p = y13 UPDATE-MAX(x) // Actualizar max después de rotación14 UPDATE-MAX(y)Rotación Derecha (con actualización de max)
RIGHT-ROTATE(T, x)1 y = x.left2 x.left = y.right3 if y.right ≠ T.nil4 y.right.p = x5 y.p = x.p6 if x.p == T.nil7 T.root = y8 elseif x == x.p.right9 x.p.right = y10 else x.p.left = y11 y.right = x12 x.p = y13 UPDATE-MAX(x) // Actualizar max después de rotación14 UPDATE-MAX(y)Insertar Intervalo
INTERVAL-INSERT(T, i)1 z = new INTERVAL-NODE with interval i2 z.max = i.high3 z.left = T.nil4 z.right = T.nil5 y = T.nil6 x = T.root7 while x ≠ T.nil8 y = x9 x.max = max(x.max, i.high) // Actualizar max en el camino10 if i.low < x.int.low11 x = x.left12 else x = x.right13 z.p = y14 if y == T.nil15 T.root = z16 elseif z.int.low < y.int.low17 y.left = z18 else y.right = z19 z.color = RED20 RB-INSERT-FIXUP(T, z)Buscar Intervalo con Solapamiento
INTERVAL-SEARCH(T, i)1 x = T.root2 while x ≠ T.nil and not OVERLAP(x.int, i)3 if x.left ≠ T.nil and x.left.max ≥ i.low4 x = x.left5 else x = x.right6 return xBuscar Todos los Solapamientos
INTERVAL-SEARCH-ALL(T, i)1 resultados = []2 INTERVAL-SEARCH-ALL-HELPER(T.root, i, resultados)3 return resultados
INTERVAL-SEARCH-ALL-HELPER(nodo, i, resultados)1 if nodo == T.nil2 return3 if OVERLAP(nodo.int, i)4 resultados.append(nodo)5 if nodo.left ≠ T.nil and nodo.left.max ≥ i.low6 INTERVAL-SEARCH-ALL-HELPER(nodo.left, i, resultados)7 if nodo.right ≠ T.nil and nodo.int.low ≤ i.high8 INTERVAL-SEARCH-ALL-HELPER(nodo.right, i, resultados)Eliminar Intervalo
INTERVAL-DELETE(T, i)1 z = BUSCAR-EXACTO(T, i)2 if z == T.nil3 return FALSE4 y = z5 y-original-color = y.color6 if z.left == T.nil7 x = z.right8 x-padre = z.p9 RB-TRANSPLANT(T, z, z.right)10 elseif z.right == T.nil11 x = z.left12 x-padre = z.p13 RB-TRANSPLANT(T, z, z.left)14 else y = TREE-MINIMUM(z.right)15 y-original-color = y.color16 x = y.right17 if y.p == z18 x.p = y19 x-padre = y20 else x-padre = y.p21 RB-TRANSPLANT(T, y, y.right)22 y.right = z.right23 y.right.p = y24 RB-TRANSPLANT(T, z, y)25 y.left = z.left26 y.left.p = y27 y.color = z.color28 UPDATE-MAX(y)29 PROPAGAR-MAX-ARRIBA(x-padre) // Propagar max hacia arriba30 if y-original-color == BLACK31 RB-DELETE-FIXUP(T, x)32 return TRUEPropagar Max hacia Arriba
PROPAGAR-MAX-ARRIBA(nodo)1 while nodo ≠ nil and nodo ≠ T.nil2 UPDATE-MAX(nodo)3 nodo = nodo.pImplementación en Python
import graphvizimport random
ROJO = 0NEGRO = 1
class Intervalo: def __init__(self, low, high): self.low = low self.high = high
def __str__(self): return f"[{self.low}, {self.high}]"
def __repr__(self): return self.__str__()
def __eq__(self, other): if other is None: return False return self.low == other.low and self.high == other.high
class NodoIntervalo: def __init__(self, intervalo): self.intervalo = intervalo self.max = intervalo.high if intervalo else 0 self.izq = None self.der = None self.p = None self.color = ROJO
class ArbolIntervalos:
def __init__(self): self.NIL = NodoIntervalo(Intervalo(0, 0)) self.NIL.color = NEGRO self.NIL.max = float('-inf') self.NIL.izq = None self.NIL.der = None self.NIL.p = None self.raiz = self.NIL
def _actualizar_max(self, x): if x != self.NIL: m = x.intervalo.high if x.izq != self.NIL: m = max(m, x.izq.max) if x.der != self.NIL: m = max(m, x.der.max) x.max = m
def _propagar_max_arriba(self, nodo): while nodo is not None and nodo != self.NIL: self._actualizar_max(nodo) nodo = nodo.p
# Rotación izquierda def rotar_izquierda(self, x): y = x.der x.der = y.izq
if y.izq != self.NIL: y.izq.p = x
y.p = x.p
if x.p == self.NIL: self.raiz = y elif x == x.p.izq: x.p.izq = y else: x.p.der = y
y.izq = x x.p = y
self._actualizar_max(x) self._actualizar_max(y)
# Rotación derecha def rotar_derecha(self, x): y = x.izq x.izq = y.der
if y.der != self.NIL: y.der.p = x
y.p = x.p
if x.p == self.NIL: self.raiz = y elif x == x.p.der: x.p.der = y else: x.p.izq = y
y.der = x x.p = y
self._actualizar_max(x) self._actualizar_max(y)
# INTERVAL-INSERT def interval_insert(self, intervalo): z = NodoIntervalo(intervalo) z.izq = self.NIL z.der = self.NIL
y = self.NIL x = self.raiz
while x != self.NIL: y = x x.max = max(x.max, intervalo.high)
if z.intervalo.low < x.intervalo.low: x = x.izq else: x = x.der
z.p = y
if y == self.NIL: self.raiz = z elif z.intervalo.low < y.intervalo.low: y.izq = z else: y.der = z
z.color = ROJO self._insertar_fixup(z)
def _insertar_fixup(self, z): while z.p.color == ROJO: if z.p == z.p.p.izq: y = z.p.p.der
if y.color == ROJO: z.p.color = NEGRO y.color = NEGRO z.p.p.color = ROJO z = z.p.p else: if z == z.p.der: z = z.p self.rotar_izquierda(z) z.p.color = NEGRO z.p.p.color = ROJO self.rotar_derecha(z.p.p) else: y = z.p.p.izq
if y.color == ROJO: z.p.color = NEGRO y.color = NEGRO z.p.p.color = ROJO z = z.p.p else: if z == z.p.izq: z = z.p self.rotar_derecha(z) z.p.color = NEGRO z.p.p.color = ROJO self.rotar_izquierda(z.p.p)
self.raiz.color = NEGRO
# Verifica si dos intervalos se solapan def _overlap(self, i1, i2): return i1.low <= i2.high and i2.low <= i1.high
# INTERVAL-SEARCH def interval_search(self, i): x = self.raiz
while x != self.NIL and not self._overlap(x.intervalo, i): if x.izq != self.NIL and x.izq.max >= i.low: x = x.izq else: x = x.der
return x if x != self.NIL else None
# Busca todos los solapamientos def interval_search_all(self, i): resultados = [] self._interval_search_all_helper(self.raiz, i, resultados) return resultados
def _interval_search_all_helper(self, nodo, i, resultados): if nodo == self.NIL: return
if self._overlap(nodo.intervalo, i): resultados.append(nodo)
if nodo.izq != self.NIL and nodo.izq.max >= i.low: self._interval_search_all_helper(nodo.izq, i, resultados)
if nodo.der != self.NIL and nodo.intervalo.low <= i.high: self._interval_search_all_helper(nodo.der, i, resultados)
# Búsqueda exacta def buscar_exacto(self, intervalo): return self._buscar_exacto_helper(self.raiz, intervalo)
def _buscar_exacto_helper(self, nodo, intervalo): if nodo == self.NIL: return nodo
if intervalo.low == nodo.intervalo.low and intervalo.high == nodo.intervalo.high: return nodo
if intervalo.low < nodo.intervalo.low: return self._buscar_exacto_helper(nodo.izq, intervalo) else: resultado = self._buscar_exacto_helper(nodo.der, intervalo) if resultado == self.NIL and intervalo.low == nodo.intervalo.low: resultado = self._buscar_exacto_helper(nodo.izq, intervalo) return resultado
def _transplantar(self, u, v): if u.p == self.NIL: self.raiz = v elif u == u.p.izq: u.p.izq = v else: u.p.der = v v.p = u.p
def minimo(self, x=None): if x is None: x = self.raiz while x.izq != self.NIL: x = x.izq return x
# INTERVAL-DELETE def interval_delete(self, intervalo): z = self.buscar_exacto(intervalo)
if z == self.NIL: print(f" {intervalo} no encontrado") return False
y = z y_color_original = y.color x_padre = None
if z.izq == self.NIL: x = z.der x_padre = z.p self._transplantar(z, z.der) elif z.der == self.NIL: x = z.izq x_padre = z.p self._transplantar(z, z.izq) else: y = self.minimo(z.der) y_color_original = y.color x = y.der
if y.p == z: x.p = y x_padre = y else: x_padre = y.p self._transplantar(y, y.der) y.der = z.der y.der.p = y
self._transplantar(z, y) y.izq = z.izq y.izq.p = y y.color = z.color self._actualizar_max(y)
if x_padre and x_padre != self.NIL: self._propagar_max_arriba(x_padre)
if y_color_original == NEGRO: self._borrar_fixup(x)
return True
def _borrar_fixup(self, x): while x != self.raiz and x.color == NEGRO: if x == x.p.izq: w = x.p.der
if w.color == ROJO: w.color = NEGRO x.p.color = ROJO self.rotar_izquierda(x.p) w = x.p.der
if w.izq.color == NEGRO and w.der.color == NEGRO: w.color = ROJO x = x.p else: if w.der.color == NEGRO: w.izq.color = NEGRO w.color = ROJO self.rotar_derecha(w) w = x.p.der w.color = x.p.color x.p.color = NEGRO w.der.color = NEGRO self.rotar_izquierda(x.p) x = self.raiz else: w = x.p.izq
if w.color == ROJO: w.color = NEGRO x.p.color = ROJO self.rotar_derecha(x.p) w = x.p.izq
if w.der.color == NEGRO and w.izq.color == NEGRO: w.color = ROJO x = x.p else: if w.izq.color == NEGRO: w.der.color = NEGRO w.color = ROJO self.rotar_izquierda(w) w = x.p.izq w.color = x.p.color x.p.color = NEGRO w.izq.color = NEGRO self.rotar_derecha(x.p) x = self.raiz
x.color = NEGRO
def recorrido_inorder(self, x): if x != self.NIL: self.recorrido_inorder(x.izq) color = "R" if x.color == ROJO else "N" print(f"{x.intervalo}(max={x.max})({color})", end=' ') self.recorrido_inorder(x.der)
def imprimir_inorder(self): self.recorrido_inorder(self.raiz) print()
def imprimir_arbol(self): self._imprimir_arbol_helper(self.raiz, "", True)
def _imprimir_arbol_helper(self, nodo, indent, ultimo): if nodo != self.NIL: print(indent, end='') if ultimo: print("R----", end='') indent += " " else: print("L----", end='') indent += "| "
color_str = "ROJO" if nodo.color == ROJO else "NEGRO" print(f"{nodo.intervalo} (max={nodo.max}) [{color_str}]") self._imprimir_arbol_helper(nodo.izq, indent, False) self._imprimir_arbol_helper(nodo.der, indent, True)
def crear_desde_lista(self, lista_intervalos): for intervalo in lista_intervalos: self.interval_insert(intervalo) return self
def obtener_todos_intervalos(self): intervalos = [] self._obtener_todos_helper(self.raiz, intervalos) return intervalos
def _obtener_todos_helper(self, nodo, lista): if nodo != self.NIL: self._obtener_todos_helper(nodo.izq, lista) lista.append(nodo.intervalo) self._obtener_todos_helper(nodo.der, lista)
def crear_arbol_desde_lista(lista_intervalos): arbol = ArbolIntervalos() arbol.crear_desde_lista(lista_intervalos) return arbolDemostración con Múltiples Iteraciones
Se creó un árbol de intervalos con 10 intervalos aleatorios iniciales, y se realizaron 3 iteraciones donde en cada una se eliminan 5 intervalos y se insertan 5 nuevos, demostrando que la estructura mantiene sus propiedades después de cada operación.
if __name__ == "__main__":
print("\nArbol de Intervalos\n")
arbol = ArbolIntervalos() lista_intervalos = []
print("\nInsertando 10 intervalos:\n") for _ in range(10): low = random.randint(1, 50) length = random.randint(5, 20) intervalo = Intervalo(low, low + length) lista_intervalos.append(intervalo) arbol.interval_insert(intervalo) print(f" INSERT {intervalo}")
print("\nArbol inicial:") arbol.imprimir_arbol() arbol.generar_diagrama("arbol_0_inicial")
# Busqueda inicial busqueda = Intervalo(random.randint(10, 30), random.randint(35, 50)) print(f"\nBusqueda: {busqueda}") resultado = arbol.interval_search(busqueda) if resultado: print(f" Encontrado: {resultado.intervalo}") else: print(" No encontrado")
for iteracion in range(1, 4): print(f"\n--- Iteracion {iteracion} ---")
print("\nEliminando 5 intervalos:") if len(lista_intervalos) >= 5: a_borrar = lista_intervalos[:5] lista_intervalos = lista_intervalos[5:]
for intervalo in a_borrar: print(f" DELETE {intervalo}") arbol.interval_delete(intervalo)
print("\nInsertando 5 intervalos:") for _ in range(5): low = random.randint(1, 60) length = random.randint(5, 25) nuevo = Intervalo(low, low + length) print(f" INSERT {nuevo}") lista_intervalos.append(nuevo) arbol.interval_insert(nuevo)
print(f"\nArbol resultante:") arbol.imprimir_arbol() arbol.generar_diagrama(f"arbol_{iteracion}_iteracion")
busqueda = Intervalo(random.randint(15, 35), random.randint(40, 55)) print(f"\nBusqueda: {busqueda}") resultado = arbol.interval_search(busqueda)
if resultado: print(f" Encontrado: {resultado.intervalo}") todos = arbol.interval_search_all(busqueda) if len(todos) > 1: print(f" Total solapamientos: {len(todos)}") for nodo in todos: print(f" - {nodo.intervalo}") else: print(" No encontrado")Resultados
Árbol Inicial
Insertando 10 intervalos:
Arbol de Intervalos
Insertando 10 intervalos:
INSERT [8, 13] INSERT [7, 12] INSERT [8, 28] INSERT [40, 47] INSERT [38, 50] INSERT [9, 23] INSERT [41, 53] INSERT [39, 50] INSERT [25, 36] INSERT [24, 43]
Arbol inicial:R----[9, 23] (max=53) [NEGRO] L----[8, 13] (max=28) [ROJO] | L----[7, 12] (max=12) [NEGRO] | R----[8, 28] (max=28) [NEGRO] R----[38, 50] (max=53) [ROJO] L----[25, 36] (max=43) [NEGRO] | L----[24, 43] (max=43) [ROJO] R----[40, 47] (max=53) [NEGRO] L----[39, 50] (max=50) [ROJO] R----[41, 53] (max=53) [ROJO]
Busqueda: [19, 49] Encontrado: [9, 23]Visualización del árbol inicial:

Iteración 1
Eliminando 5 intervalos e insertando 5 nuevos:
--- Iteracion 1 ---
Eliminando 5 intervalos: DELETE [8, 13] DELETE [7, 12] DELETE [8, 28] DELETE [40, 47] DELETE [38, 50]
Insertando 5 intervalos: INSERT [55, 76] INSERT [28, 47] INSERT [18, 23] INSERT [8, 23] INSERT [29, 35]
Arbol resultante:R----[39, 50] (max=76) [NEGRO] L----[24, 43] (max=47) [ROJO] | L----[9, 23] (max=23) [NEGRO] | | L----[8, 23] (max=23) [ROJO] | | R----[18, 23] (max=23) [ROJO] | R----[28, 47] (max=47) [NEGRO] | L----[25, 36] (max=36) [ROJO] | R----[29, 35] (max=35) [ROJO] R----[41, 53] (max=76) [NEGRO] R----[55, 76] (max=76) [ROJO]
Busqueda: [27, 41] Encontrado: [39, 50] Total solapamientos: 6 - [39, 50] - [24, 43] - [28, 47] - [25, 36] - [29, 35] - [41, 53]Visualización del árbol después de la iteración 1:

Iteración 2
Eliminando 5 intervalos e insertando 5 nuevos:
--- Iteracion 2 ---
Eliminando 5 intervalos: DELETE [9, 23] DELETE [41, 53] DELETE [39, 50] DELETE [25, 36] DELETE [24, 43]
Insertando 5 intervalos: INSERT [21, 31] INSERT [37, 46] INSERT [9, 21] INSERT [12, 24] INSERT [21, 40]
Arbol resultante:R----[28, 47] (max=76) [NEGRO] L----[18, 23] (max=40) [ROJO] | L----[9, 21] (max=24) [NEGRO] | | L----[8, 23] (max=23) [ROJO] | | R----[12, 24] (max=24) [ROJO] | R----[21, 31] (max=40) [NEGRO] | R----[21, 40] (max=40) [ROJO] R----[37, 46] (max=76) [NEGRO] L----[29, 35] (max=35) [ROJO] R----[55, 76] (max=76) [ROJO]
Busqueda: [15, 53] Encontrado: [28, 47] Total solapamientos: 9 - [28, 47] - [18, 23] - [9, 21] - [8, 23] - [12, 24] - [21, 31] - [21, 40] - [37, 46] - [29, 35]Visualización del árbol después de la iteración 2:

Iteración 3
Eliminando 5 intervalos e insertando 5 nuevos:
--- Iteracion 3 ---
Eliminando 5 intervalos: DELETE [55, 76] DELETE [28, 47] DELETE [18, 23] DELETE [8, 23] DELETE [29, 35]
Insertando 5 intervalos: INSERT [9, 24] INSERT [12, 20] INSERT [37, 60] INSERT [54, 72] INSERT [31, 53]
Arbol resultante:R----[21, 31] (max=72) [NEGRO] L----[9, 24] (max=24) [ROJO] | L----[9, 21] (max=21) [NEGRO] | R----[12, 24] (max=24) [NEGRO] | R----[12, 20] (max=20) [ROJO] R----[37, 46] (max=72) [ROJO] L----[21, 40] (max=53) [NEGRO] | R----[31, 53] (max=53) [ROJO] R----[37, 60] (max=72) [NEGRO] R----[54, 72] (max=72) [ROJO]
Busqueda: [23, 40] Encontrado: [21, 31] Total solapamientos: 7 - [21, 31] - [9, 24] - [12, 24] - [37, 46] - [21, 40] - [31, 53] - [37, 60]Visualización del árbol después de la iteración 3:

Referencias
Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2009). Introduction to algorithms (3ra ed.). MIT Press. Capítulo 14: Augmenting Data Structures.