Por qué los árboles rojo-negro son tan populares

Hay muchos árboles de búsqueda binarios equilibrados, pero cuando mencionamos árboles de búsqueda binarios equilibrados, a menudo se mencionan árboles rojo-negro, y su "tasa de aparición" es incluso mayor que la de los árboles de búsqueda binarios equilibrados.

El árbol rojo-negro es un árbol de búsqueda binario relativamente equilibrado, que no cumple con la definición estricta de un árbol de búsqueda binario equilibrado.

Tabla de contenido

Inserción de árbol rojo-negro

 Verificación del árbol rojo-negro


Propiedades de los árboles rojo-negro.

  • Cada nodo es rojo o negro.
  • el nodo raíz es negro 
  • Cualquier nodo adyacente arriba y abajo no puede ser rojo al mismo tiempo. Si un nodo es rojo, sus dos nodos secundarios son negros. 
  •  Para cada nodo, todas las rutas desde el nodo hasta todos sus nodos de hoja descendientes contienen la misma cantidad de nodos negros. 
  • Cada nodo hoja es un nodo negro vacío, es decir, el nodo hoja no almacena datos.

Cumpliendo las propiedades anteriores, el árbol rojo-negro puede garantizar que el número de nodos en el camino más largo no exceda el doble del número de nodos en el camino más corto.

Análisis de rendimiento del árbol rojo-negro.

Suponiendo que el número de nodos negros en cada ruta es N, la ruta más corta son todos los nodos negros, y el nodo de la ruta más larga es un nodo negro y un nodo rojo alternativamente, N<=longitud de ruta arbitraria<=2N.

El árbol de búsqueda binario equilibrado se propone para resolver el problema de degradación del rendimiento del árbol de búsqueda binario debido a las actualizaciones dinámicas. Por lo tanto, "equilibrado" puede ser equivalente a ninguna degradación del rendimiento y "aproximadamente equilibrado" puede ser equivalente a una degradación del rendimiento menos grave. La complejidad temporal de muchas operaciones en un árbol de búsqueda binaria es proporcional a la altura del árbol. La altura de un árbol binario extremadamente balanceado (árbol binario completo o árbol binario completo) es aproximadamente log n. Si desea probar que el árbol rojo-negro está aproximadamente balanceado, solo necesita probar que la altura del árbol rojo-negro árbol es aproximadamente log n (como la misma magnitud).

probar:

 El camino más largo del árbol rojo-negro no excederá 2log n, es decir, la altura del árbol rojo-negro no excederá 2log n. Un árbol rojo-negro es solo el doble de alto que un árbol AVL de altura equilibrada, por lo que la pérdida de rendimiento no es mucha. En comparación con el árbol AVL, el costo de mantenimiento del árbol rojo-negro es más bajo, por lo que el rendimiento no es peor que el del árbol AVL.

Entonces, ¿por qué los árboles rojo-negro son tan populares? 

El árbol AVL es un árbol binario muy equilibrado, que es muy eficiente para encontrar datos. Sin embargo, para mantener este equilibrio alto, el árbol AVL necesita pagar más. Para mantener el equilibrio, la distribución de los nodos en el árbol debe ajustarse cada vez que se insertan o eliminan datos, lo cual es complejo y requiere mucho tiempo.
El árbol rojo-negro está solo aproximadamente equilibrado, pero no estrictamente definido. Por lo tanto, el costo de mantener el equilibrio es más bajo que el del árbol AVL, pero la pérdida de rendimiento no es grande. Para aplicaciones de ingeniería, preferimos árboles rojo-negros con costos de mantenimiento y rendimiento relativamente comprometidos. Más importante aún, la mayoría de los lenguajes de programación brindan clases que encapsulan la implementación del árbol rojo-negro, que podemos usar directamente sin comenzar desde cero, lo que ahorra mucho tiempo de desarrollo. Un árbol rojo-negro es un árbol de búsqueda binario aproximadamente equilibrado. Fue creado para resolver el problema de degradación del rendimiento causado por la actualización dinámica de datos del árbol de búsqueda binario. La altura del árbol rojo-negro es aproximadamente logn, y la complejidad temporal de las operaciones de inserción, eliminación y búsqueda es O(logn).
 

Definición de nodo de árbol rojo-negro 

   // color de nodo
    enum Color { ROJO, NEGRO };


    // Plantilla de definición de nodo de árbol rojo-negro
    <clase T>
    struct RBTreeNode
    {         RBTreeNode(const T& data = T(), Color color = RED)             : _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)             , _data(data ), _color(color)         {}         RBTreeNode<T>* _pLeft; // el hijo izquierdo del nodo         RBTreeNode<T>* _pRight; // el hijo derecho del nodo         RBTreeNode<T>* _pParent; // el padre de el nodo         T_data; // El campo de valor del nodo         Color _color; // el color del nodo     };









Al insertar un nodo, le damos el color predeterminado rojo al nodo, porque los nodos negros en cada ruta son los mismos, si el nodo se le da negro, afectará a todas las rutas, y el nuevo nodo se le da rojo si no lo hace. cumple con las reglas, solo necesita ajustar esta ruta o la adyacente, y el costo de darle rojo al nuevo nodo es mucho menor que darle negro al nuevo nodo.

