Está en la página 1de 24

Árboles de segmentos

Iván Yesid Castellanos


Motivación
 En algunos casos necesitamos resolver un problema sobre rangos en listas
una única vez:
 Ejemplo: Encontrar el máximo de la lista en el rango ubicado en entre las
posiciones [i, j] (inclusive)
 Podemos recorrer desde el elemento en la posición i hasta el elemento en la
posición j encontrando el máximo del rango – Complejidad O(j- i + 1), en peor
caso O(n)
 ¿Qué pasaría si necesitáramos resolver el problema sobre rangos (en el
mismo arreglo) q veces?
 Utilizando el mismo enfoque anterior estaríamos realizando un método de
complejidad O(n) q veces, es decir, la complejidad total será O(q*n)
 Veremos un enfoque de resolver este problema con árboles que será mas
eficiente
Arboles de segmentos

 Los arboles de segmentos son arboles binarios (balanceados)


en los cuales tendremos la solución a problemas de rangos (o
segmentos) en un arreglo
 Con estos arboles podremos solucionar eficientemente
problemas relacionados con rangos cuando hay muchas
consultas
 Al igual que en muchas aplicaciones de arboles vamos a
buscar que cada operación en el árbol de segmentos tenga
complejidad O(log n)
Propiedades árboles de segmentos

 Los problemas que vamos a resolver con árboles de segmentos deben ser
problemas que se puedan resolver a partir de la combinación de la
solución de 2 subproblemas del problema original
 Ejemplo: Para calcular el mínimo entre las posiciones [i, j] podemos calcular
primero el mínimo entre las posiciones [i, k] y [k+1, j] y el mínimo de esas 2
soluciones es la solución del problema [i, j]
 Podremos ir guardando el resultado de los subproblemas para calcular la
solución de los problemas “mas grandes” sin necesidad de recalcular estas
soluciones
 En el enfoque de árboles vamos a tener en cada nodo la solución de un
subproblema
Propiedades árboles de segmentos

 Iniciamos con el problema mas grande y la idea es ir construyendo el árbol


dividiendo el problema almacenado en un nodo de forma equitativa en 2
subproblemas mas pequeños hasta llegar a los subproblemas mas
pequeños posibles
 En la raíz tendremos la solución para el problema mas grande posible
 Ejemplo: Para el caso de los mínimos, en la raíz tendremos el mínimo elemento
de todo el arreglo
 En las hojas tendremos los subproblemas mas pequeños posibles
 Ejemplo: Para el caso de los mínimos, en las hojas tendremos el valor de un
elemento, es decir, en alguna hoja tendremos el i-esimo elemento del arreglo,
pues este es precisamente el mínimo en el rango [i, i]
TDA árbol de segmentos

 Instancias: un arreglo de elementos, de los cuales saldrán las


operaciones por rangos que toque solucionar
 Operaciones:
 Inicializar( elementos[] ): crea el árbol de segmentos a partir de un
arreglo con los elementos
 Consultar(a, b): retorna la solución del problema para el rango [a, b]
 Actualizar(a, x): reemplaza el elemento a-ésimo en el arreglo por x, esto
debe mantener la propiedad de árbol de segmentos
Implementación
Se puede implementar tanto con arreglos como con apuntadores pero hay
que tener en cuenta algunas consideraciones:
 En el caso de la implementación con arreglos podemos extender la
cantidad de elementos a una potencia de 2 para tener un árbol perfecto
 Por ejemplo: si se tiene 14 elementos suponer que hay 16, si se tienen 100
suponer que hay 1024
 Los elementos adicionales se escogen dependiendo de la operación
realizada, para el caso del mínimo lo ideal sería escoger elementos infinitos
(muy grandes) de modo que no afecten el mínimo
 En el caso de la implementación con apuntadores tocará definir las
funciones de forma recursiva y no tendremos acceso directo a los
elementos originales (las hojas) sino por medio de la raíz
 La implementación queda como tarea del estudiante
Inicialización
 Primero construimos el árbol a partir del arreglo de
elementos, para esto inicializamos la raíz con el
rango completo del árbol y empezamos a partir
(iterativa o recursivamente) los nodos partiendo el
rango en 2 partes equitativas (la cantidad de ambas
partes difiere a lo sumo en 1)

 Nos detenemos cuando lleguemos a rangos de


tamaño 1, es decir, subproblemas que no se pueden
partir más
Inicialización
 Llenamos las hojas del árbol con los elementos de
nuestro arreglo
 Empezamos a propagar hacia arriba (iterativa o
recursivamente) obteniendo el resultado para cada
uno de los rangos guardados en los nodos
 Finalizamos este procedimiento cuando lleguemos a
la raíz
 Complejidad: O(n) – se hace una única vez por
arreglo
Ejemplo inicialización
 Inicialicemos el árbol de segmentos correspondiente al arreglo [-20, 19, 7,
4, -10, 5, 100, 1, 3]
Ejemplo inicialización
 [-20, 19, 7, 4, -10, 5, 100, 1, 3]
Ejemplo inicialización
 [-20, 19, 7, 4, -10, 5, 100, 1, 3]
Actualizar
 Adicional a conocer las respuesta en un rango, el árbol de
segmentos estándar es lo suficientemente flexible para también
permitir actualizar individualmente valores en el arreglo (de forma
eficiente) sin dañar los resultados del árbol
 Ejemplo: inicialmente teníamos el arreglo [-20, 19, 7, 4, -10, 5, 100, 1, 3]
cuyo mínimo total era -20, después actualizamos el valor en la posición
6 por -99 entonces el arreglo es [-20, 19, -7, 4, -10, 5, -99, 1, 3] cuyo
mínimo total es -99
 Para actualizar un valor en nuestro arreglo debemos primero
acceder al subproblema base (la hoja) que contiene ese elemento
y cambiarlo
 Luego hacer propagación hacia arriba cambiando los
correspondientes rangos padres hasta la raíz en el peor caso
 Complejidad: O(log n)
Ejemplo Actualizar
 Actualizar(6, -99)
Ejemplo Actualizar
 Actualizar(6, -99)
Consultar
Para consultar tenemos que considerar 2 casos:
 Caso 1: Si el rango exacto que queremos consultar está en
algún nodo basta simplemente con retornar el valor que
tiene ese nodo
 Para hacer el recorrido hasta ese nodo vamos desde la raíz
revisando que el rango esté contenido completamente en el
hijo izquierdo o en el hijo derecho, y realizamos el
procedimiento hasta que lleguemos al rango esperado
 Complejidad: O(log n)
Ejemplo Consultar
 Ejemplo del primer caso, consultar(5, 6)
Consultar
Para consultar tenemos que considerar 2 casos:
 Caso 2: Cuando el rango exacto que queremos consultar no
está en ningún nodo es porque en algún momento debemos
partir el nodo en 2 partes, entonces simplemente partiremos
los nodos
 Para hacer ese recorrido vamos mirando desde la raíz si el
rango está contenido completamente en el hijo izquierdo,
en el derecho o toca partirlo y así nos vamos expandiendo
hacia abajo hasta completar todo el rango uniendo el
resultado de los nodos que tienen particiones del rango
 No es posible visitar mas de 4*log(n) nodos realizando este
procedimiento, por lo tanto la complejidad es O(log n)
Ejemplo Consultar
 Consultar(0, 7)
Complejidades
Operaciones Listas Árboles de
segmentos
Consultar O(n) O(log n)

Actualizar O(1) O(log n)

Q operaciones O(Q * n) O(Q * log n)

Si tenemos Q = 100000 y n = 100000

Con listas hacemos aproximadamente 10000000000 operaciones


Con arboles de segmentos hacemos aproximadamente 2000000
operaciones (aprox 500 veces mas rápido)
Test árbol de segmentos
Test árbol de segmentos
Extensiones
El árbol de segmentos mostrado es el que se usa de manera estándar para las
aplicaciones vistas, pero existen arboles de segmentos especializados para
problemas similares:
 Árbol de segmentos con lazy propagation: Funciona cuando en lugar de
actualizar individualmente elementos quiero actualizar rangos
(poniéndoles el mismo elemento)
 Ejemplo: Inicialmente teníamos el arreglo [-20, 19, 7, 4, -10, 5, 100, 1, 3] y
queremos actualizar el rango [2,5] con el elemento -30, el nuevo arreglo para
análisis sería [-20, 19, -30, -30, -30, -30, 100, 1, 3]
 Lazy propagation consiste en una técnica en la que no actualizamos desde las
hojas hasta la raíz completamente, sino que vamos de la raíz y actualizamos
hasta los rangos totalmente necesarios que deba actualizar (por eso es lazy)
 Adicional debemos llevar una marca en los nodos para saber si están
actualizados o no
Extensiones
 Árbol de Fenwick o Árbol Indexado Binario (BIT): Funciona igual que el
árbol de segmentos estándar, pero la representación del árbol la hace a
partir de bits y las operaciones de recorrido de este árbol las hace también
con operaciones de bits
 El código del BIT es mas corto y requiere menos memoria, lo que lo puede hacer
una buena alternativa para dispositivos móviles o sistemas embebidos
 Árbol de cuadrados (Quadtree): Es una extensión del árbol de segmentos
a matrices, en algunos problemas toca realizar consultas en 2D, para
hacer esto de forma eficiente utilizamos un árbol que parte cuadrados (o
rectángulos) en 4 partes y el funcionamiento es similar al del árbol de
segmentos
 Ejemplo: para imágenes médicas podemos requerir hacer consultas sobre varios
rangos en las coordenadas x y y de la imagen para saber si hay pixeles de cierto
tono con esto saber si puede existir alguna anomalia

También podría gustarte