红黑树,插入和删除,基于C++的实现

        接上篇红黑树,插入和删除,更好的理解 ,在这篇文章中,我不会过多介绍理解部分,因为上篇文章已经说的差不多了。接下来我将基于C++来实现红黑树,同样的,欢迎大家在评论区提出疑问和指出错误!

        首先,我们需要构建红黑树的结点类,这个类里面,要有key值,value值,结点的颜色,还要有指向左右子结点的结点类指针,最后加上指向父结点的结点类指针。我这里,将key值设为int类型,value值设为string类型,结点的颜色用bool类型表示,也就是true表示红色,false表示黑色。

class Node_RB {
public:
	int m_key;
	string m_value;
	bool m_color;
	Node_RB* m_left;
	Node_RB* m_right;
	Node_RB* m_parent;

	Node_RB(int k, string v,bool c,Node_RB* l,Node_RB* r,Node_RB* p) {
		m_key = k;
		m_value = v;
		m_color = c;
		m_left = l;
		m_right = r;
		m_parent = p;
	}
};

        接下来,就开始写我们的红黑树类了,这个类的成员变量和二叉树差不多,也有root根结点和表示结点数量的m_n。同时,我们知道,在插入和删除中,我们都会频繁用到左旋右旋,所以,也提前写好这两个函数。注释部分我也参考了阿里P7大佬手撕红黑树全套教程,已经写的很详细了,也就不再过多赘述。

class RB_tree {
public:
	Node_RB* root;
	int m_n;

	RB_tree() {
		m_n = 0;
		root = NULL;
	}
	/*  左旋,以p为支点,注意,要记得考虑p的父节点,要加上p的父节点怎么连
	    p                pr
	   /\                /\
      pl pr     =>      p  rr
	     /\            /\
		rl rr         pl rl
	*/
	void left_rotate(Node_RB* p) {
		// temp = pr; p的右结点 = rl;rl的父节点设成p; pr的父节点 = p的父节点;p的父节点连pr; p的父节点 = pr; pr的左结点 = p;
			Node_RB* temp = p->m_right;   //就是pr
			p->m_right = temp->m_left;
			if (p->m_right != NULL) {
				//假如rl不是空,那就要设置rl的父节点为p
				p->m_right->m_parent = p;
			}
			temp->m_parent = p->m_parent; //p是它父节点的左结点还是右节点,或者它就是root
			if (p->m_parent == NULL) {
				//p是根节点
				root = temp;
			}
			else if (p->m_parent->m_left == p) {
				//p是左结点
				p->m_parent->m_left = temp;
			}
			else if (p->m_parent->m_right == p) {
				//p是右节点
				p->m_parent->m_right = temp;
			}
			p->m_parent = temp;
			temp->m_left = p;
	}

	/*  右旋 以p为支点
	      p                 pl
		 /\                 /\
	   pl  pr    =>       rl  p
	   /\                    /\
     rl  rr                 rr pr

	*/
	void right_rotate(Node_RB* p) {
		Node_RB* temp = p->m_left;    //pl
		p->m_left = temp->m_right;
		if (p->m_left != NULL) {
			p->m_left->m_parent = p;
		}

		temp->m_parent = p->m_parent;
		if (p->m_parent == NULL) {
			root = temp;
		}
		else if (p->m_parent->m_left == p) {
			p->m_parent->m_left = temp;
		}
		else if (p->m_parent->m_right == p) {
			p->m_parent->m_right = temp;
		}

		temp->m_right = p;
		p->m_parent = temp;
	}
}

        开始我们最重要的两部分之一了:插入

       回顾一下上一篇我总结的:

    1.空树:作为根节点,变为黑色false
    2.存在已有的key,换值即可
    3.父节点为黑色结点:直接添加即可,无需改变
    前三种情况已经处理好了,adjust函数处理后面的情况
    4.父节点为红色:(那爷爷结点一定是黑色了,同时叔叔结点要么没有,要么就是红色,如果叔叔结点为黑色,那就不平衡了,叔叔那边深度就会大)
        1.叔叔结点不存在
            插入结点和父节点在同一侧
                都在左侧,以爷爷节点为支点右旋,然后父节点改为黑色,爷爷结点改为红色
                都在右侧,以爷爷节点为支点左旋,变色同上
            插入结点和父节点不在同一侧
                父节点在左侧,以父节点为支点左旋,转换为上面同一侧的情况,这里改颜色的是自己和              爷爷,要注意的是,旋转后,谁是父节点,谁是爷爷结点
                父节点在右侧,以父节点为支点右旋,转换为上面同一侧的情况
        2.叔叔结点为红色,这种情况有可能就要从下往上迭代了,因为这让爷爷结点变红了,就可能出现爷爷的父节点也是红色
            只需要变色,爷爷结点变红,父节点和叔叔结点变黑

void put(int k, string v) {
		Node_RB* n = root;
		Node_RB* np = root;
		if (n == NULL) {
			//空树插入第一个结点
			root = new Node_RB(k, v, false, NULL, NULL, NULL);
			m_n = 1;
			return;
		}
		//先找一下这个结点在哪
		while (n != NULL) {
			np = n;//记录父节点
			if (n->m_key == k) {
				//存在已有的key,直接换值即可
				n->m_value = v;
				return;
			}
			else if (k < n->m_key) {
				n = n->m_left;
			}
			else {
				n = n->m_right;
			}
		}
		//先让这个插入结点的父节点指对,指向np
		Node_RB* n_new = new Node_RB(k, v, true, NULL, NULL, np);
		//判断这个结点是在np的左边还是右边
		if (k < np->m_key) {
			np->m_left = n_new;
		}
		else {
			np->m_right = n_new;
		}
		//判断父节点是不是黑的
		if (np->m_color == false) {
			m_n++;
			return;
		}
		else {
			adjust(n_new);
			m_n++;
			return;
		}
	}


void adjust(Node_RB* n) {
		//循环,n的父节点不是黑结点,这个主要是为了叔叔结点是红色这一种情况,而且循环到n自己就是根结点就可以退出循环了
		while (n != root && n->m_parent->m_color != false) {
			if (n->m_parent->m_parent->m_left == n->m_parent) {
				//父节点是爷爷结点的左结点,那就考虑叔叔结点有无,没有的话,考虑插入节点和父节点在不在同一侧
				if (n->m_parent->m_parent->m_right == NULL) {
					//没有叔叔结点
					if (n->m_parent->m_left == n) {
						//插入结点和父节点都是左节点,最好先变色,再旋转,不然旋转完,就不太好分清谁是父节点谁是插入结点了
						n->m_parent->m_color = false;
						n->m_parent->m_parent->m_color = true;
						right_rotate(n->m_parent->m_parent);
						//这里n->parent颜色是黑色的了,就会退出循环
					}
					else {
						//插入结点和父节点不在同一侧
						n->m_color = false;
						n->m_parent->m_parent->m_color = true;
						left_rotate(n->m_parent);
						//这里原来的父结点已经变成插入结点的左子结点了,所以接下来要右旋,是要以原来的爷爷结点为支点
						//也就是现在这个n的父节点
						right_rotate(n->m_parent);
						n = n->m_left;//这一步是为了退出循环,这样n的父节点(其实就是原来的n)就是黑色的了
					}
				}
				else {
					//叔叔结点为红色
					n->m_parent->m_parent->m_color = true;
					n->m_parent->m_parent->m_right->m_color = false;
					n->m_parent->m_color = false;
					//以爷爷为插入结点进行新一轮循环,或递归,假如爷爷的父节点是红的,就会继续循环,否则就会退出循环
					n = n->m_parent->m_parent;
				}
			}
			else {
				//父节点是右节点
				if (n->m_parent->m_parent->m_left == NULL) {
					//没有叔叔结点
					if (n->m_parent->m_right == n) {
						//和父节点一样在右侧
						n->m_parent->m_color = false;
						n->m_parent->m_parent->m_color = true;
						left_rotate(n->m_parent->m_parent);
					}
					else {
						//不在同一侧
						n->m_color = false;
						n->m_parent->m_parent->m_color = true;
						right_rotate(n->m_parent);
						left_rotate(n->m_parent);
						n = n->m_right;
					}
				}
				else {
					//叔叔结点为红色
					n->m_parent->m_parent->m_color = true;
					n->m_parent->m_parent->m_left->m_color = false;
					n->m_parent->m_color = false;
					n = n->m_parent->m_parent;
				}
			}
		}
		//循环完了要记得把root设成黑色
		root->m_color = false;
		
	}

        最后是比较麻烦的删除

        同样的,回顾一下上一篇总结的思路

删除结点,总共三种情况
    1.删除结点有两个子结点
        这种情况都会转换,通过找它的后继(或者前驱)结点,然后把后继结点的key和value替换掉删除结点,最后将问题转换为删除后继结点即可
        并且,后继结点要么就是叶节点,要么就是只有一个子结点
    2.删除结点只有一个子结点
        这种情况也很简单,只会出现一种情况,就是结点为黑,有一个红色的子结点,如果是后继结点,那只可能是右子结点
        因为其他情况,在平衡时不存在,红红不行,黑黑,红黑都会导致左右不平衡
        让子结点代替要删掉的结点,并把颜色换成黑色即可
    3.删除结点是叶节点
        这种情况分两种,红色还是黑色
        1.红色,直接删除
        2.黑色,很麻烦,再写一个函数来专门处理这种情况

//这个函数用来找到这个key的结点
Node_RB* find(int k) {
		Node_RB* n = root;
		if (n == NULL) {
			cout << "no key" << endl;
			return n;
		}

		while (n != NULL) {
			if (k < n->m_key) {
				n = n->m_left;
			}
			else if (k > n->m_key) {
				n = n->m_right;
			}
			else {
				return n;
			}
		}
		cout << " no key!"<<endl;
		return NULL;
	}

//这个函数用来找后继结点,就是当前结点的右边子树里的最左边的点,也就是这树里面只比当前结点大的点
Node_RB* back_node(Node_RB* n) {
		if (n == NULL) {
			return NULL;
		}
		else if (n->m_right != NULL) {
			//先看看有没有右子树,当前情况是一定有的
			n = n->m_right;
			//然后循环找最左边的点
			while (n->m_left != NULL) {
				n = n->m_left;
			}
			return n; //找到了这个最左边的点,返回
		}
		else {
			//没有右边子树,要是根结点,就不用找了,否则就要往上找了
			//就是当n是父节点的左结点时,就说明这个父节点就是所需要的后继结点
			while (n != NULL && n == n->m_parent->m_right) {
				n = n->m_parent;
			}
			return n->m_parent;
		}
	}

//删除结点,并返回此结点的value
string delete_node(int k) {
		Node_RB* n = find(k);
		if (n != NULL) {
			//找到了要删除的结点
			if (n->m_left != NULL && n->m_right != NULL) {
				//这个结点有两个子结点,所以先找到它的后继结点,让后继结点的key和value覆盖掉原来n的
				//将问题转换为删除后继结点,只有一个子结点或者没有子结点
				Node_RB* n_back = back_node(n);
				n->m_key = n_back->m_key;
				string temp = n->m_value;
				n->m_value = n_back->m_value;
				n_back->m_value = temp;

				n = n_back;
			}

			//判断这个结点是不是只有一个子结点,并且上面一个if已经把有两个子结点的情况去除掉了
			if (n->m_left != NULL || n->m_right != NULL) {
				Node_RB* ns = NULL;

				if (n->m_left != NULL) {
					//有左红子结点,让子结点代替删除结点n
					ns = n->m_left;
				}
				else {
					//有右红子结点
					ns = n->m_right;
				}

				ns->m_parent = n->m_parent;

				if (n->m_parent == NULL) {
					//n就是root
					root = ns;
				}
				else if (n == n->m_parent->m_left) {
					//n是左结点
					n->m_parent->m_left = ns;
				}
				else {
					//n是右结点
					n->m_parent->m_right = ns;
				}

				ns->m_color = false;
				string old_value = n->m_value;
				delete n;
				m_n--;
				return old_value;
			}
			else if (n->m_parent == NULL) {
				//n就是root,并且没有子结点
				string old_value = n->m_value;
				delete n;
				m_n--;
				return old_value;
			}
			else {
				//n是叶节点
				if (n->m_color == true) {
					//假如是红结点,那直接删除即可
					if (n->m_parent->m_left == n) {
						n->m_parent->m_left = NULL;
					}
					else {
						n->m_parent->m_right = NULL;
					}
					string old_value = n->m_value;
					delete n;
					m_n--;
					return old_value;
				}
				else {
					//最麻烦的情况,黑色的叶节点,再写一个函数来平衡
					//这种情况就是先调整平衡,再删除
					balance_bnode(n);
					//调整完后,再同样地删除
					if (n->m_parent->m_left == n) {
						n->m_parent->m_left = NULL;
					}
					else {
						n->m_parent->m_right = NULL;
					}
					string old_value = n->m_value;
					delete n;
					m_n--;
					return old_value;
				}
			}

		}
		else {
			return "no key!";
		}
	}

        下面要单独处理删除结点事黑色叶节点情况的函数

我会基于这个思路:

总的来说,因为删了黑色的,所以肯定就会不平衡了,这里通过找兄弟结点借来平衡,并且因为是黑色叶结点,所以肯定会有兄弟结点的。
        意思就是,假设,这个结点是要被删掉的左黑叶结点,那就需要通过左旋,让父节点下来代替它(不管父节点原来什么颜色,都变成它的颜色——黑),同时,兄弟结点(黑)上来代替父亲(变成父亲原来的颜色),并且,兄弟结点的右子红结点变黑维持平衡。
        其他情况都会转换成这种情况
        但要是兄弟结点也是黑色叶子结点,那就是兄弟结点没有红色子结点,也就是没得借了

