Estructura de datos avanzada: árbol rojo-negro

Tabla de contenido

1. El concepto de árbol rojo-negro.

2. Propiedades de los árboles rojo-negros.

3. Árbol rojo-negro

6. Verificación del árbol rojo-negro.

7. Eliminación de árboles rojo-negros.

8. Comparación de árboles rojo-negro y números AVL

9. Aplicación de árboles rojo-negros.

10. Código completo

10.1 RBTárbol.h

10.2 prueba.cpp

1. El concepto de árbol rojo-negro.

Un árbol rojo-negro es un árbol de búsqueda binario , pero se agrega un bit de almacenamiento a cada nodo para representar 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 garantiza que ningún camino será dos veces más largo que cualquier otro camino ( el camino más largo no excede 2 veces el camino más corto ), por lo que está cerca del equilibrio .

 Ambos son árboles equilibrados de búsqueda binaria, pero el control del árbol AVL es mucho más estricto que el del árbol rojo-negro. Si el valor absoluto del factor de equilibrio de cada nodo en el árbol AVL no excede 1, provocará Desrotación y ajuste constantes, y se incurrirá en un costo relativamente alto. El costo, y el árbol rojo-negro aquí se parece más a un equilibrio aproximado, y las condiciones no son tan duras.

El siguiente árbol está equilibrado desde la perspectiva de un árbol rojo-negro, pero desequilibrado desde la perspectiva de un árbol AVL y necesita rotarse y ajustarse:

 Sin embargo, desde la perspectiva de la eficiencia de búsqueda, el árbol AVL es aún mejor, porque su estándar de equilibrio es alto, lo que lo hace más equilibrado. Con el mismo número de nodos, la altura del árbol AVL será menor. Además de almacenar 1 millón de datos, el árbol AVL tiene aproximadamente 20 capas (log100w), y el árbol rojo-negro en el peor de los casos puede alcanzar las 40 capas Obviamente, la eficiencia de búsqueda del árbol AVL es alta. Pero no hay diferencia entre buscar 20 veces en la memoria y buscar 40 veces, porque la CPU es lo suficientemente rápida, así que lo mencionaré brevemente aquí.

2. Propiedades de los árboles rojo-negros.

1. Cada nodo es rojo o negro.
2. El nodo raíz debe ser negro .
3. Si un nodo es rojo, entonces sus dos nodos secundarios son negros ( no hay nodos rojos consecutivos )
4. Para cada nodo, hay una ruta simple desde el nodo hasta todos sus nodos hoja descendientes. Todos contienen el mismo número de nodos negros ( cada ruta contiene el mismo número de nodos negros )
5. Cada nodo hoja es negro (el nodo hoja aquí se refiere al nodo vacío-》nodo NIL)

De acuerdo con estas reglas, ¿cómo garantiza el árbol rojo-negro que el camino más largo no exceda el doble del camino más corto?

En primer lugar, sabemos por el análisis de reglas que asumimos que el número de nodos negros en una ruta es N, luego la ruta más larga y la ruta más corta son las siguientes:

  • Camino más corto: todo negro

  • El camino más largo: un intervalo negro y otro rojo.

 La razón del intervalo entre un negro y un rojo aquí es que el árbol rojo-negro no permite nodos rojos continuos. Para garantizar el número máximo de nodos, solo el intervalo entre un negro y un rojo puede lograr el más largo. En resumen, cuando el número de nodos negros es Cuando el número se fija en N, el número de nodos de ruta más corta es N y el número de nodos de ruta más larga es 2N.

3. Árbol rojo-negro

En comparación con el árbol AVL, la implementación de los nodos aquí todavía se crea como un modelo KV y una estructura de cadena de tres puntas . El único cambio es que el rojo y el negro deben definirse mediante enumeración, y la variable _col debe definirse dentro del clase de nodo Indica el color del nodo y finalmente recuerda escribir el constructor .

