データ構造: 赤黒ツリーの説明 (C++)

1 はじめに

  • この記事は、C++ を理解するために、赤黒ツリーと色の変化の回転ルールの基本概念を理解することを目的としています。 a> の基本原則は red- の削除操作をカバーしていません。黒い木々。 setmap
  • 基本的な回転操作 (単一回転と二重回転) については、この記事では詳しく説明しません。詳細な説明はここにあります:
    AVL ツリーの回転の説明



2. 赤黒木の簡単な説明

2.1 コンセプト

赤黒ツリーは二分探索ツリーですが、各ノードにはノードの色を表すストレージ ビットが追加されます または です。赤黒ツリーは、ルートからリーフまでのパス上の各ノードの色を制限することで、最長パスが最短パスの 2 倍を超えないようにします なので、平衡に近い状態になります。


2.2 プロパティ

  1. 各ノードは赤または黒です。
  2. ルート ノードは黒です。 (回転数を減らすため、回転については後で理解します)
  3. 赤いノードの場合、その子は黒のみになります。 (つまり、パス上に連続した赤いノードが表示されることはありません)
  4. 各パスには同じ数の黒いノードが含まれている必要があります。

上記のルールの制限により、赤黒ツリーの最長パスは最短パスの 2 倍を超えることはありません。 、高度な相対的バランスを維持
3 と 4 を組み合わせて、次の 2 つのパスを確認します。
最長: 黒、赤、黒、赤、黒、赤... a>
最短: 黒、黒、黒…………



3. 赤黒木の挿入

3.1 新規挿入ノードの色について

新しく挿入したノードについては、赤に設定します。その理由は、赤黒ツリーの各パスには同じ数の黒いノードが含まれている必要があるためです (プロパティ 4) 、新しく挿入された赤いノードは、赤黒ツリーの構造を間違いなく破壊します。新しく挿入された黒いノードは、プロパティ 4 に間違いなく準拠しません 調整が難しくなります。
ここに画像の説明を挿入します


3.2 ノードの定義

//用枚举来定义颜色
enum Color
{
    
    
	RED,
	BLACK
};

//这里直接实现key_value模型
template<class K, class V>
struct RBTreeNode
{
    
    
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;  //涉及到旋转,多加父亲指针来简化操作
	pair<K, V> _kv;  //存储键值对
	Color _col; //颜色
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		,_col(RED)  //新节点颜色为红色
	{
    
    }
};

3.3 新しいノードの挿入

これは比較的簡単です。二分探索ツリーのルールに従って挿入するだけです。

bool Insert(const pair<K, V>& kv)
{
    
    
	if (_root == nullptr)
	{
    
    
		_root = new Node(kv);
		_root->_col = BLACK;
		return true;
	}
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
    
    
		if (kv.first > cur->_kv.first) //待插入节点在右子树
		{
    
    
			parent = cur;
			cur = cur->_right;
		}
		else if (kv.first < cur->_kv.first)  //待插入节点在左子树
		{
    
    
			parent = cur;
			cur = cur->_left;
		}
		else  //相同
		{
    
    
			return false;
		}
	}
	cur = new Node(kv);
	if (kv.first > parent->_kv.first) //新节点在父亲右子树
	{
    
    
		parent->_right = cur;
	}
	else  //新节点在父亲左子树
	{
    
    
		parent->_left = cur;
	}
	cur->_parent = parent;  //记得更新父亲指针
	
	/// 变色旋转维持红黑树结构(暂时省略)  //
	
	_root->_col = BLACK; //可能改变根部颜色,保持根部为黑色
	return true;
}

3.4 挿入後に調整が必要かどうかを判断する

実際、赤黒ツリーを挿入した後は、現在のノードと親の色を確認するだけでよく、新しいノードは赤でなければなりません。

  1. 父親は黒人であり、ルールに準拠しており、調整の必要はありません。
  2. 父親は赤色ですが、このとき赤色の連続ノードが現れるので調整が必要です。

3.5 挿入後の赤黒ツリー構造の維持(キーポイント)

説明の便宜上、次のように定義します。

  1. cur は現在のノードを表します
  2. p は cur の親ノードを表します
  3. u は叔父ノードを表します
  4. g は祖父 (p と u の父) ノードを表します
3.5.1cur、p、u は赤、g は黒

ここに画像の説明を挿入します
コード:

while (parent && parent->_col == RED)  //父亲为红就调整,调整到根部要结束
{
    
    
	Node* granderfather = parent->_parent;  //祖父
	//需要对叔叔进行操作,需要判断叔叔是祖父的左还是右
	if (parent == granderfather->_left)  //父亲是祖父的左子树
	{
    
    
		Node* uncle = granderfather->_right;
		if (uncle && uncle->_col == RED) //叔叔不为空并且叔叔为红,变色即可
		{
    
    
			uncle->_col = parent->_col = BLACK;
			granderfather->_col = RED; 
			//当前子树可能为部分,继续向上调整
			cur = granderfather;
			parent = cur->_parent;
		}
		else  //叔叔为空或为黑色
		{
    
     
			//先省略
		}
	}
	else  //父亲是祖父的右子树
	{
    
    
		Node* uncle = granderfather->_left;
		if (uncle && uncle->_col == RED)  //叔叔不空并且为红
		{
    
    
			parent->_col = uncle->_col = BLACK;
			granderfather->_col = RED;  
			//当前可能为部分子树,需要继续上调
			cur = granderfather;
			parent = cur->_parent;
		}
		else  //叔叔为空或为黑色
		{
    
    
			// 先省略
		}
	}
}

3.5.2cur、p は赤、g は黒、u は空 / u が存在する場合は黒

後で使用する回転インターフェイスは次のとおりです。

void RotateL(Node* parent)  //左单旋,rotate->旋转
{
    
    
	Node* SubR = parent->_right;
	Node* SubRL = SubR->_left;  //这个有可能为空
	Node* ppnode = parent->_parent;  //原来父亲的父亲

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

	SubR->_left = parent;
	parent->_parent = SubR;

	if (ppnode == nullptr)  //旋转的是整颗树
	{
    
    
		_root = SubR;
		SubR->_parent = nullptr;
	}
	else  //旋转的是部分
	{
    
    
		if (ppnode->_left == parent) //是左子树
		{
    
    
			ppnode->_left = SubR;
		}
		else  //是右子树
		{
    
    
			ppnode->_right = SubR;
		}
		SubR->_parent = ppnode;
	}
}

void RotateR(Node* parent)  //右单旋细节处理和左单旋差不多
{
    
    
	Node* SubL = parent->_left;
	Node* SubLR = SubL->_right;  //这个有可能为空
	Node* ppnode = parent->_parent;

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

	SubL->_right = parent;
	parent->_parent = SubL;

	if (ppnode == nullptr)  //旋转的是整颗树
	{
    
    
		_root = SubL;
		SubL->_parent = nullptr;
	}
	else  //旋转部分
	{
    
    
		if (ppnode->_left == parent)  //是左子树
		{
    
    
			ppnode->_left = SubL;
		}
		else  //右子树
		{
    
    
			ppnode->_right = SubL;
		}
		SubL->_parent = ppnode;
	}
}

回転を伴う状況はより複雑なので、別途説明します。

(1)p は g の左の子、cur は p の左の子です
ここに画像の説明を挿入します


(2)p は g の左の子、cur は p の右の子です

ここに画像の説明を挿入します


(3)p は g の右側の子、cur は p の右側の子です

ここに画像の説明を挿入します


(4)p は g の右の子、cur は p の左の子です

ここに画像の説明を挿入します

(1、2、3、4) を積分して、次の調整コードを取得します。

//到这里插入新节点的工作完成,下面进行结构调整:
while (parent && parent->_col == RED)  //父亲为红就调整,调整到根部要结束
{
    
    
	Node* granderfather = parent->_parent;  //祖父
	if (parent == granderfather->_left)  //父亲是祖父的左子树,p为g的左孩子
	{
    
    
		Node* uncle = granderfather->_right;
		if (uncle && uncle->_col == RED) //叔叔不为空并且叔叔为红,变色即可
		{
    
    
			uncle->_col = parent->_col = BLACK;
			granderfather->_col = RED; 
			//当前子树可能为部分,继续向上调整
			cur = granderfather;
			parent = cur->_parent;
		}
		else  //叔叔为空或为黑色
		{
    
     
			//     g
			//   p   u
			// c
			if (cur == parent->_left)  //当前为父亲的左子树,cur为p的左孩子
			{
    
    
				RotateR(granderfather);
				granderfather->_col = RED;
				parent->_col = BLACK;
			}
			else   //当前为父亲的右子树,cur为p的右孩子
			{
    
    
				//    g
				//  p   u
				//    c
				//左右双旋
				RotateL(parent);
				RotateR(granderfather);
				granderfather->_col = RED;
				cur->_col = BLACK;
			}
			break;  //这两种情况调整完可以结束
		}
	}
	else  //父亲是祖父的右子树,p为g的右孩子
	{
    
    
		Node* uncle = granderfather->_left;
		if (uncle && uncle->_col == RED)  //叔叔不空并且为红
		{
    
    
			parent->_col = uncle->_col = BLACK;
			granderfather->_col = RED;  
			//当前可能为部分子树,需要继续上调
			cur = granderfather;
			parent = cur->_parent;
		}
		else  //叔叔为空或为黑色
		{
    
    
			if (cur == parent->_right)  //当前为父亲的右,cur为p的右孩子
			{
    
    
				//    g
				//  u   p
				//        c
				//左旋
				RotateL(granderfather);
				parent->_col = BLACK;
				granderfather->_col = RED;
			}
			else  //当前为父亲的左,cur为p的左孩子
			{
    
    
				//   g
				// u   p
				//   c
				//右左双旋
				RotateR(parent);
				RotateL(granderfather);
				cur->_col = BLACK;
				granderfather->_col = RED;	
			}
			break;  //这两种情况调整完可以结束
		}
	}
}
_root->_col = BLACK; //保持根部为黑色



