Un quadtree es una estructura de datos en forma de árbol en la que cada nodo interno tiene exactamente cuatro hijos. Los quadtrees son el análogo bidimensional de los octrees y se utilizan con mayor frecuencia para dividir un espacio bidimensional subdividándolo recursivamente en cuatro cuadrantes o regiones. Los datos asociados con una celda de hoja varían según la aplicación, pero la celda de hoja representa una "unidad de información espacial interesante".
Las regiones subdivididas pueden ser cuadradas o rectangulares, o pueden tener formas arbitrarias. Esta estructura de datos fue denominada quadtree por Raphael Finkel y JL Bentley en 1974. [1] Una partición similar también se conoce como Q-tree .
Todas las formas de árboles cuadráticos comparten algunas características comunes:
Una pirámide-árbol ( T-pirámide ) es un árbol "completo"; cada nodo de la pirámide-T tiene cuatro nodos secundarios, excepto los nodos de hojas; todas las hojas están en el mismo nivel, el nivel que corresponde a los píxeles individuales en la imagen. Los datos en una pirámide-árbol se pueden almacenar de forma compacta en una matriz como una estructura de datos implícita similar a la forma en que un árbol binario completo se puede almacenar de forma compacta en una matriz . [2]
Los árboles cuaternarios se pueden clasificar según el tipo de datos que representan, incluidas áreas, puntos, líneas y curvas. Los árboles cuaternarios también se pueden clasificar según si la forma del árbol es independiente del orden en el que se procesan los datos. A continuación, se enumeran los tipos comunes de árboles cuaternarios.
El quadtree de región representa una partición del espacio en dos dimensiones al descomponer la región en cuatro cuadrantes iguales, subcuadrantes, etc., con cada nodo hoja que contiene datos correspondientes a una subregión específica. Cada nodo del árbol tiene exactamente cuatro hijos o no tiene hijos (un nodo hoja). La altura de los quadtrees que siguen esta estrategia de descomposición (es decir, subdividir los subcuadrantes siempre que haya datos interesantes en el subcuadrante para el que se desea un mayor refinamiento) es sensible y depende de la distribución espacial de las áreas interesantes en el espacio que se está descomponiendo. El quadtree de región es un tipo de trie .
Un árbol cuaternario de regiones con una profundidad de n puede utilizarse para representar una imagen que consta de 2 n × 2 n píxeles, donde cada valor de píxel es 0 o 1. El nodo raíz representa toda la región de la imagen. Si los píxeles de una región no son todos 0 o 1, se subdivide. En esta aplicación, cada nodo de hoja representa un bloque de píxeles que son todos 0 o todos 1. Observe el potencial ahorro en términos de espacio cuando estos árboles se utilizan para almacenar imágenes; las imágenes suelen tener muchas regiones de tamaño considerable que tienen el mismo valor de color en todas partes. En lugar de almacenar una gran matriz 2D de cada píxel de la imagen, un árbol cuaternario puede capturar la misma información potencialmente a muchos niveles de división más altos que las celdas de tamaño de resolución de píxel que necesitaríamos de otro modo. La resolución del árbol y el tamaño general están limitados por los tamaños de píxel e imagen.
Un árbol cuaternario de regiones también se puede utilizar como representación de resolución variable de un campo de datos. Por ejemplo, las temperaturas de un área se pueden almacenar como un árbol cuaternario, en el que cada nodo hoja almacena la temperatura promedio de la subregión que representa.
El árbol cuaternario de puntos [3] es una adaptación de un árbol binario que se utiliza para representar datos puntuales bidimensionales. Comparte las características de todos los árboles cuaternarios, pero es un árbol verdadero, ya que el centro de una subdivisión siempre está en un punto. Suele ser muy eficiente para comparar puntos de datos ordenados bidimensionales, y normalmente funciona en tiempo O(log n) . Vale la pena mencionar los árboles cuaternarios de puntos por su integridad, pero han sido superados por los árboles k -d como herramientas para la búsqueda binaria generalizada. [4]
Los árboles de puntos cuadráticos se construyen de la siguiente manera. Dado el siguiente punto que se va a insertar, buscamos la celda en la que se encuentra y lo añadimos al árbol. El nuevo punto se añade de forma que la celda que lo contiene se divide en cuadrantes por las líneas verticales y horizontales que pasan por el punto. En consecuencia, las celdas son rectangulares, pero no necesariamente cuadradas. En estos árboles, cada nodo contiene uno de los puntos de entrada.
Dado que la división del plano se decide por el orden de inserción de los puntos, la altura del árbol es sensible y depende del orden de inserción. Insertar en un orden "incorrecto" puede dar lugar a un árbol con una altura lineal en relación con el número de puntos de entrada (momento en el que se convierte en una lista enlazada). Si el conjunto de puntos es estático, se puede realizar un preprocesamiento para crear un árbol con una altura equilibrada.
Un nodo de un quadtree de puntos es similar a un nodo de un árbol binario , con la principal diferencia de que tiene cuatro punteros (uno para cada cuadrante) en lugar de dos ("izquierdo" y "derecho") como en un árbol binario común. Además, una clave suele descomponerse en dos partes, que hacen referencia a las coordenadas x e y. Por lo tanto, un nodo contiene la siguiente información:
Los árboles cuaternarios de regiones puntuales (PR) [5] [6] son muy similares a los árboles cuaternarios de regiones. La diferencia es el tipo de información almacenada sobre las celdas. En un árbol cuaternario de regiones, se almacena un valor uniforme que se aplica a toda el área de la celda de una hoja. Sin embargo, las celdas de un árbol cuaternario PR almacenan una lista de puntos que existen dentro de la celda de una hoja. Como se mencionó anteriormente, para los árboles que siguen esta estrategia de descomposición, la altura depende de la distribución espacial de los puntos. Al igual que el árbol cuaternario de puntos, el árbol cuaternario PR también puede tener una altura lineal cuando se le da un conjunto "malo".
Los árboles cuaternarios de aristas [7] [8] (de forma muy similar a los árboles cuaternarios PM) se utilizan para almacenar líneas en lugar de puntos. Las curvas se aproximan subdividiendo las celdas con una resolución muy fina, específicamente hasta que haya un solo segmento de línea por celda. Cerca de las esquinas o los vértices, los árboles cuaternarios de aristas continuarán dividiéndose hasta que alcancen su nivel máximo de descomposición. Esto puede generar árboles extremadamente desequilibrados que pueden anular el propósito de la indexación.
El quadtree de mapa poligonal (o PM Quadtree) es una variación de quadtree que se utiliza para almacenar colecciones de polígonos que pueden estar degenerados (lo que significa que tienen vértices o aristas aislados). [9] [10] Una gran diferencia entre los quadtrees PM y los quadtrees de aristas es que la celda en consideración no se subdivide si los segmentos se encuentran en un vértice de la celda.
Existen tres clases principales de árboles cuaternarios PM, que varían según la información que almacenan dentro de cada nodo negro. Los árboles cuaternarios PM3 pueden almacenar cualquier cantidad de aristas que no se intersequen y, como máximo, un punto. Los árboles cuaternarios PM2 son iguales a los árboles cuaternarios PM3, excepto que todas las aristas deben compartir el mismo punto final. Por último, los árboles cuaternarios PM1 son similares a los PM2, pero los nodos negros pueden contener un punto y sus aristas o solo un conjunto de aristas que comparten un punto, pero no se puede tener un punto y un conjunto de aristas que no contengan el punto.
Esta sección resume una subsección de un libro de Sariel Har-Peled . [11]
Si tuviéramos que almacenar cada nodo correspondiente a una celda subdividida, podríamos terminar almacenando muchos nodos vacíos. Podemos reducir el tamaño de estos árboles dispersos almacenando solo subárboles cuyas hojas tengan datos interesantes (es decir, "subárboles importantes"). En realidad, podemos reducir el tamaño aún más. Cuando solo mantenemos subárboles importantes, el proceso de poda puede dejar caminos largos en el árbol donde los nodos intermedios tienen grado dos (un enlace a un padre y un hijo). Resulta que solo necesitamos almacenar el nodo al comienzo de este camino (y asociar algunos metadatos con él para representar los nodos eliminados) y adjuntar el subárbol enraizado en su extremo a . Todavía es posible que estos árboles comprimidos tengan una altura lineal cuando se les dan puntos de entrada "malos".
Aunque recortamos gran parte del árbol cuando realizamos esta compresión, aún es posible lograr búsquedas, inserciones y eliminaciones en tiempo logarítmico aprovechando las curvas de orden Z. La curva de orden Z asigna cada celda del árbol cuaternario completo (y, por lo tanto, incluso del árbol cuaternario comprimido) en el tiempo a una línea unidimensional (y también la asigna hacia atrás en el tiempo), creando un orden total en los elementos. Por lo tanto, podemos almacenar el árbol cuaternario en una estructura de datos para conjuntos ordenados (en la que almacenamos los nodos del árbol).
Debemos plantear una suposición razonable antes de continuar: suponemos que, dados dos números reales expresados en binario, podemos calcular en el tiempo el índice del primer bit en el que difieren. También suponemos que podemos calcular en el tiempo el ancestro común más bajo de dos puntos/celdas en el árbol cuaternario y establecer su ordenamiento Z relativo , y podemos calcular la función base en el tiempo.
Con estas suposiciones, la ubicación de un punto dado (es decir, determinar la celda que lo contendría ), las operaciones de inserción y eliminación se pueden realizar en el tiempo (es decir, el tiempo que lleva hacer una búsqueda en la estructura de datos del conjunto ordenado subyacente).
Para realizar una localización de puntos (es decir, encontrar su celda en el árbol comprimido):
Sin entrar en detalles específicos, para realizar inserciones y eliminaciones primero ubicamos un punto para lo que queremos insertar/eliminar y luego lo insertamos/eliminamos. Se debe tener cuidado de remodelar el árbol según corresponda, creando y eliminando nodos según sea necesario.
Los árboles cuaternarios, en particular el árbol cuaternario de regiones, se han adaptado bien a las aplicaciones de procesamiento de imágenes. Limitaremos nuestro análisis a los datos de imágenes binarias, aunque los árboles cuaternarios de regiones y las operaciones de procesamiento de imágenes que se realizan en ellos son igualmente adecuados para imágenes en color. [4] [16]
Una de las ventajas de usar quadtrees para la manipulación de imágenes es que las operaciones de unión e intersección de conjuntos se pueden realizar de forma sencilla y rápida. [4] [17] [18] [19] [20] Dadas dos imágenes binarias, la unión de imágenes (también llamada superposición ) produce una imagen en la que un píxel es negro si cualquiera de las imágenes de entrada tiene un píxel negro en la misma ubicación. Es decir, un píxel en la imagen de salida es blanco solo cuando el píxel correspondiente en ambas imágenes de entrada es blanco, de lo contrario, el píxel de salida es negro. En lugar de hacer la operación píxel por píxel, podemos calcular la unión de forma más eficiente aprovechando la capacidad del quadtree para representar múltiples píxeles con un solo nodo. Para los fines de la discusión a continuación, si un subárbol contiene píxeles blancos y negros, diremos que la raíz de ese subárbol está coloreada de gris.
El algoritmo funciona recorriendo los dos árboles cuaternarios de entrada ( y ) mientras se construye el árbol cuaternario de salida . De manera informal, el algoritmo es el siguiente. Considere los nodos y correspondientes a la misma región en las imágenes.
Si bien este algoritmo funciona, no garantiza por sí mismo un árbol cuaternario de tamaño mínimo. Por ejemplo, considere el resultado si uniéramos un tablero de ajedrez (donde cada mosaico es un píxel) de tamaño con su complemento. El resultado es un cuadrado negro gigante que debería estar representado por un árbol cuaternario con solo el nodo raíz (de color negro), pero en cambio el algoritmo produce un árbol cuaternario completo de profundidad . Para solucionar esto, realizamos un recorrido de abajo hacia arriba del árbol cuaternario resultante donde verificamos si los cuatro nodos hijos tienen el mismo color, en cuyo caso reemplazamos su padre con una hoja del mismo color. [4]
La intersección de dos imágenes es prácticamente el mismo algoritmo. Una forma de pensar en la intersección de las dos imágenes es que estamos realizando una unión con respecto a los píxeles blancos . Por lo tanto, para realizar la intersección intercambiamos las menciones de blanco y negro en el algoritmo de unión.
Considere dos píxeles negros vecinos en una imagen binaria. Son adyacentes si comparten un borde horizontal o vertical delimitador. En general, dos píxeles negros están conectados si se puede llegar a uno desde el otro moviéndose solo a los píxeles adyacentes (es decir, hay un camino de píxeles negros entre ellos donde cada par consecutivo es adyacente). Cada conjunto máximo de píxeles negros conectados es un componente conectado . Usando la representación de imágenes en quadtree, Samet [21] mostró cómo podemos encontrar y etiquetar estos componentes conectados en un tiempo proporcional al tamaño del quadtree. [4] [22] Este algoritmo también se puede usar para colorear polígonos.
El algoritmo funciona en tres pasos:
Para simplificar el análisis, supongamos que los hijos de un nodo en el árbol cuaternario siguen el orden Z (SO, NO, SE, NE). Como podemos contar con esta estructura, para cualquier celda sabemos cómo navegar por el árbol cuaternario para encontrar las celdas adyacentes en los diferentes niveles de la jerarquía.
El primer paso se logra con un recorrido de orden posterior del árbol cuaternario. Para cada hoja negra, observamos el nodo o los nodos que representan celdas que son vecinas del norte y vecinas del este (es decir, las celdas del norte y del este que comparten bordes con la celda de ). Dado que el árbol está organizado en orden Z , tenemos la invariante de que ya se han tenido en cuenta y atendido a los vecinos del sur y del oeste. Sea . Si representa píxeles negros:
El segundo paso se puede lograr utilizando la estructura de datos union-find . [23] Comenzamos con cada etiqueta única como un conjunto separado. Para cada relación de equivalencia observada en el primer paso, unimos los conjuntos correspondientes. Luego, cada conjunto restante distinto se asociará con un componente conectado distinto en la imagen.
El tercer paso realiza otro recorrido posterior al orden. Esta vez, para cada nodo negro, utilizamos la operación find de union-find (con la etiqueta anterior de ) para buscar y asignar su nueva etiqueta (asociada con el componente conectado del cual es parte).
Esta sección resume un capítulo de un libro de Har-Peled y de Berg et al. [24] [25]
La generación de mallas es esencialmente la triangulación de un conjunto de puntos para el cual se puede realizar un procesamiento posterior. Como tal, es deseable que la triangulación resultante tenga ciertas propiedades (como falta de uniformidad, triángulos que no sean "demasiado delgados", triángulos grandes en áreas dispersas y triángulos pequeños en áreas densas, etc.) para que el procesamiento posterior sea más rápido y menos propenso a errores. Los árboles cuádruples creados a partir del conjunto de puntos se pueden utilizar para crear mallas con estas propiedades deseadas.
Considere una hoja del quadtree y su celda correspondiente . Decimos que está equilibrado (para la generación de mallas) si los lados de la celda son intersectados por los puntos de las esquinas de las celdas vecinas como máximo una vez en cada lado. Esto significa que los niveles del quadtree de las hojas adyacentes a difieren como máximo en uno del nivel de . Cuando esto es cierto para todas las hojas, decimos que todo el quadtree está equilibrado (para la generación de mallas).
Consideremos la celda y el vecindario de celdas del mismo tamaño centradas en . Llamamos a este vecindario el grupo extendido . Decimos que el árbol cuádruple está bien equilibrado si está equilibrado y, para cada hoja que contiene un punto del conjunto de puntos, su grupo extendido también está en el árbol cuádruple y el grupo extendido no contiene ningún otro punto del conjunto de puntos.
La creación de la malla se realiza de la siguiente manera:
Consideramos los puntos de las esquinas de las celdas del árbol como vértices en nuestra triangulación. Antes del paso de transformación, tenemos un montón de cajas con puntos en algunas de ellas. El paso de transformación se realiza de la siguiente manera: para cada punto, deformamos la esquina más cercana de su celda para que se encuentre con él y triangulamos los cuatro cuadrángulos resultantes para formar triángulos "agradables" (el lector interesado puede consultar el capítulo 12 de Har-Peled [24] para obtener más detalles sobre qué hace que los triángulos sean "agradables").
Los cuadrados restantes se triangulan de acuerdo con algunas reglas simples. Para cada cuadrado regular (sin puntos dentro ni puntos de esquina en sus lados), introduzca la diagonal. Debido a la forma en que separamos los puntos con la propiedad de buen equilibrio, ningún cuadrado con una esquina que interseca un lado es uno que esté deformado. Como tal, podemos triangular cuadrados con esquinas que se intersecan de la siguiente manera. Si hay un lado intersecado, el cuadrado se convierte en tres triángulos agregando las diagonales largas que conectan la intersección con las esquinas opuestas. Si hay cuatro lados intersecados, dividimos el cuadrado por la mitad agregando una arista entre dos de las cuatro intersecciones y luego conectamos estos dos puntos finales a los dos puntos de intersección restantes. Para los otros cuadrados, introducimos un punto en el medio y lo conectamos a las cuatro esquinas del cuadrado, así como a cada punto de intersección.
Al final, tenemos una bonita malla triangulada de nuestro conjunto de puntos construida a partir de un árbol cuaternario.
El siguiente pseudocódigo muestra una forma de implementar un árbol cuaternario que solo maneja puntos. Existen otros enfoques disponibles.
Se supone que se utilizan estas estructuras.
// Objeto de coordenadas simple para representar puntos y vectores struct XY { float x ; float y ; function __construct ( float _x , float _y ) {...} } // Cuadro delimitador alineado al eje con media dimensión y centro struct AABB { XY center ; float halfDimension ; function __construct ( XY _center , float _halfDimension ) {...} function containsPoint ( XY point ) {...} function intersectsAABB ( AABB other ) {...} }
Esta clase representa tanto un árbol cuádruple como el nodo donde está enraizado.
clase QuadTree { // Constante arbitraria para indicar cuántos elementos se pueden almacenar en este nodo de árbol cuádruple constante int QT_NODE_CAPACITY = 4 ; // Cuadro delimitador alineado con el eje almacenado como un centro con medias dimensiones // para representar los límites de este árbol cuádruple AABB bound ; // Puntos en este nodo de árbol cuádruple Matriz de puntos XY [ size = QT_NODE_CAPACITY ] ; // Hijos QuadTree * northWest ; QuadTree * northEast ; QuadTree * southWest ; QuadTree * southEast ; // Métodos function __construct ( AABB _boundary ) {...} function insert ( XY p ) {...} function subdivide () {...} // crea cuatro hijos que dividen completamente este cuadrilátero en cuatro cuadriláteros de igual área function queryRange ( AABB range ) {...} }
El siguiente método inserta un punto en el cuadrante apropiado de un árbol cuadrático, dividiéndolo si es necesario.
class QuadTree { ... // Insertar un punto en el QuadTree function insert ( XY p ) { // Ignorar objetos que no pertenecen a este árbol cuádruple if ( ! bounder . containsPoint ( p )) return false ; // no se puede agregar un objeto // Si hay espacio en este árbol cuádruple y no tiene subdivisiones, agregue el objeto aquí if ( points . size < QT_NODE_CAPACITY && northWest == null ) { points . append ( p ); return true ; } // De lo contrario, subdivida y luego agregue el punto a cualquier nodo que lo acepte if ( northWest == null ) subdivide (); // Tenemos que agregar los puntos/datos contenidos en esta matriz cuádruple a los nuevos cuádruples si solo queremos // que el último nodo contenga los datos if ( northWest -> insert ( p )) return true ; if ( northEast -> insert ( p )) return true ; if ( southWest -> insert ( p )) return true ; if ( southEast -> insert ( p )) return true ; // De lo contrario, el punto no se puede insertar por alguna razón desconocida (esto nunca debería suceder) return false ; } }
El siguiente método encuentra todos los puntos contenidos dentro de un rango.
class QuadTree { ... // Encuentra todos los puntos que aparecen dentro de un rango function queryRange ( AABB range ) { // Prepara una matriz de resultados Matriz de XY pointsInRange ; // Aborta automáticamente si el rango no interseca este cuadrilátero if ( ! bounder . intersectsAABB ( range )) return pointsInRange ; // lista vacía // Verifica los objetos en este nivel de cuadrilátero for ( int p = 0 ; p < points . size ; p ++ ) { if ( range . containsPoint ( points [ p ])) pointsInRange . append ( points [ p ]); } // Termina aquí, si no hay hijos if ( northWest == null ) return pointsInRange ; // De lo contrario, agrega los puntos de los hijos pointsInRange . appendArray ( northWest -> queryRange ( range )); pointsInRange . appendArray ( northEast -> queryRange ( range )); pointsInRange . appendArray ( suroeste -> rango de consulta ( rango )); puntosEnRango . appendArray ( sureste -> rango de consulta ( rango )); devolver puntosEnRango ; } }
Los estudios de Aluru [4] y Samet [22] [16] ofrecen una buena descripción general de los árboles cuaternarios.
{{cite book}}
: CS1 maint: varios nombres: lista de autores ( enlace )Capítulo 14: Árboles cuaternarios: págs. 291–306.