红黑树学习记录

红黑树是什么

红黑树是一种二叉树。树的结点具有“color”之类的二值属性。

红黑树的性质

红黑树具有一下性质:
1.根结点必须是黑色
2.叶子结点必须是黑色
3.红色结点的两个子节点必须是黑色(红色结点不能相邻)
4.对于任意节点,它到所有叶子结点的路径上具有相同数量的黑结点(3、4两点决定了最长路径的长度最长是最短路径2倍-1,保证了相对平衡)

红黑树的应用场景

1.cfs调度(key是task的vruntime)
2.Epoll中管理io
3.定时器
4.nginx
5.hashmap中冲突的组织(冲突数量小于阈值时用链表,大于阈值用红黑树)

红黑树和AVL树的区别

1.avl是完全平衡的,最长路径最多比最短路径长1,而红黑树最长路径最多是最短路径的2倍左右。所以用来查找时,红黑树可能比avl多1次。
2.avl树删除时,最坏的情况下需要旋转logn次以维持平衡,而红黑树最多需要三次。
综上,在删除操作较多的场景下红黑树统计性能更好。

红黑树的实现

构造一个红黑树

根据需要,value或key可以用宏或模板抽象。本文简单起见固定写法。
结点:

class rbNode {
	friend class rbTree;
public:
	rbNode(int k, int v) : _key(k), _value(v), _Red(true) {}
	rbNode() : _Red(true) {}
private:
	int _key;
	int _value;
	bool _Red;
	rbNode* _parent;
	rbNode* _left;
	rbNode* _right;
};

树:

class rbTree {
public:
	void Insert(rbNode* n);
	void Delete(rbNode* n);
private:
	void RotateLeft(rbNode* n);		//调整黑高时使用
	void RotateRight(rbNode *n);	//调整黑高时使用
	void fix_up(rbNode *n);			//插入或删除时,如果改变平衡则调用
	bool isLeftChild(rbNode *n);	//判断当前结点是父节点的左子树还是右子树
	rbNode *GetUncle(rbNode *n);	//获取指定结点的叔叔结点
private:
	rbNode* root;
	rbNode* NIL;
};

工具函数:

bool rbTree::isLeftChild(rbNode* n) {
	// need check parent in caller
	return n->_parent->_left == n;
}
rbNode* rbTree::GetUncle(rbNode* n) {
	rbNode *grandpa = n->parent->parent;
	return isLeftChild(n->parent) ? grandpa->_right : grandpa->_left;
}

旋转操作

旋转操作涉及到三对连接(6个指针),按一定的顺序设置指针即可。需要注意的是判断父节点是否为root。
右旋示意图
上图为右旋示意图,绿色箭头为需要改变的连接

void rbTree::RotateRight(rbNode* n) {
	rbNode* prt = n->_parent;
	if (prt == root) {
		root = n;
		n->_parent = NIL;
	}
	else {
		rbNode* grd = prt->_parent;
		if (grd->_left == prt) {
			grd->_left = n;
		}
		else {
			grd->_right = n;
		}
		n->_parent = grd;
	}
	prt->_parent = n;
	n->_right->_parent = prt;
	prt->_left = n->_right;
	n->_right = prt;
}

void rbTree::RotateLeft(rbNode* n) {
	rbNode* prt = n->_parent;
	if (prt == root) {
		root = n;
		n->_parent = NIL;
	}
	else {
		rbNode* grd = prt->_parent;
		if (isLeftChild(prt)) {
			grd->_left = n;
		}
		else {
			grd->_right = n;
		}
		n->_parent = grd;
	}
	n->_left->_parent = prt;
	prt->_right = n->_left;
	n->_right = prt;
	prt->_parent = n;
}

插入

步骤:
1.找到插入位置
2.插入结点
3.如果父节点是红色结点,则需要调整

void rbTree::Insert(rbNode* n) {
	if (_root == NIL) {
		_root = n;
		n->Red = false;
		return;
	}
	rbNode *cur = _root, *prt;
	while (cur != NIL) {
		prt = cur;
		if (n->key > cur->key) {
			cur = cur->_left;
		}
		else {
			cur = cur->_right;
		}
	}
	if (prt->key > n->key) {
		prt->_left = n;
	}
	else {
		prt->_right = n;
	}
	n->_parent = prt;
	if (prt->Red) {
		fix_up(n);
	}
}

插入的调整有三种情况:
1.叔父结点存在且为红色,这样的话只需要把父节点和叔父结点变为黑色,祖父结点变为红色,并递归调整祖父结点即可。
2.叔父结点存在且为黑色,这需要先将父节点进行旋转(注意判断当前节点、父节点和祖父结点的位置,如果是直线则直接旋转,是折线则先对当前节点旋转成为直线,再旋转父节点),然后将祖父节点和父节点变色。
3.如果叔叔结点不存在,则将父节点旋转,然后把父节点和祖父节点变色即可。
代码(待补充)

删除(比较麻烦,有空再研究)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值