enum Colour
{
	Red,
	Black,
};
//节点类
template <class K, class V>
struct RBTreeNode
{
	//三叉链结构
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	//存储的键值对
	pair<K, V> _kv;
	//节点的颜色
	Colour _col;
	//构造函数
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(Red)
	{}
};

¿Por qué los nodos insertados se procesan en rojo en el constructor?

  1. Si se procesa como negro, definitivamente conducirá a un nodo negro más en la ruta donde se inserta el nodo recién insertado, lo que ya no satisface la propiedad de tener la misma cantidad de nodos negros en cada ruta y definitivamente destruirá la propiedad. 4, que es difícil de mantener en este momento.
  2. Si se procesa en rojo, el nodo principal también puede ser rojo. En este momento, aparecen nodos rojos continuos, lo que destruye la propiedad 3. Sin embargo, podemos ajustarlo hacia arriba en este momento, pero si el nodo principal es negro, entonces hay no es necesario operarlo y no viola de ninguna naturaleza.

Según los pros y los contras, insertar un nodo negro definitivamente destruirá la propiedad 4 , mientras que insertar un nodo rojo puede destruir la propiedad 3 , por lo que es mejor tratarlo en rojo.

5. Operación de inserción del árbol rojo-negro.

La operación de inserción del árbol rojo-negro se divide principalmente en estos pasos principales:

  • 1. El árbol está vacío al principio y se agregan nuevos nodos directamente;
  • 2. Comience con un árbol que no esté vacío y busque una ubicación adecuada para insertarlo;
  • 3. Después de encontrar la posición de inserción adecuada, realizar un vínculo bidireccional entre el padre y el niño;
  • 4. Compruebe si las propiedades del árbol rojo-negro causan daños después de insertar el nuevo nodo.

A continuación, analízalos uno por uno:

  • 1. El árbol está vacío al principio y se agregan nuevos nodos directamente:

Debido a que el árbol está vacío, simplemente cree un nodo recién insertado y utilícelo como root_root, y luego actualice color_col a negro.

  • 2. Comience con un árbol que no esté vacío y busque la ubicación adecuada para la inserción:

La idea aquí es la misma que la idea de encontrar una posición de inserción adecuada en un árbol de búsqueda binario, y se deben seguir los siguientes pasos:

  1. Valor insertado> valor de nodo, actualizar a la búsqueda del subárbol derecho
  2. Valor insertado <valor de nodo, actualizar a la búsqueda del subárbol izquierdo
  3. Valor insertado = valor de nodo, la inserción de redundancia de datos falla y devuelve falso

Cuando finaliza el bucle, significa que se ha encontrado la ubicación adecuada para la inserción y se puede llevar a cabo el siguiente paso de vinculación.

  • 3. Luego de encontrar la posición de inserción adecuada, realizar un vínculo bidireccional entre el padre y el niño:

Tenga en cuenta que el nodo aquí se compone de una cadena de tres vías, por lo que el vínculo final entre el hijo de backend y el padre es un vínculo de dos vías. La operación específica es la siguiente:

  1. Valor insertado> valor del padre, vincule el valor insertado a la derecha del padre
  2. Valor insertado <valor del padre, vincule el valor insertado a la izquierda del padre
  3. Debido a que es un enlace de tres puntas, recuerde tener un enlace de dos vías después de insertarlo (enlaces del niño al padre)

Llegar a este punto significa que el nodo ha sido insertado, a continuación debemos ajustar el color del árbol rojo-negro.

  • 4. Compruebe si las propiedades del árbol rojo-negro causan daños después de insertar el nuevo nodo:

No todas las situaciones requieren ajustes: cuando el padre del nodo insertado es negro (el color predeterminado del nuevo nodo es rojo), no es necesario realizar ajustes porque no destruye ninguna propiedad del árbol rojo-negro.

Solo cuando el padre del nodo insertado es rojo (el color predeterminado del nuevo nodo también es rojo), es necesario realizar ajustes, porque en este momento el nodo insertado y el padre son nodos rojos, pero el árbol rojo-negro no permitir nodos rojos consecutivos. En este punto es momento de hacer ajustes.

