根据Java的TreeMap源码的原理编写C++红黑树删除操作

news2024/11/22 10:57:04

(一)了解二叉搜索树的删除操作

删除操作总结:

******普通结点删除:*******

①删除叶结点

②删除只有1个子结点的结点

>>如果被删除结点的左子树为空,则令其右子树子承父业代替其位置即可

>>如果被删除结点的右子树为空,则令其左子树子承父业代替其位置即可

③删除一个有2个子结点的结点(找前驱/后继来替换待被删除的结点的值,然后判断前驱/后继是否有左右孩子,若有孩子,需要判断孩子存在,删除该替换结点需要进行的操作)

*******特殊情况:*******

④根结点无左右子树的情况

⑤删除只有一个子结点的根结点

 (二)探讨红黑树与2-3-4树之间的等价关系,以及如何转换。

根据2-3-4树进行删除操作可以分为三种情况:

情况一:自己能搞定的,对应叶子节点应该是3节点和4节点

情况二:自己搞不定的,需要跟兄弟借,找父亲借,父亲下来,然后兄弟我一个人去代替父亲当家,(找兄弟借,兄弟有的借.分2种情况: 兄弟节点本来就是3节点,或者4节点的情况)

情况三:找兄弟借,兄弟没得借 

问题思考:什么叫做兄弟结点呢?什么时真正的兄弟结点呢?什么叫做有得借呢?

兄弟结点:此文所指的兄弟结点是指待删除结点在2-3-4树上能找到兄弟结点

真正的兄弟结点:待删除结点在2-3-4树上找到的兄弟结点,有可能不是待删除结点在红黑树上能过找到的兄弟结点,此时需要对红黑树进行调整,才能够找到对应2-3-4树上找到的兄弟结点。

兄弟有得借:是指该兄弟结点存在红色的子结点,可以被借走。

兄弟没得借:若该兄弟无左右孩子或者左右孩子的颜色都为黑色,则无法被借走,此为兄弟没得借情况。

情况一:自己能搞定的,对应叶子节点应该是3节点和4节点

》》详细分析上图

                                               红黑树与2-3-4树的等价关系图

要对一棵红黑树进行结点删除,比如删除4结点,我们可以找到4的前驱/后继,这里找4的后继5作为替代结点,可以替换掉4结点的值。根据二叉搜索树的删除规则可知,由于5结点没有左右孩子,本可以直接删除,但是在一棵红黑树中,5结点的颜色为黑色,直接删除将会导致红黑树不平衡。此时我们需要对红黑树进行调整,然后再将其删除掉。【大致思路,后续会详细讲解各种情况】

如何解决删除结点会导致红黑树不平衡呢?我们可以从2-3-4树上找到解决思路,我们可以发现在2-3-4树,3节点和4节点里面删除一个元素是合法的,所以红黑树需要调整到与之对应的2-3-4树,此时进行删除操作就可以使得红黑树平衡。

结论:红黑树上面的删除结点一定是2-3-4树上的叶子结点

情况一详解:

      

红黑树中的0结点、7.5结点、9结点、11结点都可以直接被删除,因为它的颜色为红色且为叶子结点,删除后不会影响红黑树的平衡。从二叉搜索树的删除操作中来看这属于删除叶子结点的情况。从删除2-3-4树上的视角上看,是满足3节点和4节点里面删除一个元素是合法的。

      

红黑树中的1结点、7结点都有一个孩子结点且其为红色,所以可以将其孩子结点的值赋值给待删除的结点的值,然后将其颜色设置为黑色,这样红黑树依然保持平衡。从二叉搜索树的删除操作中来看这属于删除只有1个子结点的结点的情况。从删除2-3-4树上的视角上看,是满足3节点和4节点里面删除一个元素是合法的。

       

红黑树中的6结点是有左孩子和右孩子的结点且颜色为黑色,在二叉树搜索树中,删除一个有两个子结点的结点,需要找其前驱/后继的值进行替换,然后再把前驱/后继可能存在的子树挂在相应的结点上,最后再把前驱/后继删除。此处红黑树中的6结点的后继是7结点,我们发现7结点是有一个右孩子7.5且颜色为红色,所以可以将7.5结点的值赋值给7结点并设置颜色为黑色,删除掉原先的7.5结点。从删除2-3-4树上的视角上看,是满足3节点和4节点里面删除一个元素是合法的。

        

红黑树中8结点是有左孩子和右孩子的结点且颜色为红色,在二叉树搜索树中,删除一个有两个子结点的结点,需要找其前驱/后继的值进行替换,然后再把前驱/后继可能存在的子树挂在相应的结点上,最后再把前驱/后继删除。此处红黑树中的8结点的后继是9结点,我们发现9结点是叶子结点且为红色,所以可以将9结点的值赋值给8结点,颜色设置为红色,删除掉原先的9结点。从删除2-3-4树上的视角上看,是满足3节点和4节点里面删除一个元素是合法的。

>>核心部分代码: 

//情况一、替代节点是红色,则直接染黑,补偿删除的黑色节点,这样红黑树依然保持平衡
x->color = BLACK;

情况二:自己搞不定的,需要跟兄弟借,找父亲借,父亲下来,然后兄弟我一个人去代替父亲当家,(找兄弟借,兄弟有的借.分2种情况: 兄弟节点本来就是3节点,或者4节点的情况) 

》》兄弟节点本来就是3节点的情况

》》详细分析上图

例如删除5结点:

由于5结点是黑色的叶子结点,直接删除会破坏红黑树的平衡,所以属于自己搞不定,需要跟兄弟借。在2-3-4树上来看,5结点的兄弟结点是7,可以向其借子结点6.5来实现删除后调整。 但是在红黑树上来看,5结点的兄弟结点是8且颜色为红色。当红黑树上的兄弟结点颜色为红色,则说明其不是真正的兄弟结点,因为其在2-3-4树上的兄弟结点与其在红黑树上的兄弟结点的值并不一样,所以需要进行调整来寻找其真正的兄弟结点。

 

