[C++]——Explicación detallada del árbol AVL

Tabla de contenido

preámbulo

(1) Concepto de árbol AVL

1. El origen del árbol AVL

2. Características del árbol AVL

3. Factor de equilibrio

(2) Inserción del árbol AVL

1. Comprensión de la operación de inserción. 

2. Rotación del árbol AVL

1️⃣ Rotación equilibrada LL (rotación única derecha)

2️⃣ Rotación equilibrada RR (rotación única izquierda)

3️⃣ Rotación equilibrada LR (primero doble rotación izquierda y luego derecha)

4️⃣ Rotación equilibrada RL (doble rotación derecha y luego izquierda)

3. Ejemplo de construcción

(3) Eliminación del árbol AVL (comprensión)

(4) Implementación del código

1. Definición de nodos del árbol AVL

2. Rotación única izquierda

3. Rotación única derecha

4. Doble rotación primero a la izquierda y luego a la derecha.

5. Doble rotación primero a la derecha y luego a la izquierda.

(5) Rendimiento del árbol AVL

Resumir


preámbulo

Anteriormente hicimos una breve introducción a map/multimap/set/multiset . En su documentación, descubrimos que estos contenedores tienen una cosa en
común:
todas sus capas inferiores se implementan de acuerdo con árboles de búsqueda binaria implementa mediante un árbol equilibrado .
 


(1) Concepto de árbol AVL

1. El origen del árbol AVL

Aunque el árbol de búsqueda binaria puede acortar la eficiencia de la búsqueda, si los datos están ordenados o casi ordenados, el árbol de búsqueda binaria degenerará en un árbol de una sola rama, y ​​buscar
elementos equivale a buscar elementos en una tabla de secuencia
, que es ineficiente;

Por lo tanto, dos matemáticos rusos GMAdelson-Velskii y EM Landis inventaron un método para resolver el problema anterior en 1962:

  • Después de insertar un nuevo nodo en el árbol de búsqueda binaria, si se puede garantizar que el valor absoluto de la diferencia entre las alturas de los subárboles izquierdo y derecho de cada nodo no exceda 1 (los nodos en el árbol deben ajustarse) , se puede reducir la altura del árbol, reduciendo así la longitud media de búsqueda
     

2. Características del árbol AVL

Un árbol AVL es un árbol vacío o un árbol de búsqueda binario con las siguientes propiedades:

  1. Sus subárboles izquierdo y derecho son árboles AVL.
  2. El valor absoluto de la diferencia entre las alturas de los subárboles izquierdo y derecho (denominado factor de equilibrio) no supera 1 (-1/0/1)
     

 El árbol binario balanceado que se muestra en la figura siguiente, el de la derecha es un árbol binario no balanceado:

 Si un árbol de búsqueda binario está muy equilibrado, es un árbol AVL. Si tiene n nodos, su altura se puede mantener en
O (log_2 n) y la complejidad del tiempo de búsqueda es O (log_2 n).


3. Factor de equilibrio

El factor de equilibrio es un indicador que se utiliza para medir el grado de equilibrio entre el subárbol izquierdo y el subárbol derecho de un nodo de árbol binario. Se define como la altura del subárbol izquierdo menos la altura del subárbol derecho (o viceversa), es decir:

  • Factor de equilibrio = altura del subárbol derecho - altura del subárbol izquierdo ( también es posible lo contrario )

El valor del factor de equilibrio puede ser positivo, cero o negativo , y los significados específicos son los siguientes:

  1. Si el factor de equilibrio es positivo , significa que el subárbol izquierdo es más alto que el subárbol derecho.
  2. Si el factor de equilibrio es cero , significa que los subárboles izquierdo y derecho tienen la misma altura.
  3. Si el factor de equilibrio es negativo , significa que el subárbol derecho es más alto que el subárbol izquierdo.

Para los árboles AVL , el factor de equilibrio debe permanecer dentro del rango de -1, 0 y 1 en cada nodo . Si el factor de equilibrio excede este rango, significa que el árbol ya no está equilibrado y es necesario rotarlo para restablecer el equilibrio.

Por ejemplo, el siguiente árbol representa un árbol AVL y el valor fuera del nodo representa el factor de equilibrio del nodo:

Por lo tanto, en resumen, podemos saber que la introducción del factor de equilibrio es para garantizar que la altura del árbol se mantenga dentro de un rango pequeño, mejorando así el rendimiento de las operaciones de consulta, inserción y eliminación del árbol. Al mantener el factor de equilibrio dentro de un rango específico, puede garantizar que la estructura del árbol esté relativamente equilibrada y evitar desequilibrios extremos que empeorarían la complejidad temporal de la operación.


(2) Inserción del árbol AVL