Tenga en cuenta que dado que el padre p del nodo cur insertado es rojo, de acuerdo con las propiedades del árbol rojo-negro (el nodo raíz es negro), el padre g de su padre, es decir, el abuelo debe existir y debe ser negro. luego el nodo u hermano de su padre (puede no existir) Es decir, el tío del nodo cur recién insertado. Por lo tanto, estamos de acuerdo: cur es el nodo actual, p es el nodo padre, g es el nodo abuelo y u es el nodo tío .

El método de ajuste aquí depende principalmente del color del nodo tío. Diferentes nodos tío darán lugar a tres situaciones diferentes que deben ajustarse:

  • Caso 1: cur es rojo, p es rojo, g es negro, u existe y es rojo
  • Caso 2: cur es rojo, p es rojo, g es negro, u no existe
  • Caso 3: cur es rojo, p es rojo, g es negro, u existe y es negro

Analicémoslos por separado:

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

Para evitar la aparición de nodos rojos continuos, podemos convertir el nodo padre p en negro, pero para asegurarnos de que el número de nodos negros en cada ruta sea el mismo, debemos convertir el nodo abuelo g en rojo (sin afectar el número de nodos negros en otros caminos) y luego convierta el nodo tío u en negro.

El ajuste aún no ha terminado. En este momento, el nodo abuelo g está en rojo. Pero ¿qué pasa si este árbol es un árbol completo? Es decir, g es el nodo raíz, por lo que solo necesita convertir el nodo g en negro.

Si este árbol es un subárbol de un árbol, entonces el nodo abuelo g se usa como el nodo cur recién insertado y continúa ajustándose hacia arriba (continúe determinando el padre, el tío ...) hasta que se complete el ajuste.

Suplemento: al caso 1 no le importa la relación izquierda-derecha, solo cambia de color pero no gira, por lo que no importa si p o u está a la izquierda o a la derecha de g, y no importa si cur está a la izquierda o la derecha de la pág.

A continuación, analice la situación 2:

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

 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 cuyo color sea negro, lo que no satisface la propiedad 4: el número de negros Los nodos en cada camino son los mismos.

En este momento, es una estructura de rotación simple hacia la derecha muy clásica (el nuevo nodo se inserta en el lado izquierdo del subárbol superior izquierdo), podemos realizar una rotación simple hacia la derecha primero y luego actualizar el color . Los pasos específicos son los siguientes:

  1. Dejemos que el abuelo g se convierta en el subárbol derecho del padre p.
  2. El padre p sirve como nodo raíz.
  3. Actualice el nodo principal p a negro.
  4. Actualice el abuelo g a rojo.

  • Reponer:

Si p es el hijo derecho de g y cur es el hijo derecho de p, realice una rotación única hacia la izquierda en p. Ejemplo:

Si la relación entre las tres generaciones de antepasados ​​​​y nietos es una polilínea (los tres nodos de cur, padre y abuelo son un descuento), primero debemos realizar una operación de doble rotación y luego ajustar el color . Ajuste, la raíz del subárbol rotado es negra, por lo que no hay necesidad de ir más allá. Ejemplo:

 Para resumir:

  1. p es el lado izquierdo de g, y cur es el lado izquierdo de p, luego se realiza una rotación única hacia la derecha + p se vuelve negro y g se vuelve rojo;
  2. p es el lado derecho de g, cur es el lado derecho de p, luego la monorotación izquierda + p se vuelve negra y g se vuelve roja;
  3. p es el lado izquierdo de g y cur es el lado derecho de p, luego la doble rotación izquierda y derecha + cur se vuelve negra y g se vuelve roja;
  4. p es el lado derecho de g y cur es el lado izquierdo de p, luego realice una doble rotación derecha-izquierda + cur se vuelve negro y g se vuelve rojo.

Entremos en la situación tres.

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