(一)兄弟节点本来就是3节点

>>寻找真正的兄弟结点

2-3-4树对应红黑树来看的话,首先先把6结点和8结点的颜色进行调整,6结点的颜色设置为红色,8结点的颜色设置为黑色,在红黑树看来,此时5结点可以方便找到真正的兄弟结点。

             

                                  图1                                             图2

需要将图1的2-3-4树转为图2的2-3-4树,在图1中等价关系的红黑树中,将6结点的颜色设置为红色,将8结点设置为黑色, 由于5结点的兄弟结点是8,其是6结点的右孩子,所以需要对其进行“左旋”,便可找到真正的兄弟结点。

所以得到如下等价的红黑树和2-3-4树,5结点找到真正的兄弟结点7之后,发现7的左孩子是颜色为红色的6.5结点,可以向其借走子结点来实现红黑树删除平衡。

接下来进行颜色调整,将5结点的真正兄弟结点7的颜色设置为红色,将7结点的左孩子的颜色设置为黑色,然后进行右旋操作。继续再进行颜色调整,将6.5结点的颜色设置为父亲结点6的颜色,然后将父亲结点6和右孩子结点7设置为黑色,再进行“右旋操作”,最后把结点5删除掉,可以得到一棵平衡的红黑树。

 》》兄弟节点本来就是4节点的情况

  (二)兄弟节点本来就是4节点(借1个结点)

2-3-4树中4节点情况:(借走一个子结点)

        

我们可以看到这里的操作和3节点情况一样,都是需要找到被删除结点5结点的真正兄弟结点,然后对其进行变色->右旋->变色->左旋,这里的红黑树调整需要进行两次旋转。接下来会讲解只用一次旋转的方案,即向2-3-4树中的4节点借走两个子结点:

 

 (二)兄弟节点本来就是4节点(借2个结点)

2-3-4树中4节点情况:(借走两个子结点)

  

需要找到被删除结点5结点的真正兄弟结点,然后对其进行变色->左旋,这里的红黑树调整需要进行一次旋转。相比借走1个节点,借走2个节点可以减少一次旋转操作,可以提升性能,也能使得红黑树删除平衡。

>>核心部分代码:

//情况二,找兄弟借,兄弟有的借(x是左孩子的情况)
else {
	//分2种小情况:兄弟节点本来是3节点或者是4节点的情况
	if (colorOf(rnode->right) == BLACK) {
	//if (rnode->right == nullptr) {
		rnode->left->color = BLACK;
		rnode->color = RED;
		rightRotate(rnode);
		rnode = x->parent->right;
	}
	rnode->color = x->parent->color;
	x->parent->color = BLACK;
	if(rnode->right)
		rnode->right->color = BLACK;
	leftRotate(x->parent);
	x = root;
}

//情况二,找兄弟借,兄弟有的借(x是右孩子的情况)
else {
	//分2种小情况:兄弟节点本来是3节点或者是4节点的情况
	if (colorOf(lnode->left) == BLACK) {
	//if (lnode->left == nullptr) {
		lnode->right->color = BLACK;
		lnode->color = RED;
		leftRotate(lnode);
		lnode = x->parent->left;
	}
	lnode->color = x->parent->color;
	x->parent->color = BLACK;
	if(lnode->left)
		lnode->left->color = BLACK;
	rightRotate(x->parent);
	x = root;
}

情况三:找兄弟借,兄弟没得借 

>>核心部分代码: 

//情况三,找兄弟借,兄弟没得借 (x是左孩子)
if (colorOf(rnode->left) == BLACK && colorOf(rnode->right) == BLACK){
	//情况复杂,暂时不写
	rnode->color = RED;
	x = x->parent;
}
//情况三,找兄弟借,兄弟没得借(x是右孩子)
if(colorOf(lnode->left) == BLACK && colorOf(lnode->right) == BLACK){
	//情况复杂,暂时不写
	lnode->color = RED;
	x = x->parent;
}

>> 红黑树删除核心代码:

1、找前驱代码:

/*
		void predecessor(Node* node)
		找到指定结点的前驱结点,即找小于node结点的最大值
	*/
	Node* predecessor(Node* node) {
		if (node == nullptr) {
			return nullptr;
		}
		else if (node->left != nullptr) {
			Node* p = node->left;
			while (p->right != nullptr) {
				p = p->right;
			}
			return p;
		}
		else {
			Node* p = node->parent;
			Node* cur = node;
			while (p != nullptr && cur == p->left) {
				cur = p;
				p = p->parent;
			}
			return p;
		}
	}

2、找后继代码

/*
		void successor(Node* node)
		找后继结点:即大于结点的最小值
	*/
	Node* successor(Node* node) {
		if (node == nullptr) {
			return nullptr;
		}
		else if (node->right != nullptr) {
			Node* p = node->right;
			while (p->left != nullptr) {
				p = p->left;
			}
			return p;
		}
		else {
			Node* p = node->parent;
			Node* cur = node;
			while (p != nullptr && cur == p->right) {
				cur = p;
				p = p->parent;
			}
			return p;
		}
	}

3、根据key、value获取节点

Node* getNode(const pair<k, v> kv) {
	Node* node = this->root;
	while (node != nullptr) {
		//int cmp = key.compareTo((K)node.key);
		int cmp = compareTo(kv, node);
		if (cmp < 0) {
			node = node->left;
		}
		else if (cmp > 0) {
			node = node->right;
		}
		else
			return node;
	}
	return nullptr;
}

4、根据key、value进行删除操作

v remove(const pair<k, v> kv) {
	Node* node = getNode(kv);
	if (node == nullptr) {
		return "";
	}
	//V oldValue = (V)node.value;
	v oldValue = kv.second;
	deleteNode(node);
	return oldValue;
}

5、删除结点

/**
	 * 删除操作:
	 * 1、删除叶子结点,直接删除
	 * 2、删除的结点有一个子结点,那么用子结点来替代
	 * 3、如果删除的节点有2个子结点,此时需要找到前驱结点或者后继结点来替代
	 *
	 */
	void deleteNode(Node* node) {
		//3、node结点有2个孩子【即node是度为2的结点】
		if (node->left != nullptr && node->right != nullptr) {
			//后继结点替代
			Node* replaceNode = successor(node);
			node->kv.first = replaceNode->kv.first;
			node->kv.second = replaceNode->kv.second;
			node = replaceNode;
		}

		//node的孩子结点是度为0或1的结点
		Node* replacement = node->left != nullptr ? node->left : node->right;
		//2、替代结点不为空
		if (replacement != nullptr) {
			//替代者的父指针指向的原来node的父亲
			replacement->parent = node->parent;
			//node是根节点
			if (node->parent == nullptr) {
				root = replacement;
			}
			//node是左孩子,所以替代者依然是左孩子
			else if (node == node->parent->left) {
				node->parent->left = replacement;
			}
			//node是右孩子,所以替代者依然是右孩子
			else {
				node->parent->right = replacement;
			}
			//将node的左右孩子指针和父指针都指向null(此时node处于游离状态,等待垃圾回收)
			node->left = node->right = node->parent = nullptr;

			//替换完之后需要调整平衡
			if (node->color == BLACK) {
				//需要调整,这种情况一定是红色(替代节点一定是红色,此时只要变色)
				fixAfterRemove(replacement);
			}
			delete node;
		}
		//删除结点就是根结点
		else if (node->parent == nullptr) {
			root = nullptr;
		}
		//1、node结点是叶子结点,replacement为nullptr
		else {
			//先调整
			if (node->color == BLACK) {
				fixAfterRemove(node);
			}
			//再删除
			if (node->parent != nullptr) {
				if (node == node->parent->left) {
					node->parent->left = nullptr;
				}

				else if (node == node->parent->right) {
					node->parent->right = nullptr;
				}
				node->parent = nullptr;
				delete node;
			}
		}
	}

6、完整代码:

/*
	1、2-3-4树:新增元素+2节点合并(节点中只有1个元素)=3节点(节点中有2个元素)
		红黑树:新增一个红色节点+黑色父亲节点=上黑下红(2节点)--------------不要调整
	2、2-3-4树:新增元素+3节点合并(节点中有2个元素)=4节点(节点中有3个元素)
		这里有6种情况(左3,右3,还有2个左中右不需要调整)----------------左3,右3需要调整,其余2个不需要调整
		红黑树:新增红色节点+上黑下红=排序后中间节点是黑色,两边节点都是红色(3节点)
	3、2-3-4树:新增一个元素+4节点合并=原来的4节点分裂,中间元素升级为父节点,新增元素与剩下的其中一个合并
		红黑树:新增红色节点+爷爷节点黑,父节点和叔叔节点都是红色=爷爷节点变红,父亲和叔叔变黑,如果爷爷是根节点,则再变黑

*/
#include<iostream>
using namespace std;
#include<vector>
#include<cmath>
#include<string>
#include<vector>
#define N 100
//#include "StringBuilder.h"

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 color;
	RBTreeNode(const pair<k, v>& kv)
		:left(nullptr)
		, right(nullptr)
		, parent(nullptr)
		, kv(kv)
		, color(RED)
	{}
};

template<class k, class v>
class RBTree {
	typedef RBTreeNode<k, v> Node;
public:

	RBTree()
		:root(nullptr)
	{}
	//插入结点
	bool Insert(const pair<k, v>& kv) {
		if (root == nullptr) {
			root = new Node(kv);
			root->color = BLACK;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = root;
		int res;
		while (cur) {
			res = this->compareTo(kv, cur);
			if (res == 1) {
				parent = cur;
				cur = cur->right;
			}
			else if (res == -1) {
				parent = cur;
				cur = cur->left;
			}
			else {
				return false;
			}
		}
		cur = new Node(kv);
		cur->color = RED;//新增节点
		res = this->compareTo(kv, parent);
		//如果比较最终落在右子树,则直接将父节点右指针指向cur
		if (res == 1) {
			parent->right = cur;
			cur->parent = parent;
		}
		//如果比较最终落在左子树,则直接将父节点左指针指向cur
		else {
			parent->left = cur;
			cur->parent = parent;
		}
		//调整
		fixAfterPut(parent, cur);
		root->color = BLACK;
		return true;
	}

	void fixAfterPut(Node* p, Node* cur) {
		while (p && p->color == RED) {
			Node* grandfather = p->parent;
			if (p == grandfather->left) {
				Node* uncle = grandfather->right;
				//1、uncle存在且为红
				if (uncle && uncle->color == RED) {
					/* 变色 + 继续向上处理 */
					//①变色
					p->color = uncle->color = BLACK;
					grandfather->color = RED;

					//②继续向上处理
					cur = grandfather;
					p = cur->parent;
				}
				else {//2、uncle不存在/存在且为黑 
					if (cur == p->left) {
						//单旋
						rightRotate(grandfather);
						p->color = BLACK;
						grandfather->color = RED;
					}
					else {
						//双旋
						leftRotate(p);
						rightRotate(grandfather);
						cur->color = BLACK;
						grandfather->color = RED;
					}
					break;
				}
			}
			else {
				Node* uncle = grandfather->left;
				//1、uncle存在且为红
				if (uncle && uncle->color == RED) {
					/* 变色 + 继续向上处理 */
					//①变色
					p->color = uncle->color = BLACK;
					grandfather->color = RED;

					//②继续向上处理
					cur = grandfather;
					p = cur->parent;
				}
				else {//2、uncle不存在/存在且为黑 
					if (cur == p->right) {
						//单旋
						leftRotate(grandfather);
						p->color = BLACK;
						grandfather->color = RED;
					}
					else {
						//双旋
						rightRotate(p);
						leftRotate(grandfather);
						cur->color = BLACK;
						grandfather->color = RED;
					}
					break;
				}
			}
		}
	}

	int compareTo(const pair<k, v> kv, Node* cur) {
		if (kv.first < cur->kv.first)
			return -1;
		else if (kv.first > cur->kv.first)
			return 1;
		else
			return 0;
	}


	//左旋
	/*
		》》围绕p左旋:

		①p是左孩子
			  pf                       pf
			  /                        /
			 p                       pr(r)
			/ \						 / \
		  pl   pr(r)      ==>       p   rr(r)
			   / \                 / \
			  rl  rr			  pl  rl

		②p是右孩子

			pf                       pf
			 \                        \
			 p                       pr(r)
			/ \						 / \
		  pl   pr(r)      ==>       p   rr(r)
			   / \                 / \
			  rl  rr			  pl  rl

	*/
	void leftRotate(Node* p) {
		if (p) {
			Node* r = p->right;
			Node* rl = r->left;
			p->right = rl;
			if (rl) {
				rl->parent = p;
			}
			Node* parentParent = p->parent;
			//(双向)
			r->left = p;//p是r的左孩子
			p->parent = r;//r是p的父亲

			if (p == root) {//若p是根结点
				root = r;
				r->parent = nullptr;
			}
			else {
				if (parentParent->left == p) { //p是左孩子
					parentParent->left = r;
				}
				else { //p是右孩子
					parentParent->right = r;
				}
				r->parent = parentParent;
			}
		}
	}
	//右旋
	/*
		》》围绕p右旋:

		①p是左孩子

			pf                         pf
			/                          /
		   p						  pl(l)
		  / \           ==》         / \
	   pl(l) pr                     ll   p
		/ \                             / \
	   ll  lr                          lr  pr

		②p是右孩子

		   pf                       pf
			\                        \
			 p                       pl(l)
			/ \			 ==>		  / \
		 pl(l) pr					 ll  p
		  / \                           / \
		 ll  lr			               lr  pr

	*/
	void rightRotate(Node* p) {
		if (p) {
			Node* l = p->left;
			Node* lr = l->right;
			p->left = lr;
			if (lr) {
				lr->parent = p;
			}
			Node* parentParent = p->parent;
			l->right = p;
			p->parent = l;

			if (p == root) {
				root = l;
				root->parent = nullptr;
			}
			else {
				if (parentParent->left == p) {
					parentParent->left = l;
				}
				else {
					parentParent->right = l;
				}
				l->parent = parentParent;
			}
		}
	}
	/*
	void leftRotate(Node* p) {
		if (p != nullptr) {
			Node* r = p->right;
			p->right = r->left;
			if (r->left != nullptr) {
				r->left->parent = p;
			}
			r->parent = p->parent;
			if (p->parent == nullptr) {
				root = r;
			}
			else if (p->parent->left == p) {
				p->parent->left = r;
			}
			else {
				p->parent->right = r;
			}
			r->left = p;
			p->parent = r;
		}
	}
	void rightRotate(Node* p) {
		if (p != nullptr) {
			Node* l = p->left;
			p->left = l->right;
			if (l->right != nullptr) {
				l->right->parent = p;
			}
			l->parent = p->parent;
			if (p->parent == nullptr) {
				root = l;
			}
			else if (p->parent->right == p) {
				p->parent->right = l;
			}
			else {
				p->parent->left = l;
			}
			l->right = p;
			p->parent = l;
		}
	}
	*/
	void InOrder()
	{
		_InOrder(root);
	}

	//中序遍历
	void _InOrder(Node* root) {
		if (root == nullptr) {
			return;
		}
		_InOrder(root->left);
		cout << root->kv.first << ":" << root->kv.second << endl;
		_InOrder(root->right);
	}


	/*
		检测是否满足红黑树的性质:任意选取一条路径找出黑色结点的个数作为基准值,然后依次与其他
		路径比较,如果都相同则满足红黑树的性质。
	*/
	bool IsBalance() {
		if (root && root->color == RED) {
			cout << "根结点不是黑色" << endl;//为了测试
			return false;
		}
		//最左路径黑色结点数量做基准值
		int banchmark = 0;//保存最左侧路径中黑色结点的个数
		Node* left = root;
		while (left) {
			if (left->color == BLACK)
				++banchmark;
			left = left->left;
		}
		int blackNum = 0;//递归遍历时记录黑色结点的个数
		return _IsBalance(root, banchmark, blackNum);
	}

	bool _IsBalance(Node* root, int banchmark, int blackNum)
	{
		//走到null之后,判断banchmark和blackNum是否相等
		if (root == nullptr)
		{
			if (banchmark != blackNum)
			{
				cout << "存在路径黑色结点的数量不相等" << endl;//为了测试
				return false;
			}
			return true;
		}
		if (root->color == RED && root->parent->color == RED)
		{
			cout << "出现了连续的红色结点" << endl;//为了测试
			return false;
		}
		if (root->color == BLACK)// 统计黑色节点的个数
		{
			++blackNum;
		}
		return _IsBalance(root->left, banchmark, blackNum)
			&& _IsBalance(root->right, banchmark, blackNum);
	}
	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* getRoot() {
		return this->root;
	}


	/*
		void predecessor(Node* node)
		找到指定结点的前驱结点,即找小于node结点的最大值
	*/
	Node* predecessor(Node* node) {
		if (node == nullptr) {
			return nullptr;
		}
		else if (node->left != nullptr) {
			Node* p = node->left;
			while (p->right != nullptr) {
				p = p->right;
			}
			return p;
		}
		else {
			Node* p = node->parent;
			Node* cur = node;
			while (p != nullptr && cur == p->left) {
				cur = p;
				p = p->parent;
			}
			return p;
		}
	}

	/*
		void successor(Node* node)
		找后继结点:即大于结点的最小值
	*/
	Node* successor(Node* node) {
		if (node == nullptr) {
			return nullptr;
		}
		else if (node->right != nullptr) {
			Node* p = node->right;
			while (p->left != nullptr) {
				p = p->left;
			}
			return p;
		}
		else {
			Node* p = node->parent;
			Node* cur = node;
			while (p != nullptr && cur == p->right) {
				cur = p;
				p = p->parent;
			}
			return p;
		}
	}

	Node* getNode(const pair<k, v> kv) {
		Node* node = this->root;
		while (node != nullptr) {
			//int cmp = key.compareTo((K)node.key);
			int cmp = compareTo(kv, node);
			if (cmp < 0) {
				node = node->left;
			}
			else if (cmp > 0) {
				node = node->right;
			}
			else
				return node;
		}
		return nullptr;
	}

	v remove(const pair<k, v> kv) {
		Node* node = getNode(kv);
		if (node == nullptr) {
			return "";
		}
		//V oldValue = (V)node.value;
		v oldValue = kv.second;
		deleteNode(node);
		return oldValue;
	}

	/**
	 * 删除操作:
	 * 1、删除叶子结点,直接删除
	 * 2、删除的结点有一个子结点,那么用子结点来替代
	 * 3、如果删除的节点有2个子结点,此时需要找到前驱结点或者后继结点来替代
	 *
	 */
	void deleteNode(Node* node) {
		//3、node结点有2个孩子【即node是度为2的结点】
		if (node->left != nullptr && node->right != nullptr) {
			//后继结点替代
			Node* replaceNode = successor(node);
			node->kv.first = replaceNode->kv.first;
			node->kv.second = replaceNode->kv.second;
			node = replaceNode;
		}

		//node的孩子结点是度为0或1的结点
		Node* replacement = node->left != nullptr ? node->left : node->right;
		//2、替代结点不为空
		if (replacement != nullptr) {
			//替代者的父指针指向的原来node的父亲
			replacement->parent = node->parent;
			//node是根节点
			if (node->parent == nullptr) {
				root = replacement;
			}
			//node是左孩子,所以替代者依然是左孩子
			else if (node == node->parent->left) {
				node->parent->left = replacement;
			}
			//node是右孩子,所以替代者依然是右孩子
			else {
				node->parent->right = replacement;
			}
			//将node的左右孩子指针和父指针都指向null(此时node处于游离状态,等待垃圾回收)
			node->left = node->right = node->parent = nullptr;

			//替换完之后需要调整平衡
			if (node->color == BLACK) {
				//需要调整,这种情况一定是红色(替代节点一定是红色,此时只要变色)
				fixAfterRemove(replacement);
			}
			delete node;
		}
		//删除结点就是根结点
		else if (node->parent == nullptr) {
			root = nullptr;
		}
		//1、node结点是叶子结点,replacement为nullptr
		else {
			//先调整
			if (node->color == BLACK) {
				fixAfterRemove(node);
			}
			//再删除
			if (node->parent != nullptr) {
				if (node == node->parent->left) {
					node->parent->left = nullptr;
				}

				else if (node == node->parent->right) {
					node->parent->right = nullptr;
				}
				node->parent = nullptr;
				delete node;
			}
		}
	}
	Colour colorOf(Node* node) {
		return node == nullptr ? BLACK : node->color;
	}

	/**
	 * 删除后调整
	 */
	void fixAfterRemove(Node* x) {
		while (x != root && x->color == BLACK) {
			//x是左孩子的情况

			if (x == x->parent->left) {
				//兄弟节点
				Node* rnode = x->parent->right;

				//判断此时兄弟节点是否是真正的兄弟节点
				if (rnode->color == RED) {
					rnode->color = BLACK;
					x->parent->color = RED;
					leftRotate(x->parent);
					//找到真正的兄弟节点
					rnode = x->parent->right;
					cout << "找到真正的兄弟节点:"<<rnode->kv.first << endl;
				}
				//情况三,找兄弟借,兄弟没得借 
				//if (rnode->left == nullptr && rnode->right == nullptr || rnode->left->color==BLACK && rnode->right->color == BLACK) {
				if (colorOf(rnode->left) == BLACK && colorOf(rnode->right) == BLACK){
					//情况复杂,暂时不写
					rnode->color = RED;
					x = x->parent;
				}
				//情况二,找兄弟借,兄弟有的借
				else {
					//分2种小情况:兄弟节点本来是3节点或者是4节点的情况
					if (colorOf(rnode->right) == BLACK) {
					//if (rnode->right == nullptr) {
						rnode->left->color = BLACK;
						rnode->color = RED;
						rightRotate(rnode);
						rnode = x->parent->right;
					}
					rnode->color = x->parent->color;
					x->parent->color = BLACK;
					if(rnode->right)
						rnode->right->color = BLACK;
					leftRotate(x->parent);
					x = root;
				}
			}
			//x是右孩子的情况
			//x是右孩子的情况
			else {
				//兄弟节点
				Node* lnode = x->parent->left;
				//判断此时兄弟节点是否是真正的兄弟节点

				if (lnode->color == RED) {
					lnode->color = BLACK;
					x->parent->color = RED;
					rightRotate(x->parent);
					//找到真正的兄弟节点
					lnode = x->parent->left;
				}
				//情况三,找兄弟借,兄弟没得借
				//if (lnode->right == nullptr && lnode->left == nullptr || lnode->left->color == BLACK && lnode->right->color == BLACK)
				if(colorOf(lnode->left) == BLACK && colorOf(lnode->right) == BLACK){
					//情况复杂,暂时不写
					lnode->color = RED;
					x = x->parent;
				}
				//情况二,找兄弟借,兄弟有的借
				else {
					//分2种小情况:兄弟节点本来是3节点或者是4节点的情况
					if (colorOf(lnode->left) == BLACK) {
					//if (lnode->left == nullptr) {
						lnode->right->color = BLACK;
						lnode->color = RED;
						leftRotate(lnode);
						lnode = x->parent->left;
					}
					lnode->color = x->parent->color;
					x->parent->color = BLACK;
					if(lnode->left)
						lnode->left->color = BLACK;
					rightRotate(x->parent);
					x = root;
				}
			}
		}
		//情况一、替代节点是红色,则直接染黑,补偿删除的黑色节点,这样红黑树依然保持平衡
		x->color = BLACK;
	}
private:
	Node* root;
};

template<class k, class v>
class TreeOperation {
	typedef RBTreeNode<k, v> Node;
	/*
	树的结构示例:
			  1
			/   \
		  2       3
		 / \     / \
		4   5   6   7
	*/
	// 用于获得树的层数
public:
	int getTreeDepth(Node* root) {
		if (root == nullptr)
			return 0;
		else
		{
			int ldep = getTreeDepth(root->left);
			int rdep = getTreeDepth(root->right);
			return (ldep < rdep) ? rdep + 1 : ldep + 1;
		}
	}
	void writeArray(Node* currNode, int rowIndex, int columnIndex, string** res, int treeDepth) {
		// 保证输入的树不为空
		if (currNode == nullptr) return;
		// 0、默认无色
		//res[rowIndex][columnIndex] = String.valueOf(currNode.getValue());
		//1、颜色表示
		if (currNode->color == BLACK) {//黑色,加色后错位比较明显
			res[rowIndex][columnIndex] = ("\033[40;3m" + currNode->kv.second + "\033[0m");
			//cout << "-----------------------------------------" << res[rowIndex][columnIndex] << endl;
		}
		else {
			res[rowIndex][columnIndex] = ("\033[31;3m" + currNode->kv.second + "\033[0m");
			//cout << "=========================================" << res[rowIndex][columnIndex] << endl;
		}
		//2、R,B表示
		//res[rowIndex][columnIndex] = String.valueOf(currNode.getValue()+"-"+(currNode.isColor()?"B":"R")+"");

		// 计算当前位于树的第几层
		int currLevel = ((rowIndex + 1) / 2);
		// 若到了最后一层,则返回
		if (currLevel == treeDepth) return;
		// 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
		int gap = treeDepth - currLevel - 1;
		// 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
		if (currNode->left != nullptr) {
			res[rowIndex + 1][columnIndex - gap] = "/";
			writeArray(currNode->left, rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
		}

		// 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
		if (currNode->right != nullptr) {
			res[rowIndex + 1][columnIndex + gap] = "\\";
			writeArray(currNode->right, rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
		}
	}
	void show(Node* root) {
		if (root == nullptr) printf("EMPTY!");
		//printf("==============");
		// 得到树的深度
		int treeDepth = getTreeDepth(root);

		// 最后一行的宽度为2的(n - 1)次方乘3,再加1
		// 作为整个二维数组的宽度
		int arrayHeight = treeDepth * 2 - 1;
		int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
		/*printf("==============%d \n", arrayHeight);
		printf("==============%d \n", arrayWidth);*/

		// 用一个字符串数组来存储每个位置应显示的元素
		//string res[arrayHeight][arrayWidth];

		int rows = arrayHeight;
		int cols = arrayWidth;

		//动态创建二维字符数组arr[m][n]
		string** res;
		res = new string * [arrayHeight]; //创建行指针
		for (int i = 0; i < arrayHeight; i++)
			res[i] = new string[arrayWidth]; //为每行分配空间

		// 对数组进行初始化,默认为一个空格
		for (int i = 0; i < arrayHeight; i++)
		{
			for (int j = 0; j < arrayWidth; j++)
			{
				res[i][j] = " ";
			}
		}

		// 从根节点开始,递归处理整个树
		writeArray(root, 0, arrayWidth / 2, res, treeDepth);

		for (int i = 0; i < arrayHeight; i++)
		{
			vector<string> strVector = {};
			for (int j = 0; j < arrayWidth; j++) {
				cout << res[i][j];
				int tempStrLen = res[i][j].length();
				if (tempStrLen > 1 && j <= arrayHeight - 1) {
					j += tempStrLen > 4 ? 2 : tempStrLen - 1;
				}
				//strVector.push_back(res[i][j]);
				/*if (res[i][j].length() > 1 && j <= arrayHeight - 1) {
					j += res[i][j].length() > 4 ? 2 : res[i][j].length() - 1;
				}*/
			}
			cout << endl;
			/*string str="";
			for (vector<string>::const_iterator it = strVector.begin(); it != strVector.end(); it++)
			{
				str.append(*it);
			}
			cout << str << endl;*/
		}
	}
};



void insertOpt() {
	RBTree<string, string> t;
	int inputV = 0;
	string str = "";
	while (true) {
		printf("请输入你要插入的节点:");
		scanf_s("%d", &inputV);
		str = to_string(inputV);

		//这里代码最多支持3位数,3位以上的话红黑树显示太错位了,这里就不重构代码了,大家可自行重构

		if (str.length() == 1) {
			str = "00" + str;
		}
		else if (str.length() == 2) {
			str = "0" + str;
		}
		t.Insert(make_pair(str, str));
		TreeOperation<string, string> Tp;
		Tp.show(t.getRoot());
	}
}


void TestRBTree()
{
	//RBTree<int, int> t;
	RBTree<string, string> t;
	//string a[] = { "5","4","3","2","1","0"};
	//string a[] = { "16","3","7","11","9","26","18","14","15"};
	string a[] = { "4","2","6","1","3","5","15","7","16","14" };
	//string a[] = { "4","2","6","1","3","5","15","7","16","14","8","9","10","11","12","13","14"};
   //string a[] = { "6","4","10","8","3","7","9"};
	for (auto str : a)
	{
		if (str.length() == 1) {
			str = "00" + str;
		}
		else if (str.length() == 2) {
			str = "0" + str;
		}
		t.Insert(make_pair(str, str));
		//cout << "Insert" << e << ":" << t.IsBalance() << endl;
	}
	/*t.InOrder();
	cout << t.IsBalance() << endl;
	cout << t.Height() << endl;*/
	//TreeOperation<int, int> Tp;
	TreeOperation<string, string> Tp;
	Tp.show(t.getRoot());

}

void DeleteOpt() {
	RBTree<string, string> rbt;
	//string a[] = { "6","4","10","8","3","7","9" };
	//string a[] = { "16","3","7","11","9","26","18","14","15" };
	//string a[] = { "4","2","6","1","3","5","15","7","16","14","8","9","10","11","12","13" };
	//string a[] = { "4","2","8","1","3","0","7","13","11","16","10","12","15","17" };
	string a[] = { "2","1","3","4","6","8","5","7","9","10" };
	for (auto str : a)
	{
		if (str.length() == 1) {
			str = "00" + str;
		}
		else if (str.length() == 2) {
			str = "0" + str;
		}
		rbt.Insert(make_pair(str, str));
	}
	TreeOperation<string, string> Tp;
	Tp.show(rbt.getRoot());
	if (rbt.IsBalance()) {
		cout << "RIGHT!!!!这棵红黑树是平衡的" << endl;
		cout << endl;
		cout << endl;
	}
	else {
		cout << "Error!!!!这棵红黑树是不平衡的" << endl;
		cout << endl;
		cout << endl;
	}

	int inputV = 0;
	string str = "";
	while (true) {
		cout << "请输入你要删除的节点:" << endl;
		scanf_s("%d", &inputV);
		str = to_string(inputV);
		//这里代码最多支持3位数,3位以上的话红黑树显示太错位了,这里就不重构代码了,大家可自行重构
		if (str.length() == 1) {
			str = "00" + str;
		}
		else if (str.length() == 2) {
			str = "0" + str;
		}
		//1 2 3 88 66 77 100 5 4 101
		
		if (rbt.getRoot()->kv.first == str) {
			rbt.remove(make_pair(str, str));

			if (rbt.getRoot() == nullptr) {
				cout << "根结点已经删除!!!!无结点可删了,退出循环" << endl;
				break;
			}
			Tp.show(rbt.getRoot());
			if (rbt.IsBalance()) {
				cout << "RIGHT!!!!这棵红黑树是平衡的" << endl;
				cout << endl;
				cout << endl;
			}
			else {
				cout << "Error!!!!这棵红黑树是不平衡的" << endl;
				cout << endl;
				cout << endl;
			}
		}
		else {
			rbt.remove(make_pair(str, str));
			Tp.show(rbt.getRoot());
			if (rbt.IsBalance()) {
				cout << "RIGHT!!!!这棵红黑树是平衡的" << endl;
				cout << endl;
				cout << endl;
			}
			else {
				cout << "Error!!!!这棵红黑树是不平衡的" << endl;
				cout << endl;
				cout << endl;
			}
		}
	}
}

int main() {
	/*TestRBTree();
	cout << endl;
	cout << endl;
	cout << endl;*/
	//insertOpt();
	DeleteOpt();
	system("pause");
	return 0;
}

演示效果:

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/473134.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JPA整合达梦数据库

陈老老老板&#x1f9b8; &#x1f468;‍&#x1f4bb;本文专栏&#xff1a;国产数据库-达梦数据库&#xff08;主要讲一些达梦数据库相关的内容&#xff09; &#x1f468;‍&#x1f4bb;本文简述&#xff1a;本文讲一下SpringBoot整合JPA与达梦数据库&#xff0c;就是简单&…

《算法工程师带你去》读书笔记

什么是稀疏向量&#xff08;向量的稀疏表示&#xff09; 对数据进行预处理时&#xff0c;一般需要对类别型特征进行编码&#xff1a; 序号编码独热编码 二进制编码 其中独热编码用的是最多的。但是当类别数十分巨大时&#xff0c;独热编码是一个非常稀疏的向量&#xff0c;只有…

IDEA(八)常用插件推荐

目录 1.GitHub Copilot2.MyBatisCodeHelperPro3.Maven Helper4.Translation5.Api Savior6.Alibaba Java Coding Guidelines7.Sequence Diagram8.Key Promoter X9.Restfultoolkit-fix 在IDEA中&#xff0c;Ctrl Alt S 选择 Plugins&#xff0c;可以添加很多帮助我们开发的插件…

Arduino串口提取数字(整型和浮点型)

数据提取 文章目录 数据提取前言一、提取整型数据二、提取浮点型数据 前言 之前需要用32和ESP进行通信上传数据&#xff0c;一直都用的都是数据上传然后处理成整型数据&#xff0c;今天需要处理成浮点型数据所以就查了一下&#xff0c;于是就记录一下。 一、提取整型数据 #i…

〖Python网络爬虫实战⑳〗- 数据存储之CSV操作实战

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;目前专栏免费订阅&#xff0c;在转为付费专栏前订阅本专栏的&#xff0c;可以免费订阅付…

JDBC数据库连接技术学习笔记

1. 概述 本笔记是学习尚硅谷教育的23版jdbc的课后笔记 1.1 JDBC概念和理解 1. jdbc是(Java Database Connectivity)单词的缩写,翻译为java连接数据库 2. jdbc是java程序连接数据库的技术统称 3. jdbc由java语言的规范(接口)和各个数据库厂商的实现驱动(jar)组成 4. jdbc是一…

Mysql 判断语句

目录 5 判读 5.1 if ... then ...->示例演示 局部变量 -> 打包储存过程 5.2 case 5.2.1 语法一 5.2.2 语法二 -> 示例演示 5.3 while 循环 ->有条件循环 5.3.1 示例演示 5.4 repeat 循环 -> 满足条件 -> 退出循环 5.4.1 示例演示 5.5 loop 5.5.1…

【c语言小项目】基于easyX的俄罗斯方块

EeayX是针对 C/C 的简单图形库插件&#xff0c;本项目基于easyX游戏框架下实现俄罗斯方块游戏。 俄罗斯方块功能实现中主要运用了二维数组的循环遍历。能够实现基本功能&#xff0c;暂未实现旋转 c语言系列专栏&#xff1a;c语言之路重点知识整合 更多相关&#xff1a;c语…

AI VS 好莱坞?新时代电影工作流;MJ制作微信表情包的麻瓜教程;关于ControlNet的一切;AI创业真钱景 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 『OpenAI通过了一大批GPT-4申请』大量放号ing~快去看邮箱&#xff01; &#x1f916; 『小马智行 | 广州南沙区开启车内无安全员的自动…

opencv-python加载pytorch训练好的onnx格式线性回归模型

opencv是一个开源的图形库&#xff0c;有针对java,c,python的库依赖&#xff0c;它本身对模型训练支持的不好&#xff0c;但是可以加载其他框架训练的模型来进行预测。 这里举一个最简单的线性回归的例子&#xff0c;使用深度学习框架pytorch训练模型&#xff0c;最后保存模型为…

【软考备战·希赛网每日一练】2023年4月28日

文章目录 一、今日成绩二、错题总结第一题第二题第三题 三、知识查缺 题目及解析来源&#xff1a;2023年04月28日软件设计师每日一练 一、今日成绩 二、错题总结 第一题 解析&#xff1a; 大体了解即可&#xff0c;题目要考察的核心意思&#xff1a;确定的有限自动机和不确定的…

js 操作数组内容

js 操作数组内容 数组添加元素&#xff08;更改原数组&#xff09; push和unshift会返回添加了新元素的数组长度 push从数组最后加入&#xff0c;unshift从数组最前面加入 const arr ["a", "b", "c"]; arr.push("d"); //返回4…

数据结构基础day9

题目&#xff1a;187. 重复的DNA序列 解法1&#xff1a;哈希表 class Solution { public:vector<string> findRepeatedDnaSequences(string s) {vector<string> ans;unordered_map<string, int> mp;int ns.size(), L10;for(int i0; i<n-L; i){ //从开头…

【fluent UDF】warning: unused variable警报:存在未使用的变量

一、问题背景 在编译UDF时&#xff0c;出现如下错误 curing_heat_v3.c: In function ‘iter_ending’: curing_heat_v3.c:105:14: warning: unused variable ‘volume_sum’ [-Wunused-variable] real volume_sum0.0; curing_heat_v3.c:104:14: warning: unused variable ‘…

【Python零基础学习入门篇②】——第二节:Python的常用语句

⬇️⬇️⬇️⬇️⬇️⬇️ ⭐⭐⭐Hello&#xff0c;大家好呀我是陈童学哦&#xff0c;一个普通大一在校生&#xff0c;请大家多多关照呀嘿嘿&#x1f601;&#x1f60a;&#x1f618; &#x1f31f;&#x1f31f;&#x1f31f;技术这条路固然很艰辛&#xff0c;但既已选择&…

网络编程之简单socket通信

一.什么是Socket? Socket&#xff0c;又叫套接字&#xff0c;是在应用层和传输层的一个抽象层。它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。 socket分为流socket和数据报socket&#xff0c;分别基于tcp和udp实现。 SOCK_STREAM 有以下…

苦学58天,最后就这结果......

背景 非计科大专一枚&#xff0c;当初学的机械自动化专业。大学完全可以说是玩过来的&#xff0c;临近毕业开始慌了&#xff0c;毕业后一直没能找到工作&#xff0c;在高中同学&#xff08;211 计科&#xff09;的引领下&#xff0c;入坑程序员&#xff0c;学的软件测试。 从…

Lombok简介

Lombok简介 1、lombok简介2、springboot整合lombok 1、lombok简介 Lombok是一个第三方的Java工具库&#xff0c;会自动插入编辑器和构建工具。Lombok提供了一组非常有用的注解&#xff0c;用来消除Java类中的大量样板代码&#xff0c;比如setter和getter方法、构造方法等。只需…

Vue(简单了解Cookie、生命周期)

一、了解Cookie 类似于对象响应携带数据 输入用户名密码跳转到指定页面 点击指定页面中其中一个按钮跳转到另一个指定页面&#xff08;再不需用输入用户名密码&#xff09; 例如现在很多浏览器实现七天免密登录 简单理解&#xff1a;就是在网站登录页面之后&#xff0c;服务…

新建Django项目

1. 创建项目 使用Django提供的命令&#xff0c;可以创建一个Django项目实例需要的配置项——包括数据库配置、Django配置和应用程序配置的集合。新建Django项目命令的语法格式如下&#xff1a; django-admin startproject 工程名称例如&#xff1a;想要在D:\的pythonProject目…