要注意的是,这里的兄弟,指的是2-3-4树里的兄弟,所以红黑树里的红色的兄弟结点,是不对的,要通过旋转,让这个红色兄弟的黑子结点变成删除结点的兄弟结点,具体见上一篇

    0.兄弟结点是红色,通过旋转,变色,让兄弟结点是黑色
    1.兄弟结点可以借,就是有红色的子结点
        1.兄黑,子两红
        2.兄黑,子左红
        3.兄黑,子右红
    2.兄弟结点没得借,就是兄弟结点是黑色叶节点
        1.父结点是红色,那直接父节点换成黑色,兄结点换成红色即可
        2.父节点是黑色,这个就麻烦了,不可避免的子树黑色深度要减1,就需要循环或递归,一层层往上直到根结点不断变色

void balance_bnode(Node_RB* n) {
		//这里应该不用担心形参改变会影响实参,这是值传递,虽然这个while循环里有改变n
		while (n != root) {
			//先分是左节点还是右节点
			if (n->m_parent->m_left == n) {
				//n是左结点
				//先找到兄弟结点
				Node_RB* nb = n->m_parent->m_right;
				//判断它是不是红的,是红的就要经过操作让它的子黑结点,变成新的兄弟结点
				if (nb->m_color == true) {
					nb->m_color = false;
					nb->m_parent->m_color = true;
					left_rotate(nb->m_parent);
					//设置新的兄弟结点
					nb = n->m_parent->m_right;
				}
				//找到兄弟结点之后,就该判断它有没有子结点了,也就是能不能借了
				if (nb->m_left == NULL && nb->m_right == NULL) {
					//兄弟结点也是黑色叶子结点,不能借
					//不管哪种情况,兄弟结点都是变成红色
					nb->m_color = true;

					if (n->m_parent->m_color == true) {
						//1.父节点是红色
						n->m_parent->m_color = false;
						n = root; //这是为了退出循环
					}
					else {
						//2.父节点是黑色
						//那就需要以父结点变成n再循环一次了,当然,假如父节点就是根节点,那就不用循环了
						n = n->m_parent; 
					}
				}
				else {
					//兄弟结点有子结点,可以借,那就将情况全部变为有右子红的情况(子两红属于有右子红)
					if (nb->m_right == NULL) {
						//兄弟结点没有右子结点,只有左子结点,那就需要通过右旋,让它有右子结点
						nb->m_color = true;
						nb->m_left->m_color = false;
						right_rotate(nb);
						//现在nb变成那个右红子结点了,让它的父节点变成新的兄弟结点
						nb = nb->m_parent;
					}
					//现在情况已经是有右子红了,需要将,即将代替父节点的兄弟结点换成父节点的颜色,
					//即将代替删除结点n的父节点换成n的颜色,黑
					//兄弟结点的右子红会上去,就要变成黑色来保持平衡
					nb->m_color = nb->m_parent->m_color;
					nb->m_parent->m_color = false;
					nb->m_right->m_color = false;
					//然后以父节点左旋
					left_rotate(n->m_parent);

					n = root;//为了退出循环
				}
			}
			else {
				//n是右结点
				//先找到兄弟结点
				Node_RB* nb = n->m_parent->m_left;
				//判断它是不是红的,是红的就要经过操作让它的子黑结点,变成新的兄弟结点
				if (nb->m_color == true) {
					nb->m_color = false;
					nb->m_parent->m_color = true;
					right_rotate(nb->m_parent);
					//设置新的兄弟结点
					nb = n->m_parent->m_left;
				}
				//找到兄弟结点之后,就该判断它有没有子结点了,也就是能不能借了
				if (nb->m_left == NULL && nb->m_right == NULL) {
					//兄弟结点也是黑色叶子结点,不能借
					//不管哪种情况,兄弟结点都是变成红色
					nb->m_color = true;

					if (n->m_parent->m_color == true) {
						//1.父节点是红色
						n->m_parent->m_color = false;
						n = root; //这是为了退出循环
					}
					else {
						//2.父节点是黑色
						//那就需要以父结点变成n再循环一次了,当然,假如父节点就是根节点,那就不用循环了
						n = n->m_parent;
					}
				}
				else {
					//兄弟结点有子结点,可以借,那就将情况全部变为有左子红的情况(子两红属于有左子红)
					if (nb->m_left == NULL) {
						//兄弟结点没有左子结点,只有右子结点,那就需要通过左旋,让它有左子结点
						nb->m_color = true;
						nb->m_right->m_color = false;
						left_rotate(nb);
						//现在nb变成那个左红子结点了,让它的父节点变成新的兄弟结点
						nb = nb->m_parent;
					}
					//现在情况已经是有左子红了,需要将,即将代替父节点的兄弟结点换成父节点的颜色,
					//即将代替删除结点n的父节点换成n的颜色,黑
					//兄弟结点的左子红会上去,就要变成黑色来保持平衡
					nb->m_color = nb->m_parent->m_color;
					nb->m_parent->m_color = false;
					nb->m_left->m_color = false;
					//然后以父节点you旋
					right_rotate(n->m_parent);

					n = root;//为了退出循环

				}
			}
		}
	 }

        现在红黑树的插入和删除代码已经写完了,我们来测试一下。

        我用前序遍历输出一下这棵树,同时包括每个结点的颜色