Esta situación nunca existe sola . Nunca puede ser la inserción real de un nuevo nodo cur. Entonces también habrá situaciones en las que p es rojo, g es negro y u existe y es negro. Si existe, solo puede significar que el nodo o estructura se insertó previamente. Hay un problema con la función porque no se ajusta a las propiedades de un árbol rojo-negro antes de la inserción ( el número de nodos negros en cada ruta es el mismo ).

Ahora que ha aparecido la situación tres debe ser razonable, es una situación especial que se basa en la situación uno y se sigue ajustando hacia arriba, concretamente haremos un dibujo para demostrar:

 En este momento, la situación 3 es obvia: cur es rojo, pp es rojo, gg es negro y u existe y es negro, lo que demuestra que la situación 3 evoluciona mediante el ajuste ascendente de la situación 1 . Y este nuevo nodo debe insertarse o evolucionar desde cualquier subárbol izquierdo o derecho de p y x, lo que hará que el cur posterior cambie de negro a rojo.

En este momento, es una estructura de rotación simple hacia la derecha muy clásica (cur está en el lado izquierdo del subárbol superior izquierdo), primero podemos realizar una rotación simple hacia la derecha y luego actualizar el color . Los pasos específicos son los siguientes:

  1. Sea el subárbol derecho de p el subárbol izquierdo de g;
  2. Sea p la posición del nodo raíz;
  3. El subárbol derecho de p apunta a g;
  4. Actualice el color de p a negro;
  5. Actualiza el color de g a rojo.

 Reponer:

Si p es el hijo derecho de g y cur es el hijo derecho de p, realice una rotación simple hacia la izquierda + ajuste de color, ejemplo:

Si se descuenta la relación entre las tres generaciones de antepasados ​​​​y nietos (los tres nodos de cur, padre y abuelo son una polilínea), primero debemos realizar una operación de doble rotación y luego ajustar el color. , la raíz del subárbol rotado es negra, por lo que no es necesario ir más allá. Ejemplo:

Para resumir:

  • p es el lado izquierdo de g, cur es el lado izquierdo de p, luego realice una rotación simple hacia la derecha + p se vuelve negro y g se vuelve rojo;
  • p es el lado derecho de g y cur es el lado derecho de p, luego realice la rotación hacia la izquierda + p se vuelve negro y g se vuelve rojo;
  • p es la izquierda de g, cur es la derecha de p, luego realice una doble rotación hacia la izquierda y hacia la derecha + cur se vuelve negro y g se vuelve rojo;
  • p es el lado derecho de g y cur es el lado izquierdo de p, luego realice una doble rotación derecha-izquierda + cur se vuelve negro y g se vuelve rojo.

Después de rotar + cambiar el caso 2 y el caso 3, este subárbol no viola las reglas del árbol rojo-negro en comparación con antes de la inserción, y el número de nodos negros permanece sin cambios, lo que no afectará la capa superior y el procesamiento finaliza. .

El código se muestra a continuación:

bool Insert(const pair<K, V>& kv)
{
	//1、一开始为空树,直接new新节点
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = Black;//新插入的节点处理成黑色
		return true;
	}
	//2、寻找插入的合适位置
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;//插入的值 > 节点的值,更新到右子树查找
		}
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;//插入的值 < 节点的值,更新到左子树查找
		}
		else
		{
			return false;//插入的值 = 节点的值,数据冗余插入失败,返回false
		}
	}
	//3、找到了插入的位置,进行父亲与插入节点的链接
	cur = new Node(kv);
	cur->_col = Red;//插入的节点处理成红色
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;//插入的值 > 父亲的值,链接在父亲的右边
	}
	else
	{
		parent->_left = cur;//插入的值 < 父亲的值,链接在父亲的左边
	}
	cur->_parent = parent;//三叉链,要双向链接
 