4. いくつかの簡単なテストインターフェイス

void InOrder()   //中序遍历,验证是否为二叉搜索树
{
    
    
	_InOrder(_root);
	cout << endl;
}

void _InOrder(Node* root)
{
    
    
	if (root == nullptr)
		return;

	_InOrder(root->_left);
	cout << root->_kv.first << " ";
	_InOrder(root->_right);
}

// 根节点->当前节点这条路径的黑色节点的数量
bool Check(Node* root, int blacknum, const int refVal)  
{
    
    
	if (root == nullptr)  //到根部看看当前路径黑色节点和标准值是否一致
	{
    
    
		//cout << balcknum << endl;
		if (blacknum != refVal)
		{
    
    
			cout << "存在黑色节点数量不相等的路径" << endl;
			return false;
		}

		return true;
	}

	/检查子比较复杂,可以反过来去检查红节点父是否为黑色
	if (root->_col == RED && root->_parent->_col == RED)  
	{
    
    
		cout << "有连续的红色节点" << endl;

		return false;
	}

	if (root->_col == BLACK)
	{
    
    
		++blacknum;  //为黑节点加一
	}

	return Check(root->_left, blacknum, refVal)
		&& Check(root->_right, blacknum, refVal);
}

bool IsBalance()
{
    
    
	if (_root == nullptr)
		return true;

	if (_root->_col == RED)
		return false;

	//参考值,即先算出一条路径的黑色节点数
	int refVal = 0;
	Node* cur = _root;
	while (cur)
	{
    
    
		if (cur->_col == BLACK)
		{
    
    
			++refVal;
		}

		cur = cur->_left;
	}

	int blacknum = 0;
	return Check(_root, blacknum, refVal);
}



5. 完全なコード

#pragma once
#include <iostream>
#include <utility>
using namespace std;

//用枚举来定义颜色
enum Color
{
    
    
	RED,
	BLACK
};

//这里直接实现key_value模型
template<class K, class V>
struct RBTreeNode
{
    
    
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;  //涉及到旋转,多加父亲指针来简化操作
	pair<K, V> _kv;  //存储键值对
	Color _col; //颜色
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		,_col(RED)  //新节点颜色为红色
	{
    
    }
};

template<class K, class V>
class RBTree
{
    
    
public:
	typedef RBTreeNode<K, V> Node;

	bool Insert(const pair<K, V>& kv)
	{
    
    
		if (_root == nullptr)
		{
    
    
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
    
    
			if (kv.first > cur->_kv.first) //待插入节点在右子树
			{
    
    
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_kv.first)  //待插入节点在左子树
			{
    
    
				parent = cur;
				cur = cur->_left;
			}
			else  //相同
			{
    
    
				return false;
			}
		}
		cur = new Node(kv);
		if (kv.first > parent->_kv.first) //在右子树
		{
    
    
			parent->_right = cur;
		}
		else
		{
    
    
			parent->_left = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)  //父亲为红就调整,调整到根部要结束
		{
    
    
			Node* granderfather = parent->_parent;  //祖父
			if (parent == granderfather->_left)  //父亲是祖父的左子树
			{
    
    
				Node* uncle = granderfather->_right;
				if (uncle && uncle->_col == RED) //叔叔不为空并且叔叔为红,变色即可
				{
    
    
					uncle->_col = parent->_col = BLACK;
					granderfather->_col = RED; 
					//当前子树可能为部分,继续向上调整
					cur = granderfather;
					parent = cur->_parent;
				}
				else  //叔叔为空或为黑色
				{
    
     
					//     g
					//   p   u
					// c
					if (cur == parent->_left)  //当前为父亲的左子树
					{
    
    
						RotateR(granderfather);
						granderfather->_col = RED;
						parent->_col = BLACK;
					}
					else   //当前为父亲的右子树
					{
    
    
						//    g
						//  p   u
						//    c
						//左右双旋
						RotateL(parent);
						RotateR(granderfather);
						granderfather->_col = RED;
						cur->_col = BLACK;
					}
					break;
				}
			}
			else  //父亲是祖父的右子树
			{
    
    
				Node* uncle = granderfather->_left;
				if (uncle && uncle->_col == RED)  //叔叔不空并且为红
				{
    
    
					parent->_col = uncle->_col = BLACK;
					granderfather->_col = RED;  
					//当前可能为部分子树,需要继续上调
					cur = granderfather;
					parent = cur->_parent;
				}
				else  //叔叔为空或为黑色
				{
    
    
					if (cur == parent->_right)  //当前为父亲的右
					{
    
    
						//    g
						//  u   p
						//        c
						//左旋
						RotateL(granderfather);
						parent->_col = BLACK;
						granderfather->_col = RED;
					}
					else  //当前为父亲的左
					{
    
    

						//   g
						// u   p
						//   c
						//右左双旋
						RotateR(parent);
						RotateL(granderfather);
						cur->_col = BLACK;
						granderfather->_col = RED;	
					}
					break;
				}
			}
		}
		_root->_col = BLACK; //保持根部为黑色
		return true;
	}


/// //
/// /
/// 	测试代码
	 