void pre_print() {
		queue<int> q1;
		queue<bool> q2;
		pre_print(root,q1,q2);
		int cnt = q1.size();
		for (int i = 0; i < cnt; i++) {
			cout << q1.front()<<"+"<<q2.front()<<" ";
			q1.pop();
			q2.pop();
		}
		cout << endl;
	}

void pre_print(Node_RB* n, queue<int> &q1, queue<bool> &q2) {
		if (n == NULL) {
			return;
		}
		q1.push(n->m_key);
		q2.push(n->m_color);
		if (n->m_left!= NULL) {
			pre_print(n->m_left,q1,q2);
		}
		if (n->m_right != NULL) {
			pre_print(n->m_right, q1,q2);
		}
		return;
	}

        测试一下,我依次输入5,1,8,18,6,13,5,9,2,输出一次,接着删掉6再输出一次

void test01() {
	RB_tree rb;
	rb.put(5, "5");
	rb.put(1, "1");
	rb.put(8, "8");
	rb.put(18, "18");
	rb.put(6, "6");
	rb.put(13, "13");
	rb.put(5, "100");
	rb.put(9, "9");
	rb.put(2, "2");

	cout << rb.get_size()<<endl;
	cout << "-------------" << endl;
	rb.pre_print();
	cout << "-------------" << endl;
	rb.delete_node(6);
	cout << rb.get_size() << endl;
	rb.pre_print();
}

输出结果(前序遍历):

在红黑树生成网站生成的结果:红黑树

插入:

 删除key为6的结点:

 

结果符合预期。

        最后的最后,如果大家有什么疑问或者发现了错误,欢迎评论区互动,希望这篇文章对大家有所帮助!

完整代码如下:

#include<iostream>
#include<string>
using namespace std;
#include<queue>

class Node_RB {
public:
	int m_key;
	string m_value;
	bool m_color;
	Node_RB* m_left;
	Node_RB* m_right;
	Node_RB* m_parent;

	Node_RB(int k, string v,bool c,Node_RB* l,Node_RB* r,Node_RB* p) {
		m_key = k;
		m_value = v;
		m_color = c;
		m_left = l;
		m_right = r;
		m_parent = p;
	}
};

class RB_tree {
public:
	Node_RB* root;
	int m_n;

	RB_tree() {
		m_n = 0;
		root = NULL;
	}

	void left_rotate(Node_RB* p) {
		// temp = pr; p的右结点 = rl;rl的父节点设成p; pr的父节点 = p的父节点;p的父节点连pr; p的父节点 = pr; pr的左结点 = p;
			Node_RB* temp = p->m_right;   //就是pr
			p->m_right = temp->m_left;
			if (p->m_right != NULL) {
				//假如rl不是空,那就要设置rl的父节点为p
				p->m_right->m_parent = p;
			}
			temp->m_parent = p->m_parent; //p是它父节点的左结点还是右节点,或者它就是root
			if (p->m_parent == NULL) {
				//p是根节点
				root = temp;
			}
			else if (p->m_parent->m_left == p) {
				//p是左结点
				p->m_parent->m_left = temp;
			}
			else if (p->m_parent->m_right == p) {
				//p是右节点
				p->m_parent->m_right = temp;
			}
			p->m_parent = temp;
			temp->m_left = p;
	}

	
	void right_rotate(Node_RB* p) {
		Node_RB* temp = p->m_left;    //pl
		p->m_left = temp->m_right;
		if (p->m_left != NULL) {
			p->m_left->m_parent = p;
		}

		temp->m_parent = p->m_parent;
		if (p->m_parent == NULL) {
			root = temp;
		}
		else if (p->m_parent->m_left == p) {
			p->m_parent->m_left = temp;
		}
		else if (p->m_parent->m_right == p) {
			p->m_parent->m_right = temp;
		}

		temp->m_right = p;
		p->m_parent = temp;
	}

	bool get_color(Node_RB* n) {
		return n == NULL ? false : n->m_color;
	}

	int get_size() {
		return m_n;
	}
	void put(int k, string v) {
		Node_RB* n = root;
		Node_RB* np = root;
		if (n == NULL) {
			//空树插入第一个结点
			root = new Node_RB(k, v, false, NULL, NULL, NULL);
			m_n = 1;
			return;
		}
		//先找一下这个结点在哪
		while (n != NULL) {
			np = n;//记录父节点
			if (n->m_key == k) {
				//存在已有的key,直接换值即可
				n->m_value = v;
				return;
			}
			else if (k < n->m_key) {
				n = n->m_left;
			}
			else {
				n = n->m_right;
			}
		}
		//先让这个插入结点的父节点指对,指向np
		Node_RB* n_new = new Node_RB(k, v, true, NULL, NULL, np);
		//判断这个结点是在np的左边还是右边
		if (k < np->m_key) {
			np->m_left = n_new;
		}
		else {
			np->m_right = n_new;
		}
		//判断父节点是不是黑的
		if (np->m_color == false) {
			m_n++;
			return;
		}
		else {
			adjust(n_new);
			m_n++;
			return;
		}
		
	}

	
	void adjust(Node_RB* n) {
		//循环,n的父节点不是黑结点,这个主要是为了叔叔结点是红色这一种情况,而且循环到n自己就是根结点就可以退出循环了
		while (n != root && n->m_parent->m_color != false) {
			if (n->m_parent->m_parent->m_left == n->m_parent) {
				//父节点是爷爷结点的左结点,那就考虑叔叔结点有无,没有的话,考虑插入节点和父节点在不在同一侧
				if (n->m_parent->m_parent->m_right == NULL) {
					//没有叔叔结点
					if (n->m_parent->m_left == n) {
						//插入结点和父节点都是左节点,最好先变色,再旋转,不然旋转完,就不太好分清谁是父节点谁是插入结点了
						n->m_parent->m_color = false;
						n->m_parent->m_parent->m_color = true;
						right_rotate(n->m_parent->m_parent);
						//这里n->parent颜色是黑色的了,就会退出循环
					}
					else {
						//插入结点和父节点不在同一侧
						n->m_color = false;
						n->m_parent->m_parent->m_color = true;
						left_rotate(n->m_parent);
						//这里原来的父结点已经变成插入结点的左子结点了,所以接下来要右旋,是要以原来的爷爷结点为支点
						//也就是现在这个n的父节点
						right_rotate(n->m_parent);
						n = n->m_left;//这一步是为了退出循环,这样n的父节点(其实就是原来的n)就是黑色的了
					}
				}
				else {
					//叔叔结点为红色
					n->m_parent->m_parent->m_color = true;
					n->m_parent->m_parent->m_right->m_color = false;
					n->m_parent->m_color = false;
					//以爷爷为插入结点进行新一轮循环,或递归,假如爷爷的父节点是红的,就会继续循环,否则就会退出循环
					n = n->m_parent->m_parent;
				}
			}
			else {
				//父节点是右节点
				if (n->m_parent->m_parent->m_left == NULL) {
					//没有叔叔结点
					if (n->m_parent->m_right == n) {
						//和父节点一样在右侧
						n->m_parent->m_color = false;
						n->m_parent->m_parent->m_color = true;
						left_rotate(n->m_parent->m_parent);
					}
					else {
						//不在同一侧
						n->m_color = false;
						n->m_parent->m_parent->m_color = true;
						right_rotate(n->m_parent);
						left_rotate(n->m_parent);
						n = n->m_right;
					}
				}
				else {
					//叔叔结点为红色
					n->m_parent->m_parent->m_color = true;
					n->m_parent->m_parent->m_left->m_color = false;
					n->m_parent->m_color = false;
					n = n->m_parent->m_parent;
				}
			}
		}
		//循环完了要记得把root设成黑色
		root->m_color = false;
		
	}

	void pre_print() {
		queue<int> q1;
		queue<bool> q2;
		pre_print(root,q1,q2);
		int cnt = q1.size();
		for (int i = 0; i < cnt; i++) {
			cout << q1.front()<<"+"<<q2.front()<<" ";
			q1.pop();
			q2.pop();
		}
		cout << endl;
	}

	void pre_print(Node_RB* n, queue<int> &q1, queue<bool> &q2) {
		if (n == NULL) {
			return;
		}
		q1.push(n->m_key);
		q2.push(n->m_color);
		if (n->m_left!= NULL) {
			pre_print(n->m_left,q1,q2);
		}
		if (n->m_right != NULL) {
			pre_print(n->m_right, q1,q2);
		}
		return;
	}
	/*void find(int k) {
		find(root,k);
	}*/

	Node_RB* find(int k) {
		Node_RB* n = root;
		if (n == NULL) {
			cout << "no key" << endl;
			return n;
		}

		while (n != NULL) {
			if (k < n->m_key) {
				n = n->m_left;
			}
			else if (k > n->m_key) {
				n = n->m_right;
			}
			else {
				return n;
			}
		}
		cout << " no key!"<<endl;
		return NULL;
	}

	string delete_node(int k) {
		Node_RB* n = find(k);
		if (n != NULL) {
			//找到了要删除的结点
			if (n->m_left != NULL && n->m_right != NULL) {
				//这个结点有两个子结点,所以先找到它的后继结点,让后继结点的key和value覆盖掉原来n的
				//将问题转换为删除后继结点,只有一个子结点或者没有子结点
				Node_RB* n_back = back_node(n);
				n->m_key = n_back->m_key;
				string temp = n->m_value;
				n->m_value = n_back->m_value;
				n_back->m_value = temp;

				n = n_back;
			}

			//判断这个结点是不是只有一个子结点,并且上面一个if已经把有两个子结点的情况去除掉了
			if (n->m_left != NULL || n->m_right != NULL) {
				Node_RB* ns = NULL;

				if (n->m_left != NULL) {
					//有左红子结点,让子结点代替删除结点n
					ns = n->m_left;
				}
				else {
					//有右红子结点
					ns = n->m_right;
				}

				ns->m_parent = n->m_parent;

				if (n->m_parent == NULL) {
					//n就是root
					root = ns;
				}
				else if (n == n->m_parent->m_left) {
					//n是左结点
					n->m_parent->m_left = ns;
				}
				else {
					//n是右结点
					n->m_parent->m_right = ns;
				}

				ns->m_color = false;
				string old_value = n->m_value;
				delete n;
				m_n--;
				return old_value;
			}
			else if (n->m_parent == NULL) {
				//n就是root,并且没有子结点
				string old_value = n->m_value;
				delete n;
				m_n--;
				return old_value;
			}
			else {
				//n是叶节点
				if (n->m_color == true) {
					//假如是红结点,那直接删除即可
					if (n->m_parent->m_left == n) {
						n->m_parent->m_left = NULL;
					}
					else {
						n->m_parent->m_right = NULL;
					}
					string old_value = n->m_value;
					delete n;
					m_n--;
					return old_value;
				}
				else {
					//最麻烦的情况,黑色的叶节点,再写一个函数来平衡
					//这种情况就是先调整平衡,再删除
					balance_bnode(n);
					//调整完后,再同样地删除
					if (n->m_parent->m_left == n) {
						n->m_parent->m_left = NULL;
					}
					else {
						n->m_parent->m_right = NULL;
					}
					string old_value = n->m_value;
					delete n;
					m_n--;
					return old_value;
				}
			}

		}
		else {
			return "no key!";
		}
	}