//4、检测新节点插入后,红黑树的性质是否造到破坏
	while (parent && parent->_col == Red)//存在连续的红色节点
	{
		Node* grandfather = parent->_parent;
		assert(grandfather);
		//先确保叔叔的位置
		if (grandfather->_left == parent)
		{
			Node* uncle = grandfather->_right;
			//情况一:cur为红,p为红,g为黑,u存在且为红
			if (uncle && uncle->_col == Red)
			{
				//变色
				parent->_col = uncle->_col = Black;
				grandfather->_col = Red;
				//继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			//情况二+情况三:叔叔不存在,或者叔叔存在且为黑
			else
			{
				if (cur == parent->_left)//p为g的左,cur为p的左,则进行右单旋 + p变黑,g变红
				{
					//		  g
					//     p
					// cur
					RotateR(grandfather);
					parent->_col = Black;
					grandfather->_col = Red;
				}
				else//p是g的左,cur是p的右,则进行左右双旋 + cur变黑, g变红
				{
					//		  g
					//	 p
					//	     cur
					RotateLR(grandfather);
					cur->_col = Black;
					grandfather->_col = Red;
				}
				break;
			}
		}
		else//grandfather->_right == parent
		{
			Node* uncle = grandfather->_left;
			//情况一:cur为红,p为红,g为黑,u存在且为红
			if (uncle && uncle->_col == Red)
			{
				//变色
				parent->_col = uncle->_col = Black;
				grandfather->_col = Red;
				//继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			//情况二+情况三:叔叔不存在,或者叔叔存在且为黑
			else
			{
				if (cur == parent->_right)//p为g的右,cur为p的右,则进行左单旋 + p变黑,g变红
				{
					//	g
					//	   p
					//	     cur
					RotateL(grandfather);
					parent->_col = Black;
					grandfather->_col = Red;
				}
				else//p是g的右,cur是p的左,则进行右左双旋 + cur变黑, g变红
				{
					//   g
					//	      p
					//	cur
					RotateRL(grandfather);
					cur->_col = Black;
					grandfather->_col = Red;
				}
				break;
			}
		}
	}
	_root->_col = Black;//暴力处理把根变成黑色
	return true;
}
//1、左单旋
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	Node* ppNode = parent->_parent;//提前保持parent的父亲
	//1、建立parent和subRL之间的关系
	parent->_right = subRL;
	if (subRL)//防止subRL为空
	{
		subRL->_parent = parent;
	}
	//2、建立subR和parent之间的关系
	subR->_left = parent;
	parent->_parent = subR;
	//3、建立ppNode和subR之间的关系
	if (parent == _root)
	{
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{
		if (parent == ppNode->_left)
		{
			ppNode->_left = subR;
		}
		else
		{
			ppNode->_right = subR;
		}
		subR->_parent = ppNode;//三叉链双向链接关系
	}
}
//2、右单旋
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	Node* ppNode = parent->_parent;
	//1、建立parent和subLR之间的关系
	parent->_left = subLR;
	if (subLR)
	{
		subLR->_parent = parent;
	}
	//2、建立subL和parent之间的关系
	subL->_right = parent;
	parent->_parent = subL;
	//3、建立ppNode和subL的关系
	if (parent == _root)
	{
		_root = subL;
		_root->_parent = nullptr;
	}
	else
	{
		if (parent == ppNode->_left)
		{
			ppNode->_left = subL;
		}
		else
		{
			ppNode->_right = subL;
		}
		subL->_parent = ppNode;//三叉链双向关系
	}
}
//3、左右双旋
void RotateLR(Node* parent)
{
	RotateL(parent->_left);
	RotateR(parent);
}
//4、右左双旋
void RotateRL(Node* parent)
{
	RotateR(parent->_right);
	RotateL(parent);
}

6. Verificación del árbol rojo-negro.

La verificación de árboles rojo-negro se divide principalmente en dos pasos principales:

  • 1. Compruebe si satisface el árbol de búsqueda binario (si el recorrido en orden es una secuencia ordenada)
  • 2. Compruebe si cumple con las propiedades de un árbol rojo-negro.

A continuación, se demostrarán por separado:

  • 1. Compruebe si satisface el árbol de búsqueda binario (si el recorrido en orden es una secuencia ordenada):