Inserción de árbol rojo-negro

El árbol rojo-negro se basa en el árbol de búsqueda binaria con sus restricciones de equilibrio, por lo que la inserción del árbol rojo-negro se puede dividir en dos pasos:

1. Inserte nuevos nodos de acuerdo con las reglas del árbol de búsqueda binaria

2. Después de detectar la inserción de un nuevo nodo, si la naturaleza del árbol rojo-negro está dañada

Debido a que el color predeterminado de un nuevo nodo es rojo, si el color de su nodo principal es negro, no viola ninguna propiedad del árbol rojo-negro y no se requiere ningún ajuste; pero cuando el color del nodo principal del el nodo recién insertado es rojo, viola las propiedades del árbol rojo-negro, no puede haber nodos rojos conectados entre sí. En este momento, es necesario discutir el árbol rojo-negro de acuerdo con la situación:

cur es el nodo actual, p es el nodo padre, g es el nodo abuelo, u es el nodo tío

El nodo cur no es necesariamente un nodo recién agregado, puede ser un nodo apuntado después del ajuste.

Caso 1: cur es rojo, p es rojo, g es negro, u existe y es rojo

Solución: cambie p, u a negro, g a rojo, luego tome g como actual y continúe ajustando hacia arriba.

Caso 2: cur es rojo, p es rojo, g es negro, u no existe/u existe y es negro 

Hay dos casos de u
: 1. Si el nodo u no existe, entonces cur debe ser un nodo recién insertado, porque si cur no es un nodo recién insertado, entonces cur y p deben tener un nodo de color negro, que no satisface la propiedad: cada El número de nodos negros en el camino es el mismo.
2. Si existe el nodo u, debe ser negro. Entonces, el color original del nodo cur debe ser negro. La razón por la que ahora es rojo es porque el subárbol cur cambia el color del nodo cur de negro a rojo.

Solución: si p es el hijo izquierdo de g, y cur es el hijo izquierdo de p, realice una sola rotación a la derecha; por el contrario, si p es el hijo derecho de g, y cur es el hijo derecho de p, entonces realice una sola rotación a la izquierda p y g cambian de color: p se vuelve negra, g se vuelve roja 

Caso 3: cur es rojo, p es rojo, g es negro, u no existe/u existe y es negro

p es el hijo izquierdo de g, cur es el hijo derecho de p, luego haga una sola rotación a la izquierda para p; por el contrario, si p es el hijo derecho de g, cur es el hijo izquierdo de p, entonces haga una rotación a la derecha rotación única para p, luego convertida al Caso 2

Código:

bool Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (data > cur->_data)
			{
				parent = cur;
				cur = cur->_right
			}
			else if (data < >> cur->_data)
			{
				parent = cur;
				cur = cur->_left
			}
			else 
			{
				return false;
			}
		}
		//插入节点
		cur = new Node(data);
		cur->_col(RED);
		if (parent->data < data)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//控制平衡
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = parent->_right;
				//1.uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					//变色+继续向上处理
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else//2.uncle不存在/uncle存在且为黑
				{
					//        g
					//     p
					//  c

					//        g
					//     p
					//        c
					if (cur == parent->_left)
					{
						//右旋
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;

					}
					if (cur == parent->_right)
					{
						//先左旋再右旋
						RotateL(parent);
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;

					}
					break;
				}
			}
			else // parent == grandfather->_right
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)
				{
					// 变色+继续向上处理
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else // 2 + 3、uncle不存在/ 存在且为黑
				{
					//  g    
					//     p
					//        c

					//  g
					//     p
					//  c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}

 Verificación del árbol rojo-negro

La detección de árboles rojo-negro se divide en dos pasos:

1. Compruebe si cumple con el árbol de búsqueda binaria (si el recorrido en orden es una secuencia ordenada)

2. Compruebe si cumple con las propiedades del árbol rojo-negro

bool IsBalance()
	{
		if (_root && _root->_col == RED)
		{
			cout << "根节点不是黑色" << endl;
			return false;
		}
		
		// 最左路径黑色节点数量做基准值
		int banchmark = 0;
		Node* left = _root;
		while (left)
		{
			if (left->_col == BLACK)
				++banchmark;
			
			left = left->_left;
		}

		int blackNum = 0;
		return _IsBalance(_root, banchmark, blackNum);
	}

	bool _IsBalance(Node* root, int banchmark, int blackNum)
	{
		if (root == nullptr)
		{
			if (banchmark != blackNum)
			{
				cout << "存在路径黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "出现连续红色节点" << endl;
			return false;
		}

		if (root->_col == BLACK)
		{
			++blackNum;
		}

		return _IsBalance(root->_left, banchmark, blackNum)
			&& _IsBalance(root->_right, banchmark, blackNum);
	}

Supongo que te gusta

Origin blog.csdn.net/m0_55752775/article/details/129412112
Recomendado
Clasificación