En informática , un árbol k -d (abreviatura de árbol k-dimensional ) es una estructura de datos que divide el espacio en partes para organizar puntos en un espacio k -dimensional . K-dimensional es aquel que concierne exactamente a k ejes ortogonales o a un espacio de cualquier número de dimensiones . [1] Los árboles k -d son una estructura de datos útil para varias aplicaciones, como:
Los árboles k -d son un caso especial de árboles de partición de espacio binario .
El árbol k -d es un árbol binario en el que cada nodo es un punto de dimensión k . [2] Se puede pensar que cada nodo que no es una hoja genera implícitamente un hiperplano divisor que divide el espacio en dos partes, conocidas como semiespacios . Los puntos a la izquierda de este hiperplano están representados por el subárbol izquierdo de ese nodo y los puntos a la derecha del hiperplano están representados por el subárbol derecho. La dirección del hiperplano se elige de la siguiente manera: cada nodo del árbol está asociado con una de las dimensiones k , con el hiperplano perpendicular al eje de esa dimensión. Entonces, por ejemplo, si para una división particular se elige el eje "x", todos los puntos en el subárbol con un valor "x" menor que el nodo aparecerán en el subárbol izquierdo y todos los puntos con un valor "x" mayor estarán en el subárbol derecho. En tal caso, el hiperplano estaría determinado por el valor x del punto, y su normal sería el eje x unitario. [3]
Dado que existen muchas formas posibles de elegir planos de división alineados con los ejes, existen muchas formas diferentes de construir árboles k -d. El método canónico de construcción de árboles k -d tiene las siguientes restricciones: [4]
Este método conduce a un árbol k -d equilibrado , en el que cada nodo de hoja se encuentra aproximadamente a la misma distancia de la raíz. Sin embargo, los árboles equilibrados no son necesariamente óptimos para todas las aplicaciones.
Tenga en cuenta que no es necesario seleccionar el punto medio. En el caso de que no se seleccionen puntos medios, no hay garantía de que el árbol esté equilibrado. Para evitar codificar un algoritmo complejo de búsqueda de la mediana [5] [6] o utilizar una ordenación como heapsort o mergesort para ordenar los n puntos, una práctica popular es ordenar una cantidad fija de puntos seleccionados aleatoriamente y utilizar la mediana de esos puntos para que sirva como plano de división. En la práctica, esta técnica a menudo da como resultado árboles bien equilibrados.
Dada una lista de n puntos, el siguiente algoritmo utiliza una ordenación que busca la mediana para construir un árbol k -d equilibrado que contiene esos puntos.
función kdtree ( lista de puntos pointList, int profundidad){ // Seleccione el eje en función de la profundidad para que el eje recorra todos los valores válidos var int axis := depth mod k; // Ordena la lista de puntos y elige la mediana como elemento pivote select mediana por eje from pointList; // Crea un nodo y construye un subárbol nodo.ubicación := mediana; node.leftChild := kdtree(puntos en pointList antes de la mediana, profundidad+1); node.rightChild := kdtree(puntos en pointList después de la mediana, profundidad+1); devolver nodo;}
Es común que los puntos "después" de la mediana incluyan solo aquellos que son estrictamente mayores que la mediana en la dimensión actual. Para los puntos que se encuentran en la mediana en la dimensión actual, es posible definir una función que los compare en todas las dimensiones. En algunos casos, es aceptable dejar que los puntos iguales a la mediana se encuentren en un lado de la mediana, por ejemplo, dividiendo los puntos en un subconjunto "menor que" y un subconjunto "mayor o igual que".
Este algoritmo crea la invariante de que, para cualquier nodo, todos los nodos del subárbol izquierdo están en un lado de un plano de división y todos los nodos del subárbol derecho están en el otro lado. Los puntos que se encuentran en el plano de división pueden aparecer en cualquiera de los lados. El plano de división de un nodo pasa por el punto asociado con ese nodo (al que se hace referencia en el código como node.location ).
Los algoritmos alternativos para construir un árbol k -d equilibrado preordenan los datos antes de construir el árbol. Luego, mantienen el orden de la preordenación durante la construcción del árbol y, por lo tanto, eliminan el costoso paso de encontrar la mediana en cada nivel de subdivisión. Dos de estos algoritmos construyen un árbol k -d equilibrado para ordenar triángulos con el fin de mejorar el tiempo de ejecución del trazado de rayos para gráficos de computadora tridimensionales . Estos algoritmos preordenan n triángulos antes de construir el árbol k -d y luego construyen el árbol a tiempo en el mejor de los casos. [7] [8] Un algoritmo que construye un árbol k -d equilibrado para ordenar puntos tiene una complejidad en el peor de los casos de . [9] [10] Este algoritmo preordena n puntos en cada una de las k dimensiones utilizando una ordenación como Heapsort o Mergesort antes de construir el árbol. Luego mantiene el orden de estas k preordenaciones durante la construcción del árbol y, por lo tanto, evita encontrar la mediana en cada nivel de subdivisión.
Se agrega un nuevo punto a un árbol k -d de la misma manera que se agrega un elemento a cualquier otro árbol de búsqueda . Primero, recorra el árbol, comenzando desde la raíz y moviéndose hacia el hijo izquierdo o derecho dependiendo de si el punto que se insertará está en el lado "izquierdo" o "derecho" del plano de división. Una vez que llegue al nodo bajo el cual se debe ubicar el hijo, agregue el nuevo punto como el hijo izquierdo o derecho del nodo hoja, nuevamente dependiendo de qué lado del plano de división del nodo contenga el nuevo nodo.
Agregar puntos de esta manera puede hacer que el árbol se desequilibre, lo que lleva a una disminución del rendimiento del árbol. La tasa de degradación del rendimiento del árbol depende de la distribución espacial de los puntos que se agregan y de la cantidad de puntos agregados en relación con el tamaño del árbol. Si un árbol se desequilibra demasiado, es posible que sea necesario volver a equilibrarlo para restaurar el rendimiento de las consultas que dependen del equilibrio del árbol, como la búsqueda del vecino más cercano.
Para eliminar un punto de un árbol k -d existente sin romper el invariante, la forma más fácil es formar el conjunto de todos los nodos y hojas a partir de los hijos del nodo de destino y recrear esa parte del árbol. [11]
Otro enfoque es encontrar un reemplazo para el punto eliminado. [12] Primero, encuentre el nodo que contiene el punto que se eliminará. Para el caso base, donde R es un nodo hoja, no se requiere reemplazo. Para el caso general, encuentre un punto de reemplazo, digamos , del subárbol con raíz en . Reemplace el punto almacenado en con . Luego, elimine recursivamente .
Para encontrar un punto de reemplazo, si discrimina por (por ejemplo) y tiene un hijo derecho, encuentre el punto con el valor mínimo del subárbol con raíz en el hijo derecho. De lo contrario, encuentre el punto con el valor máximo del subárbol con raíz en el hijo izquierdo.
Equilibrar un árbol k -d requiere cuidado porque los árboles k -d se ordenan en múltiples dimensiones, por lo que no se puede utilizar la técnica de rotación de árboles para equilibrarlos ya que esto puede romper el invariante.
Existen varias variantes de árboles k -d balanceados, entre ellas , el árbol k -d dividido, el pseudoárbol k -d, el árbol KDB , el árbol hB y el árbol Bkd . Muchas de estas variantes son árboles kd adaptativos .
El algoritmo de búsqueda del vecino más próximo (NN) tiene como objetivo encontrar el punto en el árbol que se encuentra más próximo a un punto de entrada determinado. Esta búsqueda se puede realizar de manera eficiente utilizando las propiedades del árbol para eliminar rápidamente grandes porciones del espacio de búsqueda.
La búsqueda de un vecino más cercano en un árbol k -d se realiza de la siguiente manera:
Generalmente, el algoritmo utiliza distancias al cuadrado para la comparación a fin de evitar el cálculo de raíces cuadradas. Además, puede ahorrar cálculos al mantener la mejor distancia actual elevada al cuadrado en una variable para la comparación.
El algoritmo se puede ampliar de varias maneras mediante modificaciones simples. Puede proporcionar los k vecinos más cercanos a un punto manteniendo k mejores valores actuales en lugar de solo uno. Una rama solo se elimina cuando se han encontrado k puntos y la rama no puede tener puntos más cercanos que cualquiera de los k mejores valores actuales.
También se puede convertir en un algoritmo de aproximación para que se ejecute más rápido. Por ejemplo, la búsqueda aproximada del vecino más cercano se puede lograr simplemente estableciendo un límite superior en la cantidad de puntos a examinar en el árbol o interrumpiendo el proceso de búsqueda en función de un reloj de tiempo real (lo que puede ser más apropiado en implementaciones de hardware). El vecino más cercano para los puntos que ya están en el árbol se puede lograr al no actualizar el refinamiento para los nodos que dan una distancia cero. Como resultado, esto tiene la desventaja de descartar puntos que no son únicos pero que están ubicados junto con el punto de búsqueda original.
El vecino más cercano aproximado es útil en aplicaciones en tiempo real como la robótica debido al aumento significativo de velocidad que se obtiene al no buscar el mejor punto de manera exhaustiva. Una de sus implementaciones es la búsqueda best-bin-first .
Una búsqueda de rango busca rangos de parámetros. Por ejemplo, si un árbol almacena valores correspondientes a ingresos y edad, una búsqueda de rango podría ser algo así como buscar todos los miembros del árbol que tengan una edad entre 20 y 50 años y un ingreso entre 50.000 y 80.000. Dado que los árboles kd dividen el rango de un dominio a la mitad en cada nivel del árbol, son útiles para realizar búsquedas de rango.
Los análisis de árboles de búsqueda binaria han descubierto que el peor tiempo posible para la búsqueda de rango en un árbol k-d de dimensión k que contiene n nodos viene dado por la siguiente ecuación. [13]
Encontrar el punto más cercano es una operación en promedio, en el caso de puntos distribuidos aleatoriamente, aunque el análisis en general es complicado. [14]
En espacios de alta dimensión, la maldición de la dimensionalidad hace que el algoritmo necesite visitar muchas más ramas que en espacios de menor dimensión. En particular, cuando el número de puntos es apenas un poco mayor que el número de dimensiones, el algoritmo es apenas un poco mejor que una búsqueda lineal de todos los puntos. Como regla general, si la dimensionalidad es k , el número de puntos en los datos, n , debería ser . De lo contrario, cuando se utilizan árboles k -d con datos de alta dimensión, se evaluarán la mayoría de los puntos en el árbol y la eficiencia no es mejor que la de una búsqueda exhaustiva [15] y, si se requiere una respuesta suficientemente rápida, se deberían utilizar en su lugar métodos aproximados de vecino más cercano.
Además, incluso en un espacio de baja dimensión, si la distancia promedio por pares entre los k vecinos más cercanos del punto de consulta es significativamente menor que la distancia promedio entre el punto de consulta y cada uno de los k vecinos más cercanos, el rendimiento de la búsqueda del vecino más cercano se degrada hacia una linealidad, ya que las distancias desde el punto de consulta hasta cada vecino más cercano son de magnitud similar. (En el peor de los casos, considere una nube de puntos distribuidos en la superficie de una esfera centrada en el origen. Cada punto es equidistante del origen, por lo que una búsqueda del vecino más cercano desde el origen tendría que iterar a través de todos los puntos en la superficie de la esfera para identificar al vecino más cercano, que en este caso ni siquiera es único).
Para mitigar la posible degradación significativa del rendimiento de una búsqueda de árbol k -d en el peor de los casos, se puede proporcionar un parámetro de distancia máxima al algoritmo de búsqueda de árbol y la búsqueda recursiva se puede podar siempre que el punto más cercano en una rama dada del árbol no pueda estar más cerca que esta distancia máxima. Esto puede provocar que una búsqueda del vecino más cercano no devuelva un vecino más cercano, lo que significa que no hay puntos dentro de esta distancia máxima desde el punto de consulta.
En lugar de puntos, un árbol k -d también puede contener rectángulos o hiperrectángulos . [16] [17] Por lo tanto, la búsqueda de rango se convierte en el problema de devolver todos los rectángulos que intersecan el rectángulo de búsqueda. El árbol se construye de la forma habitual con todos los rectángulos en las hojas. En una búsqueda de rango ortogonal , se utiliza la coordenada opuesta al comparar con la mediana. Por ejemplo, si el nivel actual se divide a lo largo de x alto , verificamos la coordenada x bajo del rectángulo de búsqueda. Si la mediana es menor que la coordenada x bajo del rectángulo de búsqueda, entonces ningún rectángulo en la rama izquierda puede intersecar nunca con el rectángulo de búsqueda y, por lo tanto, puede podarse. De lo contrario, se deben recorrer ambas ramas. Véase también árbol de intervalos , que es un caso especial unidimensional.
También es posible definir un árbol k -d con puntos almacenados únicamente en hojas. [4] Esta forma de árbol k -d permite una variedad de mecanismos de división distintos a la división mediana estándar. La regla de división de punto medio [18] selecciona en el medio del eje más largo del espacio que se está buscando, independientemente de la distribución de puntos. Esto garantiza que la relación de aspecto será como máximo 2:1, pero la profundidad depende de la distribución de puntos. Una variación, llamada punto medio deslizante, solo divide en el medio si hay puntos en ambos lados de la división. De lo contrario, divide en el punto más cercano al medio. Maneewongvatana y Mount muestran que esto ofrece un rendimiento "suficientemente bueno" en conjuntos de datos comunes.
Con el punto medio deslizante, se puede responder una consulta aproximada del vecino más cercano en . El conteo aproximado del rango se puede responder con este método.
Variaciones cercanas:
Variaciones relacionadas:
Problemas que se pueden solucionar con árboles k -d: