红黑树简易C++实现

原因

这几天研究了红黑树,光说不练假把式,于是就自己手动实现一下

数据结构声明
#include <bits/stdc++.h>
using namespace std;
enum Color{
	RED = 0,
	BLACK
};

// 红黑树节点RB_Node
struct RB_Node{
	int val;
	Color color;
	RB_Node *left;
	RB_Node *right;
	RB_Node *parent;
	RB_Node() = default;
	RB_Node(int _val): val(_val) {
		left = right = parent = NULL;
	}
};

// 红黑树RBTree
struct RBTree{
	RB_Node *root;
	void RB_insert(int);
	void RB_erase(int);
	// 编写一个输出函数用于测试,输出前序遍历与中序遍历
	void displayTree() {
		printf("PreOrder: ");
		preOrder(root);
		puts("");
		printf("InOrder: ");
		inOrder(root);
	}
	RBTree(): root(NULL){}
	
private:
	void preOrder(RB_Node *node) {
		if (NULL == node) return;
		printf("%d ", node->val);
		preOrder(node->left);
		preOrder(node->right);
	}
	void inOrder(RB_Node *node) {
		if (NULL == node) return;
		inOrder(node->left);
		printf("%d ", node->val);
		inOrder(node->right);
	}
	// 辅助函数用于在插入和删除后通过染色和旋转维护红黑树性质
	void RB_insert_fixup(RB_Node*, RB_Node*&);
	void RB_erase_fixup(RB_Node*, RB_Node*&);
	// 旋转操作
	void LeftRotate(RB_Node* x, RB_Node*&);
	void RightRotate(RB_Node* y, RB_Node*&);
	// 用于删除操作中获得后继节点,此处后继节点选用左子树的最大节点
	RB_Node* TreeSuccessor(RB_Node* x) {
		if (!x) return NULL;
		while (x->right != NULL) {
			x = x->right;
		}
		return x;
	}
};
插入操作
void RBTree::RB_insert(int val) {
	// STEP0: 首先找到要插入的位置,y代表要插入节点的父节点
	RB_Node *z = new RB_Node(val);
	RB_Node *x = root;
	RB_Node *y = NULL;
	while (x != NULL) {
		y = x;
		x = val < x->val ? x->left : x->right; 
	}
	
	// STEP1: 插入该节点并将该节点染成红色,之所以染成红色是因为:若染成黑色,则必定违反性质5,但若染成红色,则可能违反性质4
	// 处理的情况要更少一些
	if (y == NULL) {
		root = z;
	} else if (z->val < y->val)
		y->left = z;
	else
		y->right = z;
	z->parent = y;	
	z->color = RED;
	RB_insert_fixup(z, root);
}

void RBTree::RB_insert_fixup(RB_Node *z, RB_Node*& root) {
// 若插入的节点为根节点或者父节点的颜色为黑色,并不需要做任何调整,直接退出循环
	while (z != root && z->parent->color == RED) {
		// 假设父节点是祖父节点的左子树,注意此处祖父节点一定存在
		// 因为若插入为根节点则直接退出,若插入点的父节点为根节点则其color为黑;
		if (z->parent == z->parent->parent->left) {
			RB_Node* y = z->parent->parent->right; // 叔叔节点,可能不存在
			// Case1: 叔叔节点为红色,为了维持性质5,将父节点和叔叔节点同时涂为黑色,祖父节点涂成红色
			// 将当前节点指向祖父节点并开始循环,因为有可能在祖父节点处破坏了性质4
			if (y && y->color == RED) {
				z->parent->color = BLACK;
				y->color = BLACK;
				z->parent->parent->color = RED;
				z = z->parent->parent;
			} else { // 无伯父节点,或伯父节点为黑 
				// Case2:若插入节点为父节点的右子树,则以父节点为根进行一次左旋,此时转成Case3
				if (z == z->parent->right) {
					z = z->parent;
					LeftRotate(z, root);
				}
				// 父节点由红涂黑,因此祖父节点涂红,做一次右旋
				z->parent->color = BLACK;
				z->parent->parent->color = RED;
				RightRotate(z->parent->parent, root);
			}
		} else {
		   // 父节点是祖父节点的右子树,此时代码逻辑和前面一样,只需要交换left和right操作即可
			RB_Node* y = z->parent->parent->left;
			if (y && y->color == RED) {
				z->parent->color = BLACK;
				y->color = BLACK;
				z->parent->parent->color = RED;
				z = z->parent->parent;
			} else {
				if (z == z->parent->left) {
					z = z->parent;
					RightRotate(z, root);
				}
				z->parent->color = BLACK;
				z->parent->parent->color = RED;
				LeftRotate(z->parent->parent, root);
			}
		}
	}
	root->color = BLACK;
}
删除操作
void RBTree::RB_erase(int val) {
   // Step0: 先找到要删除的节点z
	RB_Node *z = root;
	while (z) {
		if (val == z->val)
			break;
		else if (val < z->val)
			z = z->left;
		else
			z = z->right;
	}
	if (!z) return;
	// Step1: 类似AVL树的删除,若z为叶子结点或单孩子节点,则直接用其孩子节点替代它
	// 若z是双孩子节点,则找到其后继节点替代它,注意此处仅是将其后继节点y的数据信息拷贝
	// 到z(不包括颜色),从而没有破坏z处的性质,而转换成删除y的情形,即删除叶子结点或单孩子节点
	RB_Node *y = NULL, *x = NULL;
	if (!z->left || !z->right) {
		y = z;
	} else
	    y = TreeSuccessor(z->left);
	
	if (y->left) 
		x = y->left;
	else 
		x = y->right;
	if (x != NULL)
		x->parent = y->parent;
	if (y->parent == NULL) {
		root = x;
	} else if (y == y->parent->left)
		y->parent->left = x;
	else
		y->parent->right = x;
	if (y != z) {
		z->val = y->val;
	}
	// 若x为空,即y是叶子节点,则直接删除就行
	// 否则删除了y,对y的后代节点的性质5可能会产生影响,当y是黑色时 
	if (x && y->color == BLACK)
		RB_erase_fixup(x, root);
	delete y;
}

void RBTree::RB_erase_fixup(RB_Node* x, RB_Node*& root) {
	while (x != root && x->color == BLACK) {
		if (x == x->parent->left) {
		   // 此时兄弟节点必不为NULL,若为空则违反性质5,因为原本y这条路有y跟x都是黑色
			RB_Node* w = x->parent->right; 
			// Case1: 兄弟节点为红色,染色并右旋,这么做是为了再保持性质5的前提下转换为其余情况
			if (w->color == RED) {
				w->color = BLACK;
				x->parent->color = RED;
				LeftRotate(x->parent, root);
				w = x->parent->right;
			}
			// Case2: 兄弟节点为黑色,且拥有两个黑色孩子(包括NULL),可以这样理解:
			// 将x多余的黑色属性转移到x.p中,但这会导致所有经过w的分支中的黑色节点+1,因此将w染为红色保证性质5
			if ((!w->left || w->left->color == BLACK) && (!w->right && w->right->color == BLACK)) {
				w->color = RED;
				x = x->parent;
			} else {
			  // Case3: 如果w是黑色,且右孩子为黑色,左孩子为红色,那么右旋+染色变成情况4
				if (!w->right || w->right->color == BLACK) {
					w->left->color = BLACK; // RED to BLACK
					w->color = RED;
					RightRotate(w, root);
					w = x->parent->right;
				}
				Case4: w为黑色,右孩子红色,则左旋一次,为了维持性质5做相应染色操作,此时已经成功解决问题,将x置为根节点
				w->color = x->parent->color;
				x->parent->color = BLACK;
				w->right->color = BLACK;
				LeftRotate(x->parent, root);
				x = root;
			}	 
		} else {
			RB_Node *w = x->parent->left; // 兄弟节点
			if (w->color == RED) {
				w->color = BLACK;
				x->parent->color = RED;
				RightRotate(x->parent, root);
				w = x->parent->left;
			}
			if ((!w->left || w->left->color == BLACK) || (!w->right || w->right->color == BLACK)) {
				w->color = RED;
				x = x->parent;
			} else {
				if (!w->left || w->left->color == BLACK) {
					w->right->color = BLACK;
					w->color = RED;
					LeftRotate(w, root);
					w = x->parent->left;
				}
				w->color = x->parent->color;
				x->parent->color = BLACK;
				w->left->color = BLACK;
				RightRotate(x->parent, root);
				x = root;
			}
		}
	}
	x->color = BLACK;
}
左旋&右旋
// 不同于AVL的左右旋,由于红黑树维护了parent指针因此要维护该指针
void RBTree::LeftRotate(RB_Node* x, RB_Node*& root) {
	RB_Node *tmp = x->right;
	x->right = tmp->left;
	if (tmp->left)
		tmp->left->parent = x;
	tmp->parent = x->parent;
	if (x == root)
		root = tmp;
	else if (x == x->parent->left)
		x->parent->left = tmp;
	else
		x->parent->right = tmp;
	tmp->left = x;
	x->parent = tmp;
}

void RBTree::RightRotate(RB_Node *x, RB_Node*& root) {
	RB_Node *tmp = x->left;
	x->left = tmp->right;
	if (tmp->right)
		tmp->right->parent = x;
	tmp->parent = x->parent;
	if (x == root)
		root = tmp;
	else if (x == x->parent->right)
		x->parent->right = tmp;
	else
		x->parent->left = tmp; 
	
	tmp->right = x;
	x->parent = tmp;
}
简单测试

int main() {
	RBTree rb_tree;
	vector<int> vec{10, 7, 8, 15, 5, 6, 11, 13, 12};
	for (auto ele: vec) {
		rb_tree.RB_insert(ele); 
	}
	printf("Construction Done\n");
	vector<int> vec2{12, 5, 15, 10};
	for (auto ele : vec2) {
		rb_tree.RB_erase(ele);
		rb_tree.displayTree();
		printf("\n---------------------------\n");
	}
	return 0;
	
} 
总结
  1. 详细的步骤讲解博客网上很多,但其实最关键的一点还是自己画图,自己找一张纸,画一画各种情况的图,思考这样操作之后为了维持性质5该如何涂色。
  2. 代码参考了算法导论上的伪代码,但书上将NULL考虑为了黑色哨兵值,而实际编程中没有这样考虑,因此一些地方指针是否为NULL还需要考虑清楚。
  3. 该过程中其实还读了《STL源码剖析》中关于红黑树的讲解,讲的也蛮好的,而且有插入代码,不过其设计时添加了一个header节点,但是我研究了一下发现我上面的代码中好像不需要这个header节点。删除代码书中没讲,我需要自己去找一下和自己写的对比一番。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值