	void balance_bnode(Node_RB* n) {
		//这里应该不用担心形参改变会影响实参,这是值传递,虽然这个while循环里有改变n
		while (n != root) {
			//先分是左节点还是右节点
			if (n->m_parent->m_left == n) {
				//n是左结点
				//先找到兄弟结点
				Node_RB* nb = n->m_parent->m_right;
				//判断它是不是红的,是红的就要经过操作让它的子黑结点,变成新的兄弟结点
				if (nb->m_color == true) {
					nb->m_color = false;
					nb->m_parent->m_color = true;
					left_rotate(nb->m_parent);
					//设置新的兄弟结点
					nb = n->m_parent->m_right;
				}
				//找到兄弟结点之后,就该判断它有没有子结点了,也就是能不能借了
				if (nb->m_left == NULL && nb->m_right == NULL) {
					//兄弟结点也是黑色叶子结点,不能借
					//不管哪种情况,兄弟结点都是变成红色
					nb->m_color = true;

					if (n->m_parent->m_color == true) {
						//1.父节点是红色
						n->m_parent->m_color = false;
						n = root; //这是为了退出循环
					}
					else {
						//2.父节点是黑色
						//那就需要以父结点变成n再循环一次了,当然,假如父节点就是根节点,那就不用循环了
						n = n->m_parent; 
					}
				}
				else {
					//兄弟结点有子结点,可以借,那就将情况全部变为有右子红的情况(子两红属于有右子红)
					if (nb->m_right == NULL) {
						//兄弟结点没有右子结点,只有左子结点,那就需要通过右旋,让它有右子结点
						nb->m_color = true;
						nb->m_left->m_color = false;
						right_rotate(nb);
						//现在nb变成那个右红子结点了,让它的父节点变成新的兄弟结点
						nb = nb->m_parent;
					}
					//现在情况已经是有右子红了,需要将,即将代替父节点的兄弟结点换成父节点的颜色,
					//即将代替删除结点n的父节点换成n的颜色,黑
					//兄弟结点的右子红会上去,就要变成黑色来保持平衡
					nb->m_color = nb->m_parent->m_color;
					nb->m_parent->m_color = false;
					nb->m_right->m_color = false;
					//然后以父节点左旋
					left_rotate(n->m_parent);

					n = root;//为了退出循环
				}
			}
			else {
				//n是右结点
				//先找到兄弟结点
				Node_RB* nb = n->m_parent->m_left;
				//判断它是不是红的,是红的就要经过操作让它的子黑结点,变成新的兄弟结点
				if (nb->m_color == true) {
					nb->m_color = false;
					nb->m_parent->m_color = true;
					right_rotate(nb->m_parent);
					//设置新的兄弟结点
					nb = n->m_parent->m_left;
				}
				//找到兄弟结点之后,就该判断它有没有子结点了,也就是能不能借了
				if (nb->m_left == NULL && nb->m_right == NULL) {
					//兄弟结点也是黑色叶子结点,不能借
					//不管哪种情况,兄弟结点都是变成红色
					nb->m_color = true;

					if (n->m_parent->m_color == true) {
						//1.父节点是红色
						n->m_parent->m_color = false;
						n = root; //这是为了退出循环
					}
					else {
						//2.父节点是黑色
						//那就需要以父结点变成n再循环一次了,当然,假如父节点就是根节点,那就不用循环了
						n = n->m_parent;
					}
				}
				else {
					//兄弟结点有子结点,可以借,那就将情况全部变为有左子红的情况(子两红属于有左子红)
					if (nb->m_left == NULL) {
						//兄弟结点没有左子结点,只有右子结点,那就需要通过左旋,让它有左子结点
						nb->m_color = true;
						nb->m_right->m_color = false;
						left_rotate(nb);
						//现在nb变成那个左红子结点了,让它的父节点变成新的兄弟结点
						nb = nb->m_parent;
					}
					//现在情况已经是有左子红了,需要将,即将代替父节点的兄弟结点换成父节点的颜色,
					//即将代替删除结点n的父节点换成n的颜色,黑
					//兄弟结点的左子红会上去,就要变成黑色来保持平衡
					nb->m_color = nb->m_parent->m_color;
					nb->m_parent->m_color = false;
					nb->m_left->m_color = false;
					//然后以父节点you旋
					right_rotate(n->m_parent);

					n = root;//为了退出循环

				}
			}
		}
	 }

	Node_RB* back_node(Node_RB* n) {
		if (n == NULL) {
			return NULL;
		}
		else if (n->m_right != NULL) {
			//先看看有没有右子树,当前情况是一定有的
			n = n->m_right;
			//然后循环找最左边的点
			while (n->m_left != NULL) {
				n = n->m_left;
			}
			return n; //找到了这个最左边的点,返回
		}
		else {
			//没有右边子树,要是根结点,就不用找了,否则就要往上找了
			//就是当n是父节点的左结点时,就说明这个父节点就是所需要的后继结点
			while (n != NULL && n == n->m_parent->m_right) {
				n = n->m_parent;
			}
			return n->m_parent;
		}
	}

};

void test01() {
	RB_tree rb;
	rb.put(5, "5");
	rb.put(1, "1");
	rb.put(8, "8");
	rb.put(18, "18");
	rb.put(6, "6");
	rb.put(13, "13");
	rb.put(5, "100");
	rb.put(9, "9");
	rb.put(2, "2");

	cout << rb.get_size()<<endl;
	cout << "-------------" << endl;
	rb.pre_print();
	cout << "-------------" << endl;
	rb.delete_node(6);
	cout << rb.get_size() << endl;
	rb.pre_print();

}

int main() {
	test01();
	system("pause");
	return 0;
}

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值