La idea básica del árbol de clasificación binaria para garantizar el equilibrio es la siguiente:

  1. Siempre que se inserta (o elimina) un nodo en un árbol ordenado binario, primero verifique si los nodos en su ruta de inserción están desequilibrados debido a esta operación;
  2. Si se produce un desequilibrio, primero busque el nodo A cuyo valor absoluto del factor de equilibrio más cercano al nodo de inserción sea mayor que 1 en la ruta de inserción, y luego busque el subárbol con A como raíz, manteniendo las características de la clasificación binaria. árbol, ajuste la relación posicional de cada nodo para recuperar el equilibrio.

Nota : El objeto de cada ajuste es el subárbol mínimo desequilibrado , es decir, el subárbol con el nodo cuyo valor absoluto del factor de equilibrio más cercano al nodo de inserción en la ruta de inserción es mayor que 1 como raíz.

1. Comprensión de la operación de inserción. 

 El árbol AVL introduce un factor de equilibrio basado en el árbol de búsqueda binario, por lo que el árbol AVL también puede considerarse como un árbol de búsqueda binario. Entonces,
el proceso de inserción del árbol AVL se puede dividir en dos pasos:

  • 1. Insertar nuevos nodos según el árbol de búsqueda binario.
  • 2. Ajustar el factor de equilibrio del nodo.

 La idea general es la siguiente:

1. Primero inserte el nodo en el árbol AVL de acuerdo con las reglas del árbol de búsqueda binaria.
2. Después de insertar el nuevo nodo, el equilibrio del árbol AVL puede destruirse. En este momento, debe actualizar el factor de equilibrio. y detectar si el
árbol AVL está destruido.


Después de insertar pCur, se debe ajustar el factor de equilibrio de pParent. Antes de la inserción, el factor de equilibrio de pParent se divide en tres situaciones: -1, 0, 1 y las dos situaciones siguientes:

  • 1. Si pCur se inserta a la izquierda de pParent, simplemente proporcione el factor de equilibrio de pParent -1
  • 2. Si pCur se inserta a la derecha de pParent, simplemente agregue 1 al factor de saldo de pParent
     

En este momento: el factor de equilibrio de pParent puede tener tres situaciones: 0, más o menos 1, más o menos 2

  • 1. Si el factor de equilibrio de pParent es 0, significa que el factor de equilibrio de pParent antes de la inserción era más o menos 1 y se ajusta a 0 después de la inserción. En este momento, las propiedades del árbol AVL se satisfacen y la inserción es exitoso.
  • 2. Si el factor de equilibrio de pParent es más o menos 1, significa que el factor de equilibrio de pParent debe ser 0 antes de la inserción y se actualiza a más o menos 1 después de la inserción. En este momento, la altura del árbol con pParent A medida que aumenta la raíz, es necesario continuar actualizándose hacia arriba.
  • 3. Si el factor de equilibrio de pParent es más o menos 2, entonces el factor de equilibrio de pParent viola las propiedades del árbol equilibrado y debe rotarse.

2. Rotación del árbol AVL

Por lo tanto, si la operación anterior inserta un nuevo nodo en un árbol AVL originalmente equilibrado, puede causar un desequilibrio. En este momento, la estructura del árbol debe ajustarse para equilibrarlo. Dependiendo de la posición de inserción del nodo, la rotación del árbol AVL se divide en cuatro tipos:
 

1️⃣ Rotación equilibrada LL (rotación única derecha)

El nuevo nodo se inserta a la izquierda del subárbol superior izquierdo --- izquierda-izquierda: rotación única hacia la derecha
 

【ilustrar】 

  1.  En la figura anterior, antes de la inserción, el árbol AVL está equilibrado. El nuevo nodo se inserta en el subárbol izquierdo de 30 (nota: este no es el hijo izquierdo). El subárbol izquierdo de 30 agrega una capa, lo que hace que el árbol binario arraigado en 60 para estar desequilibrado. ;
  2. En este momento, para equilibrar 60, solo podemos reducir la altura del subárbol izquierdo de 60 en una capa y aumentar la altura del subárbol derecho en una capa, es decir, levantar el subárbol izquierdo hacia arriba;
  3. De esta manera, se rechaza 60. Debido a que 60 es mayor que 30, solo se puede colocar en el subárbol derecho de 30. Si 30 tiene un subárbol derecho, el valor de la raíz del subárbol derecho debe ser mayor que 30 y menor que 60, por lo que solo se puede colocar en el subárbol derecho de 30. Para el subárbol izquierdo de 60, una vez completada la rotación, simplemente actualice el factor de equilibrio del nodo.
     

Durante el proceso de rotación, se deben considerar las siguientes situaciones:

1. Puede que exista o no el hijo derecho del nodo 30.
2. 60 puede ser el nodo raíz o un subárbol.

  • Si es el nodo raíz, una vez completada la rotación, es necesario actualizar el nodo raíz.
  • Si es un subárbol, puede ser el subárbol izquierdo o el subárbol derecho de un nodo.
     