Aquí solo necesita escribir recursivamente un recorrido en orden y determinar si el resultado del caso de prueba es una secuencia ordenada para determinar el árbol de búsqueda binario:

//验证是否为一颗搜索二叉树
void InOrder()
{
	_InOrder(_root);//调用中序遍历子树
	cout << endl;
}
//中序遍历的子树
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;
	_InOrder(root->_left);
	cout << root->_kv.first << " ";
	_InOrder(root->_right);
}
  • 2. Compruebe si cumple con las propiedades de un árbol rojo-negro:

Aquí solo necesita determinar si se cumplen las cinco reglas del árbol rojo-negro, las operaciones específicas son las siguientes:

  • 1. Si el nodo raíz es negro;
  • 2. Si el número de nodos negros en cualquier ruta es el mismo (si cada ruta recursiva es la misma que la determinada);
  • 3. Detecte recursivamente si se viola la propiedad tres y aparecen nodos rojos continuos.
bool IsBalanceTree()
{
	Node* pRoot = _root;
	// 空树也是红黑树
	if (pRoot == nullptr)
		return true;
	// 检测根节点是否满足情况
	if (pRoot->_col != Black)
	{
		cout << "违反红黑树性质二:根节点必须为黑色" << endl;
		return false;
	}
	// 获取任意一条路径中黑色节点的个数-->拿最左路径作为比较基准值
	size_t blackCount = 0;
	Node* pCur = pRoot;
	while (pCur)
	{
		if (pCur->_col == Black)
			blackCount++;
		pCur = pCur->_left;
	}
	// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
	size_t k = 0;
	return _IsValidRBTree(pRoot, k, blackCount);
}
bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
{
	//走到null之后,判断k和black是否相等
	if (pRoot == nullptr)
	{
		if (k != blackCount)
		{
			cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
			return false;
		}
		return true;
	}
	// 统计黑色节点的个数
	if (pRoot->_col == Black)
		k++;
	// 检测当前节点与其双亲是否都为红色
	Node* pParent = pRoot->_parent;
	if (pParent && pParent->_col == Red && pRoot->_col == Red)
	{
		cout << "违反性质三:没有连在一起的红色节点,而这里出现了" << endl;
		return false;
	}
	return _IsValidRBTree(pRoot->_left, k, blackCount) && _IsValidRBTree(pRoot->_right, k, blackCount);
}

7. Eliminación de árboles rojo-negros.

La eliminación de árboles rojo-negro es la misma que la de los árboles AVL, por lo que no haré demasiadas demostraciones. Para obtener más detalles, consulte "Introducción a los algoritmos" o "Análisis del código fuente STL". Publicación de blog del jefe: Operaciones de inserción y eliminación de árboles rojo-negro

8. Comparación de árboles rojo-negro y números AVL

Tanto los árboles rojo-negro como los árboles AVL son árboles binarios equilibrados eficientes. La complejidad temporal de agregar, eliminar, modificar y verificar es O (logN). Los árboles rojo-negro no persiguen el equilibrio absoluto. Solo necesitan garantizar que el árbol más largo La ruta no excede el doble de la ruta más corta. En términos relativos, el número de inserciones y rotaciones se reduce, por lo que el rendimiento es mejor que el árbol AVL en estructuras donde a menudo se realizan adiciones y eliminaciones, y la implementación de árboles rojo-negro es relativamente simple, por lo que hay más árboles rojo-negros en uso real.

9. Aplicación de árboles rojo-negros.

  •  1. C++ STL: map/set, mutil_map/mutil_set
  • 2. Biblioteca Java
  • 3. núcleo de Linux
  • 4. Algunas otras bibliotecas 

10. Código completo

10.1 RBTárbol.h

