El principio y la implementación del árbol rojo-negro de C ++

En la sección anterior, estudiamos el árbol AVL. El árbol AVL es un árbol que es casi perfecto y está cerca del equilibrio. Sin embargo, el empaquetado subyacente de map y set usa árboles rojo-negro. Se puede ver que los árboles rojo-negro Tienen ventajas más destacadas que los árboles AVL. Ahora echemos un vistazo al principio subyacente del árbol rojo-negro y la implementación del código específico.

 

El concepto de árbol rojo-negro:

El árbol rojo-negro es un árbol de búsqueda binario , pero se agrega un bit de almacenamiento a cada nodo para indicar el color del nodo, que puede ser rojo o
Negro . Al restringir el color de cada nodo en cualquier camino desde la raíz hasta la hoja, el árbol rojo-negro asegura que ningún camino
El camino será el doble de largo que los otros caminos , por lo que está cerca del equilibrio .

 Propiedades de los árboles rojo-negros:

1. El número de nodos negros desde cualquier nodo hasta el nodo hoja de cualquier ruta es el mismo .

2. No hay nodos rojos adyacentes en todo el árbol. Un nodo es rojo, entonces sus nodos secundarios adyacentes deben ser negros.

3. El nodo raíz es negro.

4. Cada nodo de hoja es negro. (Los nodos hoja aquí se refieren a nodos vacíos)

La definición de un nodo de árbol rojo-negro:

enum Colour
{
	RED,
	BLACK,
};

template<class K, class V>
struct RBTreeNode
{
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)
	{}
};

En el constructor, ¿por qué el color está configurado en rojo de forma predeterminada?

Debido a que el rojo es una práctica segura, si el nodo insertado es negro, debe violar la propiedad 1. Es diferente insertar un nodo rojo: si el nodo principal del nodo insertado es negro, la inserción no violará las propiedades anteriores. Entonces esto es una cuestión de probabilidad: obviamente, la probabilidad de tener problemas al insertar nodos negros es mucho mayor que al insertar nodos rojos.

Inserción en un árbol rojo-negro:

Las condiciones de inserción del árbol rojo-negro son las mismas que las del árbol AVL y mantiene las características del árbol AVL original:

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	pair<iterator,bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = Black;
			return make_pair(iterator(_root), true);  //make_pair方式返回
		}

		Node* parent = nullptr;
		Node* cur = _root;
		KeyofT kot;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) >kot( data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}
		
		cur = new Node(data);
		Node* newnode = cur;

		cur->_col = Red;
		
		if (kot(parent->_data) > kot(cur->_data))
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
private:
     Node* _root=nullptr;
}

Después de insertar los datos, debido a que estamos insertando nodos rojos, necesitamos equilibrar el árbol binario. Si el padre del nodo rojo es un nodo negro, entonces no se requiere equilibrio si no se viola la condición. Si el nodo principal es un nodo rojo, entonces viola la regla de tener nodos rojos continuos, por lo que debe equilibrarse:

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

 Si g es el nodo raíz, g debe ser rojo después de completar el ajuste, pero la naturaleza del nodo raíz es que el nodo raíz es negro, así que cambie g a negro.

 Si g no es el nodo raíz, el color del nodo de g en este momento es rojo. Cuando los padres de g también son rojos, es necesario continuar ajustándose hacia arriba.

Solución: cambie p, u a negro, g a rojo, luego tome g como cur 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

 En el segundo caso, debemos discutir según la situación: u existe o u no existe. Pero si existe, debe ser un nodo negro.

Método de procesamiento: tome p como eje para realizar una rotación única hacia la derecha, después de la rotación única hacia la derecha, el nodo p se vuelve negro y el nodo g se vuelve rojo. El diagrama de árbol binario balanceado es el siguiente:

 Por el contrario, si p está a la derecha, u está a la izquierda y cur está en el lado derecho de p, entonces se realiza una rotación única hacia la izquierda con p como eje.

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

 Situación 3 Descubrimos que g, p y cur en la figura anterior forman una línea discontinua y p es convexo, por lo que primero realiza una rotación simple hacia la izquierda con p como eje y luego realiza una rotación simple hacia la derecha con g como centro. . El código de saldo es el siguiente:

//调整平衡,并且更新颜色
		while (parent && parent->_col == Red)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == Red)
				{
					parent->_col = uncle->_col = Black;
					grandfather->_col = Red;
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (parent->_left == cur)
					{
						RotateR(grandfather);
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else if (parent->_right == cur)
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = Black;
						grandfather->_col = Red;
					}
					else
					{
						assert(false);
					}
					break;
		        }
			}
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == Red)
				{
					parent->_col = uncle->_col = Black;
					grandfather->_col = Red;
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (parent->_right == cur)
					{
						RotateL(grandfather);
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else if (parent->_left == cur)
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = Black;
						grandfather->_col = Red;
					}
					else
					{
						assert(false);
					}
					break;
				}

			}
		} 
		_root->_col = Black;
		return make_pair(iterator(newnode), true);
	}

El código para rotación única derecha-izquierda está disponible en el capítulo AVL, por lo que no lo mostraremos en detalle aquí.

El código completo de la implementación del árbol rojo-negro en realidad no es muy importante, solo necesitamos conocer la naturaleza del árbol rojo-negro y las diversas situaciones en las que ajusta el equilibrio. A continuación, hablemos del método de verificación del equilibrio del árbol rojo-negro:

bool IsRBTree()
	{
		//空树也符合红黑树
		if (_root == nullptr)
		{
			return true;
		 }

		
		//验证根节点是否为黑色
		if (_root->_col == Red)
		{
			cout << "根节点为红色,不符合规则" << endl;
			return false; 
		}


		int ref = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == Black)
			{
				++ref;
			}
			cur = cur->_left;
		}

		return _IsRBTree(_root, 0, ref);
		
		
	}

	bool _IsRBTree(Node* root, int count, int ref)
	{
		if (root == nullptr)
		{
			if (count != ref)
			{
				cout << "每条路径的黑色节点数不相等" << endl;
				return false;
			}
			return true;
		}

		if (root->_col == Black)
		{
			++count;
		}

		Node* ppnode = root->_parent;
		if (ppnode && root->_col == Red && ppnode->_col == Red)
		{
			cout << "存在相连的红色节点" << endl;
			return false;
		}

		return _IsRBTree(root->_left, count, ref) && _IsRBTree(root->_right, count, ref);
	}

Eso es todo por la explicación del árbol rojo-negro, ¡gracias por tu lectura y apoyo!

Supongo que te gusta

Origin blog.csdn.net/m0_69005269/article/details/130217969
Recomendado
Clasificación