2️⃣ Rotación equilibrada RR (rotación única izquierda)

El nuevo nodo se inserta a la derecha del subárbol superior derecho --- derecha-derecha: rotación única hacia la izquierda
 

 【ilustrar】 

  1. En la figura anterior, antes de la inserción, el árbol AVL está equilibrado. El nuevo nodo se inserta en el subárbol derecho de 60 (nota: este no es el hijo correcto). El subárbol derecho de 60 agrega una capa, lo que genera el árbol binario. arraigado en 30 para estar desequilibrado. ;
  2. En este momento, para equilibrar 30, solo podemos reducir la altura del subárbol derecho de 30 en un nivel y aumentar la altura del subárbol izquierdo en un nivel, es decir, elevar el subárbol derecho hacia arriba;
  3. De esta manera, se rechaza 30. Debido a que 30 es menor que 60, solo se puede colocar en el subárbol izquierdo de 60. Si 60 tiene un subárbol izquierdo, el valor de la raíz del subárbol izquierdo debe ser mayor que 30 y menor que 60, por lo que solo se puede colocar en el subárbol izquierdo de 60. El subárbol derecho de 30, una vez completada la rotación, actualiza el factor de equilibrio del nodo.

3️⃣ Rotación equilibrada LR (primero doble rotación izquierda y luego derecha)

El nuevo nodo se inserta a la derecha del subárbol superior izquierdo --- izquierda y derecha: primero izquierda y luego derecha.

  【ilustrar】 

  1. Dado que se inserta un nuevo nodo en el subárbol derecho (R) del hijo izquierdo (L) de 90, el factor de equilibrio de 90 cambia de -1 a -2, lo que hace que el subárbol con raíz en 90 se desequilibre;
  2. Se requieren dos operaciones de rotación, primero hacia la izquierda y luego hacia la derecha. Primero, gire el nodo raíz 60 del subárbol derecho del hijo izquierdo 30 del nodo 90 hacia arriba a la izquierda hasta la posición del nodo 30, y luego gire el nodo 60 hacia arriba hacia la derecha hasta la posición del nodo 90.

4️⃣ Rotación equilibrada RL (doble rotación derecha y luego izquierda)

El nuevo nodo se inserta a la izquierda del subárbol superior derecho --- derecha izquierda: primero gire a la derecha y luego gire a la izquierda
 

 

   【ilustrar】 

  1. En la figura anterior, antes de la inserción, el árbol AVL está equilibrado. El nuevo nodo se inserta en el subárbol derecho de 60 (nota: este no es el hijo correcto). El subárbol derecho de 60 agrega una capa, lo que genera el árbol binario. arraigado en 30 para estar desequilibrado. ; 
  2. Dado que se inserta un nuevo nodo en el subárbol izquierdo (L) del hijo derecho (R) de 30, el factor de equilibrio de 30 cambia de 1 a 2, lo que hace que el subárbol con raíz en 30 se desequilibre;
  3. Se requieren dos operaciones de rotación, primero hacia la derecha y luego hacia la izquierda. Primero, gire el nodo raíz 60 del subárbol izquierdo del hijo derecho 90 del nodo 30 hacia arriba a la derecha hasta la posición del nodo 90, y luego gire el nodo 60 hacia arriba hacia la izquierda hasta la posición del nodo 30.
     

[Nota] : Cuando se rotan LR y RL, si el nuevo nodo se inserta en el subárbol izquierdo o en el subárbol derecho de 60 no afecta el proceso de rotación.


3. Ejemplo de construcción

Tomemos como ejemplo el proceso de construcción de un árbol binario equilibrado con la secuencia de palabras clave (15,3,7,10,9,8) :

 【ilustrar】

  • Después de insertar 7, se produce un desequilibrio. La raíz del subárbol mínimo desequilibrado es 15. La posición de inserción es el subárbol derecho de su hijo izquierdo. Por lo tanto, se realiza la rotación LR, primero hacia la izquierda y luego hacia la derecha, y el resultado ajustado es como se muestra en la figura;
  • Cuando se inserta 9, se produce un desequilibrio. La raíz del subárbol mínimo desequilibrado es 15. La posición de inserción es el subárbol izquierdo de su hijo izquierdo, por lo que se realizan la rotación LL y la rotación única derecha;
  • Insertar 8 conduce al desequilibrio. La raíz del subárbol desequilibrado más pequeño es 7 y la posición de inserción es el subárbol izquierdo de su hijo derecho. Por lo tanto, se realiza la rotación RL, primero a la derecha y luego a la izquierda.


(3) Eliminación del árbol AVL (comprensión)

De manera similar a la operación de inserción de un árbol binario balanceado, tome la eliminación del nodo w como ejemplo para ilustrar los pasos de una operación de eliminación de un árbol binario balanceado:

1) Utilice el método del árbol de clasificación binaria para realizar la operación de eliminación en el nodo w.
2) Comenzando desde el nodo w, rastree hacia arriba para encontrar el primer nodo desequilibrado z (es decir, el subárbol desequilibrado más pequeño); y es el nodo secundario con la altura más alta del nodo z: x es la altura del nodo y El nodo secundario más alto.
3) Luego equilibre el subárbol con z como raíz, donde hay 4 posiciones posibles de x, y y z:

  •  y es el hijo izquierdo de z y x es el hijo izquierdo de y (LL, rotación única derecha);
  •  y es el hijo izquierdo de z, x es el hijo derecho de y (LR, primero a la izquierda y luego a la derecha, doble rotación);
  •  y es el hijo derecho de z y x es el hijo derecho de y (RR, rotación única a la izquierda);
  •  y es el hijo derecho de z y x es el hijo izquierdo de y (RL, doble rotación de derecha a izquierda).

Estas cuatro situaciones se ajustan de la misma manera que las operaciones de inserción. La diferencia es que la operación de inserción solo requiere ajustes de equilibrio en el subárbol con raíz en z; la operación de eliminación es diferente. El subárbol con raíz en z se equilibra primero. Si la altura del subárbol se reduce en 1 después del ajuste, entonces puede Será necesario realizar ajustes de equilibrio en el nodo ancestro de z, o incluso retroceder hasta el nodo raíz (lo que dará como resultado que la altura del árbol se reduzca en 1).


Tome como ejemplo la eliminación del nodo 32 en la siguiente figura:
 

 【ilustrar】

  1. Dado que el nodo hoja de 32 bits se puede eliminar directamente, rastrea hacia arriba para encontrar el primer nodo desequilibrado 44 (es decir, z);
  2. El nodo secundario con la altura más alta de z es 78 (es decir, y), y el nodo secundario con la altura más alta de y es 50 (es decir, x). Para satisfacer la situación RL, gire hacia la derecha y luego hacia la izquierda, y el resultado ajustado es como se muestra en la figura anterior.

(4) Implementación del código

1. Definición de nodos del árbol AVL

template<class K,class V>
class AVLTreeNode
{
	AVLTreeNode<K, V>* _left;	// 该节点的左孩子
	AVLTreeNode<K, V>* _right;	// 该节点的右孩子
	AVLTreeNode<K, V>* _parent; // 该节点的双亲
	pair<K, V> _kv;
	int _bf;		//平衡因子

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}
};

2. Rotación única izquierda

El código se muestra a continuación:

//左旋转
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		Node* ppnode = parent->_parent;

		subR->_left = parent;
		parent->_parnet = subR;

		if (ppnode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}

		// 根据调整后的结构更新部分节点的平衡因子
		parent->_bf = subR->_bf = 0;
	}

3. Rotación única derecha

El código se muestra a continuación:

//右旋转
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if(subLR)
			subLR->_parent = parent;

		Node* ppnode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (ppnode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
		subL->_bf = parent->_bf = 0;
	}

4. Doble rotación primero a la izquierda y luego a la derecha.

Si lo piensas con base en la imagen de abajo y la imagen de arriba cuando hablas de LR, creo que no es difícil de resolver:

 El código se muestra a continuación:

//LR操作
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);

		if (bf == 1)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

5. Doble rotación primero a la derecha y luego a la izquierda.

Lo mismo que arriba:

    void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(parent);

		if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
			subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			parent->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == 0)
		{
			subR->_bf = 0;
			parent->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

(5) Rendimiento del árbol AVL

El árbol AVL es un árbol de búsqueda binario absolutamente equilibrado, que requiere que el valor absoluto de la diferencia de altura entre los subárboles izquierdo y derecho de cada nodo no exceda 1.
Esto durante la consulta, es decir, $ log_2 (N ) $. Sin embargo, si desea realizar algunas modificaciones estructurales en el árbol AVL
, el rendimiento es muy bajo. Por ejemplo: al insertar, debe mantener su equilibrio absoluto y el número de rotaciones es relativamente alto. Lo peor es que al eliminar , es posible que tengas que mantener la
rotación hasta la ubicación de la raíz. Por lo tanto: si necesita una estructura de datos con consulta y orden eficientes, y
la cantidad de datos es estática (es decir, no cambiará), puede considerar el árbol AVL, pero si una estructura se modifica con frecuencia, no es adecuada. .
 


Enlace de código: Implementación de AVL


Resumir

Lo anterior es una introducción detallada a los árboles de este número de AVL. ¡Gracias a todos por mirar y apoyar! ! !

 

Supongo que te gusta

Origin blog.csdn.net/m0_56069910/article/details/132350934
Recomendado
Clasificación