#pragma once
#include<iostream>
#include<queue>
#include<vector>
#include<assert.h>
using namespace std;
enum Colour
{
	Red,
	Black,
};
//节点类
template <class K, class V>
struct RBTreeNode
{
	//三叉链结构
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	//存储的键值对
	pair<K, V> _kv;
	//节点的颜色
	Colour _col;
	//构造函数
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(Red)
	{}
};
//红黑树的类
template <class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
	//1、一开始为空树,直接new新节点
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = Black;//新插入的节点处理成黑色
			return true;
		}
	//2、寻找插入的合适位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;//插入的值 > 节点的值,更新到右子树查找
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;//插入的值 < 节点的值,更新到左子树查找
			}
			else
			{
				return false;//插入的值 = 节点的值,数据冗余插入失败,返回false
			}
		}
	//3、找到了插入的位置,进行父亲与插入节点的链接
		cur = new Node(kv);
		cur->_col = Red;//插入的节点处理成红色
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;//插入的值 > 父亲的值,链接在父亲的右边
		}
		else
		{
			parent->_left = cur;//插入的值 < 父亲的值,链接在父亲的左边
		}
		cur->_parent = parent;//三叉链,要双向链接

	//4、检测新节点插入后,红黑树的性质是否造到破坏
		while (parent && parent->_col == Red)//存在连续的红色节点
		{
			Node* grandfather = parent->_parent;
			assert(grandfather);
			//先确保叔叔的位置
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//情况一:cur为红,p为红,g为黑,u存在且为红
				if (uncle && uncle->_col == Red)
				{
					//变色
					parent->_col = uncle->_col = Black;
					grandfather->_col = Red;
					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二+情况三:叔叔不存在,或者叔叔存在且为黑
				else
				{
					if (cur == parent->_left)//p为g的左,cur为p的左,则进行右单旋 + p变黑,g变红
					{
						//		  g
						//     p
						// cur
						RotateR(grandfather);
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else//p是g的左,cur是p的右,则进行左右双旋 + cur变黑, g变红
					{
						//		  g
						//	 p
						//	     cur
						RotateLR(grandfather); 
						cur->_col = Black;
						grandfather->_col = Red;
					}
					break;
				}
			}
			else//grandfather->_right == parent
			{
				Node* uncle = grandfather->_left;
				//情况一:cur为红,p为红,g为黑,u存在且为红
				if (uncle && uncle->_col == Red)
				{
					//变色
					parent->_col = uncle->_col = Black;
					grandfather->_col = Red;
					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二+情况三:叔叔不存在,或者叔叔存在且为黑
				else
				{
					if (cur == parent->_right)//p为g的右,cur为p的右,则进行左单旋 + p变黑,g变红
					{
						//	g
						//	   p
						//	     cur
						RotateL(grandfather);
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else//p是g的右,cur是p的左,则进行右左双旋 + cur变黑, g变红
					{
						//   g
						//	      p
						//	cur
						RotateRL(grandfather); 
						cur->_col = Black;
						grandfather->_col = Red;
					}
					break;
				}
			}
		}
		_root->_col = Black;//暴力处理把根变成黑色
		return true;
	}
//验证是否为一颗搜索二叉树
	void InOrder()
	{
		_InOrder(_root);//调用中序遍历子树
		cout << endl;
	}
//验证是否为红黑树
	bool IsBalanceTree()
	{
		Node* pRoot = _root;
		// 空树也是红黑树
		if (pRoot == nullptr)
			return true;
		// 检测根节点是否满足情况
		if (pRoot->_col != Black)
		{
			cout << "违反红黑树性质二:根节点必须为黑色" << endl;
			return false;
		}
		// 获取任意一条路径中黑色节点的个数-->拿最左路径作为比较基准值
		size_t blackCount = 0;
		Node* pCur = pRoot;
		while (pCur)
		{
			if (pCur->_col == Black )
				blackCount++;
			pCur = pCur->_left;
		}
		// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
		size_t k = 0;
		return _IsValidRBTree(pRoot, k, blackCount);
	}

//求一棵树的高度
	void Height()
	{
		cout << "最长路径:" << _maxHeight(_root) << endl;
		cout << "最短路径:" << _minHeight(_root) << endl;
	}
private:
	//1、左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* ppNode = parent->_parent;//提前保持parent的父亲
		//1、建立parent和subRL之间的关系
		parent->_right = subRL;
		if (subRL)//防止subRL为空
		{
			subRL->_parent = parent;
		}
		//2、建立subR和parent之间的关系
		subR->_left = parent;
		parent->_parent = subR;
		//3、建立ppNode和subR之间的关系
		if (parent == _root)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;//三叉链双向链接关系
		}
	}
	//2、右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppNode = parent->_parent;
		//1、建立parent和subLR之间的关系
		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		//2、建立subL和parent之间的关系
		subL->_right = parent;
		parent->_parent = subL;
		//3、建立ppNode和subL的关系
		if (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;//三叉链双向关系
		}
	}
	//3、左右双旋
	void RotateLR(Node* parent)
	{
		RotateL(parent->_left);
		RotateR(parent);
	}

	//4、右左双旋
	void RotateRL(Node* parent)
	{
		RotateR(parent->_right);
		RotateL(parent);
	}