	void InOrder()   //中序遍历,验证是否为二叉搜索树
	{
    
    
		_InOrder(_root);
		cout << endl;
	}

	void _InOrder(Node* root)
	{
    
    
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

	// 根节点->当前节点这条路径的黑色节点的数量
	bool Check(Node* root, int blacknum, const int refVal)  
	{
    
    
		if (root == nullptr)  //到根部看看当前路径黑色节点和标准值是否一致
		{
    
    
			//cout << balcknum << endl;
			if (blacknum != refVal)
			{
    
    
				cout << "存在黑色节点数量不相等的路径" << endl;
				return false;
			}

			return true;
		}

		/检查子比较复杂,可以反过来去检查红节点父是否为黑色
		if (root->_col == RED && root->_parent->_col == RED)  
		{
    
    
			cout << "有连续的红色节点" << endl;

			return false;
		}

		if (root->_col == BLACK)
		{
    
    
			++blacknum;  //为黑节点加一
		}

		return Check(root->_left, blacknum, refVal)
			&& Check(root->_right, blacknum, refVal);
	}

	bool IsBalance()
	{
    
    
		if (_root == nullptr)
			return true;

		if (_root->_col == RED)
			return false;

		//参考值,即先算出一条路径的黑色节点数
		int refVal = 0;
		Node* cur = _root;
		while (cur)
		{
    
    
			if (cur->_col == BLACK)
			{
    
    
				++refVal;
			}

			cur = cur->_left;
		}

		int blacknum = 0;
		return Check(_root, blacknum, refVal);
	}


	int Height()
	{
    
    
		return _Height(_root);
	}

	int _Height(Node* root)  //求高度的
	{
    
    
		if (root == nullptr)
			return 0;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}


	Node* Find(K key)
	{
    
    
		return _Find(key, _root);
	}

	Node* _Find(K key, Node* root)
	{
    
    
		if (root == nullptr)
			return nullptr;
		if (key > root->_kv.first) //在右子树
		{
    
    
			return _Find(key, root->_right);
		}
		else if (key < root->_kv.first) //在左子树
		{
    
    
			return _Find(key, root->_left);
		}
		else  //找到了
		{
    
    
			return root;
		}
	}

private:
	Node* _root = nullptr;

	void RotateL(Node* parent)  //左单旋,rotate->旋转
	{
    
    
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;  //这个有可能为空
		Node* ppnode = parent->_parent;  //原来父亲的父亲

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

		SubR->_left = parent;
		parent->_parent = SubR;

		if (ppnode == nullptr)  //旋转的是整颗树
		{
    
    
			_root = SubR;
			SubR->_parent = nullptr;
		}
		else  //旋转的是部分
		{
    
    
			if (ppnode->_left == parent) //是左子树
			{
    
    
				ppnode->_left = SubR;
			}
			else  //是右子树
			{
    
    
				ppnode->_right = SubR;
			}
			SubR->_parent = ppnode;
		}
	}

	void RotateR(Node* parent)  //右单旋细节处理和左单旋差不多
	{
    
    
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;  //这个有可能为空
		Node* ppnode = parent->_parent;

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

		SubL->_right = parent;
		parent->_parent = SubL;

		if (ppnode == nullptr)  //旋转的是整颗树
		{
    
    
			_root = SubL;
			SubL->_parent = nullptr;
		}
		else  //旋转部分
		{
    
    
			if (ppnode->_left == parent)  //是左子树
			{
    
    
				ppnode->_left = SubL;
			}
			else  //右子树
			{
    
    
				ppnode->_right = SubL;
			}
			SubL->_parent = ppnode;
		}
	}
};

おすすめ

転載: blog.csdn.net/2301_76269963/article/details/134450719
おすすめ