赤黒木の性質とノード定義と挿入のコード実装の深い理解(写真とテキストによる詳細な説明)

赤黒い木

コンセプト

赤黒木は二分探索木ですが、ノードの色 (赤または黒) を示すために各ノードにストレージ ビットが追加されます。ルートからリーフまでのいずれか 1 つのパスの各ノードの色付けを制限することにより、赤黒ツリーは、他のパスの 2 倍の長さのパスがないことを保証しバランスに近い(厳密にバランスが取れていない) ようにします。

自然

プロパティ 3 と 4 が最も重要であり、プロパティ 5 は無視できます

  1. 各ノードは、赤または黒のいずれかです。
  2. ルート ノードは黒です。
  3. 連続する赤のノードはなく、ノードが赤の場合、その 2 つの子は black ですただし、ノードは黒であり、その子は黒にすることができます。
  4. ノードごとに、ノードからそのすべての子孫リーフ ノードへの単純なパスには、同じ数の黒いノードが含まれます
  5. 各 NIL ノード/葉ノード/空のノードは黒で、葉ノード == null ノード== NIL ノードです。

上記のプロパティを満たす赤黒木は、最長パスのノード数が最短パスのノード数の 2 倍を超えないことを保証できます。

Shortest path : 極端な場合、すべて黒のパス、またはパスが通過する黒のノードの数です。

最長パス: 1 つの黒と 1 つの赤のパス。

赤黒木がバランスに近いという概念について話し合う

最悪の場合:左右のバランスが極端に悪い場合、つまりサブツリーが黒と赤の場合が最悪2 log 2 N 2log_2N2ログ_ _2N、他のサブツリーはすべて黒ですlog 2 N log_2Nログ_ _2

最適な状況: 左右のバランスが最適です。つまり、すべてが黒であるか、各パスが黒と赤で、木が完全な二分木に近い場合です。

コード

ノード定義

ノードの定義では、ノードのデフォルトの色は赤です。

理由: すべての新しいノードが黒の場合、規則 4 (すべてのパスの黒ノードの数が同じであるという規則) に違反している必要があり、新しいノードが赤であるたびに、そのノードの親ノードが必ずしも赤であるとは限りません。 、黒かもしれないのでルール3(赤いノードが連続しない)に違反している可能性があるので、デフォルトの色は赤です。

enum Color { RED, BLACK };

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& kv, Color color = RED)
		: _kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _color(color)
	{}

	RBTreeNode<K, V>* _left;  // 节点的左孩子
	RBTreeNode<K, V>* _right; // 节点的右孩子
	RBTreeNode<K, V>* _parent;// 节点的双亲
	KV _kv;				// 节点的值
	Color _color;		// 节点的默认颜色给成红色的
};

入れる

二分探索木法に従って新しいノードを挿入し、新しいノードの挿入後に赤黒木の性質が損なわれているかどうかを確認し、ルールが満たされている場合は直接終了します。ルールが満たされていない場合は、ノードの色を更新する必要があります。赤黒木は、赤黒木に関する規則の要件を満たすように回転され、色付けされます。

規則: cur は現在のノード、p は親ノード、g は祖父母ノード、u は叔父ノードです。

//父节点
parent = cur->_parent;
//祖父节点
grandparent = parent->_parent;
//叔叔节点
if(grandparent->left == parent)
    uncle = grandparent->right;
else
    uncle = grandparent->left;

変色3例

ケース 1: p は赤、g は黒、cur は赤、u は存在し、赤です。[p は赤、g は黒、cur は赤 ==> while ループは親が存在し、親の色が赤であると判断します。まず、親が赤の場合、祖父母が存在し、黒でなければならないことを意味します (ルール 2 + ルール 3); 次に、新しく追加されたノードの色はデフォルトで赤です]

解決:

  • p、u を黒に、g を赤に変更します。
  • 次に、g と cur を更新し、cur に元の g を与え、g を cur の親に与え、上方調整を続けます。

1 p red g black u red cur, p, g 一直線に
1 p red g black u red cur, p, g 破線

ケース 2: cur、p、および g が直線上にある (2.1 p は g の左の子、cur は p の左の子、または 2.2 p は g の右の子、cur は p の右の子)、 p は赤、g は黒、cur は赤、u は存在しない、または u は存在するが黒です。[p は赤、g は黒、cur は赤 ==> while ループは親が存在し、親の色が赤であると判断します。まず、親が赤の場合、それは祖父母が存在し、黒でなければならないことを意味します (ルール 2 + ルール 3); 次に、新しく追加されたノードのデフォルトの色を赤にします。u が存在しないか、u が存在して黒である ==> while ループ内で判断する]

解決:

  • 2.1 p が g の左の子で、cur が p の左の子である場合、g ノードで右に 1 回回転すると、p が黒になり、g が赤になります。
  • 2.2 p が g の右の子で、cur が p の右の子である場合、g ノードで左に 1 回回転すると、p が黒になり、g が赤になります。
  • 次に、g と cur を更新し、cur に元の g を与え、g を cur の親に与え、上方調整を続けます。

2 p 赤 g 黒 u 黒 or カールなし、p、g 直線

ケース 3: cur、p、および g が破線上にある (3.1 p は g の右の子、cur は p の左の子、または 3.2 p は g の左の子、cur は p の右の子)、 p は赤、g は黒、cur は赤、u は存在しない、または u は存在するが黒です。[p は赤、g は黒、cur は赤 ==> while ループは親が存在し、親の色が赤であると判断します。まず、親が赤の場合、それは祖父母が存在し、黒でなければならないことを意味します (ルール 2 + ルール 3); 次に、新しく追加されたノードのデフォルトの色を赤にします。u が存在しないか、u が存在して黒である ==> while ループ内で判断する]

3 p 赤 g 黒 u 黒 or 破線にcur, p, gがない

解決:

  • 3.1 p が g の左の子で、cur が p の右の子である場合、p ノードで左に 1 回回転し、g ノードで右に 1 回回転すると、cur が黒になり、g が赤になります。
  • 3.2 p が g の右の子で、cur が p の左の子である場合、p ノードで右に 1 回回転し、g ノードで左に 1 回回転すると、cur が黒になり、g が赤になります。
  • 次に、g と cur を更新し、cur に元の g を与え、g を cur の親に与え、上方調整を続けます。
bool Insert(const std::pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_color = BLACK;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		// 1、找到合适的插入点
		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;
			}
		}
		// 2、开始插入以及更新节点颜色
		cur = new Node(kv);
		cur->_color = RED;//新插入的节点给红色
		//比父节点的值大,就插在右边
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		//比父节点的值小,就插在左边
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		// 3、插入成功,更新节点颜色
		// 3.1 若父亲颜色为黑色,结束退出	
		// 3.2 若父亲颜色为红色,开始调整颜色
		while(parent && RED == parent->_color)
			//这个循环条件保证了grandparent一定存在,因为parent是红色,而根节点必须为黑色
			// 因为parent存在,且不是黑色节点,则parent一定不是根,则其一定有双亲
		{
			Node* grandparent = parent->_parent;
			//情况1和x.1
			if (grandparent->_left == parent)
			{
				Node* uncle = grandparent->_right;
				// 情况1: p为红,g为黑,cur为红,u存在且为红
				if (uncle && uncle->_color == RED)
				{
					//将p、u改为黑,g改为红
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandparent->_color = RED;
					//更新cur节点
					cur = grandparent;
					parent = cur->_parent;
				}
				//情况2、情况3
				else
				{
					//情况2.1
					if (cur == parent->_left)
					{
						RotateR(grandparent);

						parent->_color = BLACK;
						grandparent->_color = RED;
					}
					//情况3.1
					else
					{
						RotateL(parent);
						RotateR(grandparent);

						cur->_color = BLACK;
						grandparent->_color = RED;
					}
					break;
				}
			}
			情况1和x.2
			else
			{
				Node* uncle = grandparent->_left;
				// 情况1: p为红,g为黑,cur为红,u存在且为红
				if (uncle && uncle->_color == RED)
				{
					//将p、u改为黑,g改为红
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandparent->_color = RED;
					//更新cur节点
					cur = grandparent;
					parent = cur->_parent;
				}
				//情况2、情况3
				else
				{
					//情况2.2
					if (cur == parent->_right)
					{
						RotateL(grandparent);

						parent->_color = BLACK;
						grandparent->_color = RED;
					}
					//情况3.2
					else
					{
						RotateR(parent);
						RotateL(grandparent);

						cur->_color = BLACK;
						grandparent->_color = RED;
					}
					break;
				}
			}			
		}
		_root->_color = BLACK;//在修改grandparent的颜色时,有可能改掉了根的颜色,再循环外要改为黑色
		
		return true;
	}

パフォーマンス

時間の複雑さだけでも、AVL ツリーは赤黒ツリーよりも優れています。ただし、AVL ツリーはローテーションによってバランスを調整することで多くのパフォーマンスを消費しますが、赤黒ツリーは厳密なバランスを必要としないため、ローテーションによるパフォーマンスの消費が抑えられます。

おすすめ

転載: blog.csdn.net/m0_61780496/article/details/129639147