//中序遍历的子树
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}
//求一棵树的最长路径的子树
	int _maxHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int lh = _maxHeight(root->_left);
		int rh = _maxHeight(root->_right);
		return lh > rh ? lh + 1 : rh + 1;
	}
//求一棵树的最短路径的子树
	int _minHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int lh = _minHeight(root->_left);
		int rh = _minHeight(root->_right);
		return lh < rh ? lh + 1 : rh + 1;
	}
//求是否满足红黑树性质的子树
	bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
	{
		//走到null之后,判断k和black是否相等
		if (pRoot == nullptr)
		{
			if (k != blackCount)
			{
				cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}
			return true;
		}
		// 统计黑色节点的个数
		if (pRoot->_col == Black)
			k++;
		// 检测当前节点与其双亲是否都为红色
		Node* pParent = pRoot->_parent;
		if (pParent && pParent->_col == Red && pRoot->_col == Red)
		{
			cout << "违反性质三:没有连在一起的红色节点,而这里出现了" << endl;
			return false;
		}
		return _IsValidRBTree(pRoot->_left, k, blackCount) && _IsValidRBTree(pRoot->_right, k, blackCount);
	}
public:
		//层序遍历(非必须)
	vector<vector<int>> levelOrder() {
		vector<vector<int>> vv;
		if (_root == nullptr)
			return vv;
		queue<Node*> q;
		int levelSize = 1;
		q.push(_root);
		while (!q.empty())
		{
			// levelSize控制一层一层出
			vector<int> levelV;
			while (levelSize--)
			{
				Node* front = q.front();
				q.pop();
				levelV.push_back(front->_kv.first);
				if (front->_left)
					q.push(front->_left);

				if (front->_right)
					q.push(front->_right);
			}
			vv.push_back(levelV);
			for (auto e : levelV)
			{
				cout << e << " ";
			}
			cout << endl;
			// 上一层出完,下一层就都进队列
			levelSize = q.size();
		}
		return vv;
	}

private:
	Node* _root = nullptr;
};

10.2 prueba.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"RBTree.h"
void TestRBTree1()
{
	//int a[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
	int a[] = { 30, 29, 28, 27, 26, 25, 24, 11, 8, 7, 6, 5, 4, 3, 2, 1 };
	RBTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}
	t.levelOrder();
	t.InOrder();
	t.Height();
}

void TestRBTree2()
{
	const size_t N = 1024 * 1024;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; ++i)
	{
		//v.push_back(rand());
		v.push_back(i);
	}

	RBTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
	}

	//t.levelOrder();
	//cout << endl;
	cout << "Ƿƽ? " << t.IsBalanceTree() << endl;
	t.Height();

	//t.InOrder();
}
int main()
{
	//TestRBTree1();
	TestRBTree2();
	return 0;
}

Supongo que te gusta

Origin blog.csdn.net/m0_49687898/article/details/131343995
Recomendado
Clasificación