1. Problema de clasificación multinivel
En el proceso de desarrollo real, a menudo se encontrará el problema de la clasificación de niveles múltiples. Por ejemplo, problemas de clasificación de varios niveles, como la barra de navegación, el menú, la categoría de producto, el enlace de varios niveles, la tabla de diccionario, etc. Luego, puede agregar pid
datos relacionados con un campo, que en esencia es en realidad un árbol. El árbol puede resolver muy bien la consulta de la subcategoría de clasificación multinivel.
Pero este método tiene un problema fatal: ¡la eficiencia de la consulta es demasiado baja! ! !
Cuando consultamos un nodo hijo en el programa, primero debemos realizar una consulta recursiva desde el nodo raíz. La complejidad del tiempo es O(n)
.
Entonces, ¿hay alguna manera de mejorar la eficiencia de las consultas del árbol? ¡La respuesta es sí! Se han mejorado muchos árboles en árboles estándar, como árboles binarios, árboles rojo-negro, montones, etc. Pero este no es el punto, lo que quiero compartir hoy es 预排序遍历树算法(MPTT)
.
MPTT
Es precisamente para resolver el problema de la eficiencia de las consultas de los datos relacionales multinivel, es decir, su complejidad temporal puede ser tan eficiente como una constante O(1)
. ¿No es increíble? Aprendamos juntos el algoritmo del árbol transversal de clasificación previa y veamos cómo se implementa.
Segundo, árbol transversal de clasificación previa
El nombre completo del algoritmo de árbol transversal de clasificación previa es: Modified Preorder Tree Traversal
abreviatura MPTT
.
1. Mapeo ORM
class Tree(Base, BaseNestedSets):
__tablename__ = 'tree'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(8), nullable=True, default=None)
def __repr__(self):
return f'<Tree(id={self.id}, name={self.name})>'
2. Análisis MPTT
En el código anterior, solo se definen dos campos: id
, name
. Pero había una base de datos adicional fuera de los 5
campos, lft
a saber: rgt
, level
, tree_id
, parent_id
,.
Estos campos adicionales se utilizan para definir la estructura y jerarquía del árbol. Analicemos cuál es el papel de cada campo.
-
tree_id
: Árbolid
, utilizado para distinguir cierto árbol entre los muchos árboles de la base de datos. -
level
: Un árbol estándar tendrá altura, profundidad y nivel. El nivel del nodo raíz es1
, y el nivel de los nodos secundarios es el nivel del nodo principal más1
. -
parent_id
: Padreid
, el padreid
del nodo, el nodo raíz no tiene padre, por lo que el valor esNULL
. -
lft
: El valor de la izquierda del nodo. -
rgt
: El valor correcto del nodo.
El nodo de valores izquierdo y derecho es MPTT
el núcleo de este algoritmo, es un lugar particularmente inteligente, la complejidad se reduce al tiempo de recorrido del árbol O(1)
. A continuación, nos centraremos en analizar cómo los valores izquierdo y derecho atraviesan el árbol.
Una estructura de árbol estándar:
Correspondencia de los datos de la base de datos:
Jerarquía de datos:
- 【1】
- - 【2】
- - - 【3】
- - 【4】
- - - 【5】
- - - 【6】
- - 【7】
- - - 【8】
- - - - 【9】
- - - 【10】
- - - - 【11】
Atraviesa todo el árbol
Atravesar todo el árbol solo necesita encontrar las tree_id
mismas 1
condiciones para
Encuentra todos los descendientes de un nodo
Encuentre el nodo a 4
todos los descendientes de nodos 4
como punto de referencia. El valor es mayor que el de la izquierda 6
y el valor de la derecha es menor que 11
todos los nodos descendientes, el nodo son 4
todos los nodos descendientes.
Encuentra todos los nodos secundarios debajo de un nodo
Encuentre el nodo 1
de todos los nodos secundarios 1
como punto de referencia. tree_id
Igual 1
e level
igual 2
.
Encuentra la ruta de un nodo
Encuentre el nodo 9
todos los caminos más altos 9
como punto de referencia. El valor es menor que el izquierdo 14
y el derecho es mayor que el valor 15
de todos los nodos, el nodo es ``. 9. 的路径。结果是:
1 -> 7 -> 8 -> 9 '.
3. Algoritmo de equilibrio MPTT
MPTT
Al atravesar rápidamente, pero otras operaciones se volverán muy lentas, por lo que MPTT
debe intentar evitar otras operaciones fuera de la consulta.
Entonces, ¿por qué otras operaciones son muy lentas, excepto las operaciones de consulta?
Esto se debe a que la inserción, actualización (movimiento) y eliminación de nodos alterará el equilibrio del árbol. Entonces, al realizar estas operaciones, debe ajustar el número para lograr un nuevo equilibrio.
Agregar
Tomando la operación del nuevo nodo como ejemplo, el algoritmo se puede dividir en los siguientes pasos:
-
Si desea agregar un nuevo nodo a un árbol que no existe, debe crear un nuevo árbol. Entonces no es
parent_id
,parent_id
esNULL
,level
es1
,tree_id
es el más grande de acuerdo con el árboltree_id
más existente1
. -
Si desea agregar un nuevo nodo al árbol existente. Por lo tanto,
parent_id
es un nodo padreid
,level
un nodo padrelevel
, más1
,tree_id
y el padre consistente. -
Repara el valor izquierdo de otros nodos cuyo equilibrio está roto. Es mayor que
parent_id
los valores izquierdo y derecho de todos los nodos agregados al valor2
. -
Repare el valor correcto de otros nodos cuyo equilibrio está roto. Que o igual a
parent_id
la derecha de todos los nodos del valor correcto más valor2
.
Eliminar
Es similar a aumentar, excepto que después de eliminar un nodo, el valor de la izquierda y el valor de la derecha se invierten, es decir, resta 2
.
Actualización (móvil)
Actualizar (mover) es en realidad eliminar un nodo antiguo y agregar uno nuevo. Consulte el ejemplo anterior para conocer el algoritmo específico.
3. Comparación de los pros y los contras de los árboles estándar y los árboles transversales preclasificados
-
标准树
: Adecuado para escenas con muchas adiciones y eliminaciones, y solo es necesario modificar un dato cada vez. En términos de consulta, a medida que aumenta el nivel de clasificación, la eficiencia de la consulta recursiva de la tabla de adyacencia disminuye gradualmente. -
预排序遍历树
: Aplicable a escenarios con muchas operaciones de consulta. La eficiencia de la consulta no se ve afectada por el aumento del nivel de clasificación. Sin embargo, con el aumento de datos, cada vez que se agregan o eliminan datos, se deben operar varios datos afectados al mismo tiempo, y la eficiencia de ejecución disminuye gradualmente.
No existe un algoritmo perfecto. La estructura de almacenamiento y el algoritmo a elegir en el proceso de desarrollo real deben seleccionarse de acuerdo con el escenario de aplicación específico.