红黑树浅析

Wincc7.5SP1 按钮操作记录(二次确认及语音播报) Wincc 7.5 SP1 按钮操作记录生成(含二次确认及语音播报) 前面一篇文章已经介绍了“语音播报WINCC报警”,本文是之前功能的延伸,记录按钮操作记录并实现播报。 操作记录及语音播报效果 <创建消息文本> 1. 创建消息文本 消息编号100,消息等级“系统无确认”,消息类型“操作员输入消息”,消息文本“循环水泵启动操作”,其他参数默认。 消息编号101,消息等级“系 阅读详情

红黑树浅析 


本文参考博客如下:
1.http://www.cnblogs.com/skywang12345/p/3245399.html
2.http://blog.youkuaiyun.com/eson_15/article/details/51144079
3.http://blog.youkuaiyun.com/v_JULY_v/article/details/6124989

概要


目录

1 红黑树的介绍

2 红黑树的应用

3 红黑树的操作流程

4 红黑树的基本操作(一)左旋和右旋

5 红黑树的基本操作(二)添加

6 红黑树的基本操作(三)删除


R-B Tree简介   


 R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。

红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

注意:
(01) 特性(3)中的叶子节点,是只为空(NIL或null)的节点。
(02) 特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

红黑树示意图如下:

 

红黑树的应用


红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。
例如,Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。

红黑树的操作流程

红黑树的结点

#ifndef _RB_TREE_NODE_H_
#define _RB_TREE_NODE_H_

//#ifndef bool
//typedef enum bool{false	,true}bool;
//#endif
#ifndef RB_TREE_COLOR
typedef enum RB_TREE_COLOR{
	RB_RED = 0,
	RB_BLACK = 1
};
#endif

/************************************************************************/
/*		名称:红黑树的结点

		时间:2016-08-19	
*/
/************************************************************************/

template <class T>				//结点元素类型
struct rb_tree_node
{
	T key;						//关键字(键值)
	RB_TREE_COLOR color;
	
	rb_tree_node<T> *left;
	rb_tree_node<T> *right;
	rb_tree_node<T> *parent;


	rb_tree_node(const T &val = 0 , const RB_TREE_COLOR &col = RB_RED 
		/*, const rb_tree_node *p = NULL*/);
	~rb_tree_node(void);

};

#include "rb_tree_node.cpp"

#endif


#ifndef _RB_TREE_NODE_CPP_
#define _RB_TREE_NODE_CPP_
#include "rb_tree_node.h"

template <class T>	
rb_tree_node<T>::rb_tree_node(const T &val , const RB_TREE_COLOR &col
							  /*, const rb_tree_node *p = NULL*/):
left(NULL) , right(NULL) ,parent(NULL) ,key(val) , color(col){
}

template <class T>	
rb_tree_node<T>::~rb_tree_node(void)
{
}

#endif



红黑树的基本操作(一)左旋和右旋


红黑树的基本操作是添加、删除。在对红黑树进行添加或删除之后,都会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。简单点说,旋转的目的是让树保持红黑树的特性。
旋转包括两种:左旋 和 右旋。下面分别对它们进行介绍。

   


/*************对红黑树节点x进行左旋操作 ******************/  
/* 
 * 左旋示意图:对节点x进行左旋 
 *     p                       p 
 *    /                       / 
 *   x                       y 
 *  / \                     / \ 
 * lx  y      ----->       x  ry 
 *    / \                 / \ 
 *   ly ry               lx ly 
 * 左旋做了三件事: 
 * 1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时) 
 * 2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右) 
 * 3. 将y的左子节点设为x,将x的父节点设为y 
 */ 
template <class T>
void rb_tree<T>::left_rotate(rb_tree_node<T> *x){
	rb_tree_node<T> *y = x->right ;		// 前提:这里假设x的右孩子为y。下面开始正式操作

	/* xy之间断开重连 */
	y->parent = x->parent;				// 将 “x的父亲” 设为 “y的父亲”
	x->right = y->left;					// 将 “y的左孩子” 设为 “x的右孩子”,即 将ly设为x的右孩子

	/* x p 之间断开重连  */
	if( !x->parent )					// 情况1:如果 “x的父亲” 是空节点,则将y设为根节点
		root = y;
	else if( x->parent->left == x )		// 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
		x->parent->left = y;
	else								// 情况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子”
		x->parent->right = y;
	x->parent = y;						// 将 “x的父节点” 设为 “y”

	/* y ly 之间断开重连 */
	if(y->left)
		y->left->parent = x;			// 将 “x” 设为 “y的左孩子的父亲”,即 将ly的父亲设为x
	y->left = x;						// 将 “x” 设为 “y的左孩子”

}

/*************对红黑树节点y进行右旋操作 ******************/  
/* 
 * 左旋示意图:对节点y进行右旋 
 *        p                   p 
 *       /                   / 
 *      y                   x 
 *     / \                 / \ 
 *    x  ry   ----->      lx  y 
 *   / \                     / \ 
 * lx  rx                   rx ry 
 * 右旋做了三件事: 
 * 1. 将x的右子节点赋给y的左子节点,并将y赋给x右子节点的父节点(x右子节点非空时) 
 * 2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右) 
 * 3. 将x的右子节点设为y,将y的父节点设为x 
 */  
template <class T>
void rb_tree<T>::right_rotate(rb_tree_node<T> *y){
	rb_tree_node<T> *x = y->left ;             // 前提:这里假设y的左孩子为x。下面开始正式操作


	/* xy之间断开重连 */
	x->parent = y->parent;				// 将 “y的父亲” 设为 “x的父亲”
	y->left = x->right;					// 将 “x的右孩子” 设为 “y的左孩子”,即 将rx设为y的左孩子

	/* x p 之间断开重连  */
	if( !y->parent )					// 情况1:如果 “y的父亲” 是空节点,则将x设为根节点
		root = x;
	else if( y->parent->left == y )		// 情况2:如果 y是它父节点的左孩子,则将x设为“y的父节点的左孩子”
		x->parent->left = x;
	else								// 情况3:(y是它父节点的右孩子) 将x设为“y的父节点的右孩子”
		x->parent->right = x;
	y->parent = x;						// 将 “y的父节点” 设为 “x”

	/* y ly 之间断开重连 */
	if(x->right)
		x->right->parent = y ;			// 将 “y” 设为 “x的右孩子的父亲”,即 将rx的父亲设为y
	x->right = y;						// 将 “y” 设为 “x的右孩子”
}


红黑树的基本操作(二)添加


将一个节点插入到红黑树中,需要执行哪些步骤呢?首先,将红黑树当作一颗二叉查找树,将节点插入;然后,将节点着色为红色;最后,通过旋转和重新着色等方法来修正该树,使之重新成为一颗红黑树。详细描述如下:

第一步: 将红黑树当作一颗二叉查找树,将节点插入。
       红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。
       好吧?那接下来,我们就来想方设法的旋转以及重新着色,使这颗树重新成为红黑树!

template <class T>
bool rb_tree<T>::insert(const T &val){
	/*	创建新结点 */
	rb_tree_node<T> *new_node = new rb_tree_node<T>(val);			
	
	/*  调用重载版本*/
	 return this->insert(new_node);
}
template <class T>
bool rb_tree<T>::insert(rb_tree_node<T> *new_node){
	rb_tree_node<T> *p ,*q;
	
	//p = NULL; q = NULL;
	/* start to search */
	q = p = this->get_root();
	while(p){
		q = p;
		if(p->key < new_node->key )
			p = p->right;
		else if( p->key > new_node->key )
			p = p->left;
		else
			return false;					//在tree找到相同key的结点, 返回false
	}

	/* judge left or right  */
	if(!q)				
		this->set_root(new_node);
	else if( q->key > new_node->key )
		q->left = new_node;
	else
		q->right = new_node;

	/* set new_node's parent */
	new_node->parent = q;

	/* fixup */
	insert_fixup(new_node);

	return true;
}


第二步:将插入的节点着色为"红色"。
       为什么着色成红色,而不是黑色呢?为什么呢?在回答之前,我们需要重新温习一下红黑树的特性:
(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
       将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。

第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。
       第二步中,将插入节点着色为"红色"之后,不会违背"特性(5)"。那它到底会违背哪些特性呢?
       对于"特性(1)",显然不会违背了。因为我们已经将它涂成红色了。
       对于"特性(2)",显然也不会违背。在第一步中,我们是将红黑树当作二叉查找树,然后执行的插入操作。而根据二叉查找数的特点,插入操作不会改变根节点。所以,根节点仍然是黑色。
       对于"特性(3)",显然不会违背了。这里的叶子节点是指的空叶子节点,插入非空节点并不会对它们造成影响。
       对于"特性(4)",是有可能违背的!
       那接下来,想办法使之"满足特性(4)",就可以将树重新构造成红黑树了。

下面看看流程到底是怎样实现这选择和重新着色的。

根据被插入节点的父节点的情况,可以将"当节点z被着色为红色节点,并插入二叉树"划分为三种情况来处理。
① 情况说明:被插入的节点是根节点。
    处理方法:直接把此节点涂为黑色。
② 情况说明:被插入的节点的父节点是黑色。
    处理方法:什么也不需要做。节点被插入后,仍然是红黑树。
③ 情况说明:被插入的节点的父节点是红色。
    处理方法:那么,该情况与红黑树的“特性(5)”相冲突。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为3种情况(Case)。

 现象说明处理策略
Case 1当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。

(01) 将“父节点”设为黑色。
(02) 将“叔叔节点”设为黑色。
(03) 将“祖父节点”设为“红色”。
(04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。

Case 2当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子

(01) 将“父节点”作为“新的当前节点”。
(02) 以“新的当前节点”为支点进行左旋。

Case 3当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子

(01) 将“父节点”设为“黑色”。
(02) 将“祖父节点”设为“红色”。
(03) 以“祖父节点”为支点进行右旋。





template <class T>
void rb_tree<T>::insert_fixup(rb_tree_node<T> *new_node){
	rb_tree_node<T> *P , *G , *S;

	/*	
	*	注 :这里虽然多了内存空间, 但节省了寻找结点的父结点 ,
	*	祖父结点,兄弟结点的时间 ,因为需要指针一个个找
	*/

	/* 循环调整 */
	while (true){
		
		/* init P  */
		P = new_node->parent;

		/*	情况1:插入的点为根结点	*/
		if (!P){
			new_node->color = RB_BLACK;
			break;
		}
		/*	情况2:插入的点的父结点为黑色, 什么都不做, 结束调整	*/
		else if( P->color == RB_BLACK )
			break;
		/*	情况2:插入的点的父结点为红色 , 又有4种情况	*/
		else{
			/*	若父节点是祖父节点的左子节点,下面else与其相反	*/

			/* 当父结点P为红色时, 祖父结点G 是肯定存在的, 否则p就是root ,产生矛盾*/
			G = P->parent;

			if ( P == G->left){
				/*	叔叔节点是红色,就是情况3的 c , d		*/
				S = G->right;
				if( S && S->color == RB_RED ){
					/*	情况3的 d	*/
					if ( new_node == P->right ){
						new_node = P;
						left_rotate(new_node);

						/*	完成旋转后 , 重新设定PGS	*/
						P = new_node->parent;
						G = P->parent;
						S = G->right;
					}
					/*	情况3的 c	*/
					P->color = RB_BLACK;
					G->color = RB_RED;
					S->color = RB_BLACK;					//可以看出, 这是情况cd与ab处理的区别
					new_node = G;
				}
				/*	叔叔节点是黑色,就是情况3的 a , b	*/
				else{
					/*	情况3的 b	*/
					if ( new_node == P->right ){
						new_node = P;
						left_rotate(new_node);

						/*	完成旋转后 , 重新设定PGS	*/
						P = new_node->parent;
						G = P->parent;
						//S = G->right;
					}
					/*	情况3的 a	*/
					P->color = RB_BLACK;
					G->color = RB_RED;
					//new_node = G;
					/*	以“祖父节点”为支点进行右旋。	*/
					right_rotate(G);
				}
			}
			/*	若父节点是祖父节点的右子节点,与上面的完全相反,镜像处理  */
			else{
				/*	情况3的 c , d		*/
				S = G->left;
				if( S && S->color == RB_RED ){
					/*	情况3的 d	*/
					if ( new_node == P->left ){
						new_node = P;
						right_rotate(new_node);

						/*	完成旋转后 , 重新设定PGS	*/
						P = new_node->parent;
						G = P->parent;
						S = G->left;
					}
					/*	情况3的 c	*/
					P->color = RB_BLACK;
					G->color = RB_RED;
					S->color = RB_BLACK;					//可以看出, 这是情况cd与ab处理的区别
					new_node = G;
				}
				/*	情况3的 a , b	*/
				else{
					/*	情况3的 b	*/
					if ( new_node == P->left ){
						new_node = P;
						right_rotate(new_node);

						///*	完成旋转后 , 重新设定PGS	*/
						P = new_node->parent;
						G = P->parent;
						//S = G->left;
					}
					/*	情况3的 a	*/
					P->color = RB_BLACK;
					G->color = RB_RED;
					//new_node = G;
					/*	以“祖父节点”为支点进行左旋。	*/
					left_rotate(G);
				}
			}
		}
	}
}



红黑树的基本操作(三)删除


将红黑树内的某一个节点删除。需要执行的操作依次是:首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除;然后,通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。详细描述如下:

第一步:将红黑树当作一颗二叉查找树,将节点删除。
       这和"删除常规二叉查找树中删除节点的方法是一样的"。分3种情况:
       ① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。
       ② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
       ③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给"被删除节点"之后,再将后继节点删除。这样就巧妙的将问题转换为"删除后继节点"的情况了,下面就考虑后继节点。 在"被删除节点"有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然"的后继节点"不可能双子都非空,就意味着"该节点的后继节点"要么没有儿子,要么只有一个儿子。若没有儿子,则按"情况① "进行处理;若只有一个儿子,则按"情况② "进行处理。





/*********************** 得到当前结点的后继结点,即大于节点x的最小节点  **********************/  
template <class T>
rb_tree_node<T> *rb_tree<T>::successor(rb_tree_node<T> *p_cur){
	rb_tree_node<T> *p_ret , *p_par ;

	/*	判断条件	*/
	if(	p_cur ){
		if ( p_cur->right ){
			/*	一直左下,找到叶结点	*/
			p_ret = p_cur->right;
			while(p_ret->left)
				p_ret = p_ret->left;
		}
		else{
			/*	没有右结点,则查看父结点与当前结点的关系	*/

			//如果x没有右子节点,会出现以下两种情况:  
			//1. x是其父节点的左子节点,则x的后继节点为它的父节点  
			//2. x是其父节点的右子节点,则先查找x的父节点p,然后对p
			//再次进行这两个条件的判断  
			p_par = p_ret->parent;
			while( !p_par&&p_par->right == p_ret ){	
				p_ret = p_par;
				p_par = p_ret->parent;
			}
			p_ret = p_par;
		}
	}
	return p_ret;
}
/*********************** 删除红黑树中的节点 **********************/  
template <class T>
bool rb_tree<T>::remove(const T &val){
	rb_tree_node<T> *rm_node ;
	bool b_ret;

	b_ret = false;
	if( !( rm_node = this->search(val)) )
		b_ret = this->remove(rm_node);

	return b_ret;
}

/*********************** 删除红黑树中的节点,重载版本 **********************/  
template <class T>
bool rb_tree<T>::remove(rb_tree_node<T> *rm_node){
	rb_tree_node<T> *Y ,*X;
	RB_TREE_COLOR Y_color;

	// 若“z的左孩子” 或 “z的右孩子”为空,则将“z”赋值给 “y”;
	// 否则,将“z的后继节点”赋值给 “y”。
	if( !rm_node->left || !rm_node->right )
		/* case 1 2 3 */
		Y = rm_node;
	else
		/*	case 4	*/
		Y = this->successor(rm_node);

	// 若“y的左孩子” 不为空,则将“y的左孩子” 赋值给 “x”;
	// 否则,“y的右孩子” 赋值给 “x”。 注意右孩子有可能为空
	if( Y->left )
		X = Y->left;
	else
		X = Y->right;

	/* 开始处理 P与X的连接	*/

	// 将“y的父节点” 设置为 “x的父节点”
	if(X)
		X->parent = Y;

	if(!Y->parent)
		root = X;						//删除的结点y为根结点
	else if (Y->parent->left == Y)
		// 情况2:若“y是它父节点的左孩子”,则设置“x” 为 “y的父节点的左孩子”
		Y->parent->left = X;
	else
		// 情况3:若“y是它父节点的右孩子”,则设置“x” 为 “y的父节点的右孩子”
		Y->parent->right = X;
	
	/*	若Y是后继结点	*/
	if ( Y != rm_node ){
		// 若“y的值” 赋值给 “z”。注意:这里只拷贝z的值给y,而没有拷贝z的颜色!!!
		rm_node->key = Y->key;			
	}
	
	Y_color = Y->color;
	/*	删除结点Y	*/
	delete Y; Y = NULL;

	/*因为删除了一个黑色结点, 就要开始调整*/
	if( Y_color == RB_BLACK )
		this->remove_fixup(X);

	return true;
}


第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。
       因为"第一步"中删除节点之后,可能会违背红黑树的特性。所以需要通过"旋转和重新着色"来修正该树,使之重新成为一棵红黑树。

删除某个节点后,会用它的后继节点来填上,并且后继节点会设置为和删除节点同样的颜色,所以删除节点的那个位置是不会破坏平衡的。可能破坏平衡的是后继节点原来的位置,因为后继节点拿走了,原来的位置结构改变了,这就会导致不平衡的出现。所以后面的remove_fixup方法中传入的参数也是后继节点的子节点.

  为了方便下文的叙述,我们现在约定:后继节点的子节点称为“当前节点”。

  删除操作后,如果当前节点是黑色的根节点,那么不用任何操作,因为并没有破坏树的平衡性,即没有违背红-黑树的规则,这很好理解。如果当前节点是红色的,说明刚刚移走的后继节点是黑色的,那么不管后继节点的父节点是啥颜色,我们只要将当前节点涂黑就可以了,红-黑树的平衡性就可以恢复。但是如果遇到以下四种情况,我们就需要通过变色或旋转来恢复红-黑树的平衡了。

 现象说明处理策略
Case 1x是"黑+黑"节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。

(01) 将x的兄弟节点设为“黑色”。
(02) 将x的父节点设为“红色”。
(03) 对x的父节点进行左旋。
(04) 左旋后,重新设置x的兄弟节点。

Case 2x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。

(01) 将x的兄弟节点设为“红色”。
(02) 设置“x的父节点”为“新的x节点”。

Case 3x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。

(01) 将x兄弟节点的左孩子设为“黑色”。
(02) 将x兄弟节点设为“红色”。
(03) 对x的兄弟节点进行右旋。
(04) 右旋后,重新设置x的兄弟节点。

Case 4x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的,x的兄弟节点的左孩子任意颜色。

(01) 将x父节点颜色 赋值给 x的兄弟节点。
(02) 将x父节点设为“黑色”。
(03) 将x兄弟节点的右子节设为“黑色”。
(04) 对x的父节点进行左旋。
(05) 设置“x”为“根节点”。








template <class T>
void rb_tree<T>::remove_fixup(rb_tree_node<T> *rep_node){
	rb_tree_node<T> *p ,*s , *ls , *rs;

	/*	先处理情况3 ,情况1 ,2 在循环结束可以直接将rep_node 设置为black即可	*/
	while(rep_node != this-get_root() && rep_node->color == RB_BLACK){
		/*	X不可能为空,在上面的函数可以证实	*/
		p = rep_node->parent;
		if ( p->left == rep_node ){
			/*rep_node是左子节点,下面else与这里的刚好相反 */
			s = p->right;
			if ( s->color == RB_RED ){
				/*	case 1	,因为兄弟结点为red , 其子女必定为black, 所以不用判断*/
				p->color = RB_RED;
				s->color = RB_BLACK;
				this->left_rotate(p);
				s = p->right;
			}
			/*	这里不加else的原因 , 看流程图 */
			ls = s->left;
			rs = s->right;

			if( ( ls &&  ls->color == RB_BLACK ) && ( rs && rs->color == RB_BLACK ) ){
				/* case 2 */
				s->color = RB_RED ;
				rep_node = p;
			}
			else{
				/* case 3 , 4  */
				if (ls && ls->color == RB_RED && rs && rs->color == RB_BLACK ){
					/* case 3   */
					ls->color = RB_BLACK ; 
					s->color = RB_RED ;
					this->right_rotate(s);
					s = p->right;
					ls = s->left;
					rs = s->right;
				}
				/* case  4  */
				s->color = p->color ;
				p->color = RB_BLACK ;
				rs->color = RB_BLACK;
				this->left_rotate(p);
				break;
			}
		}
		else{
			/*	rep_node是右子节点 , 与第一个if对应 */
			s = p->left;
			if ( s->color == RB_RED ){
				/*	case 1	,因为兄弟结点为red , 其子女必定为black, 所以不用判断*/
				p->color = RB_RED;
				s->color = RB_BLACK;
				this->right_rotate(p);
				s = p->left;
			}
			/*	这里不加else的原因 , 看流程图 */
			ls = s->left;
			rs = s->right;

			if( ( ls &&  ls->color == RB_BLACK ) && ( rs && rs->color == RB_BLACK ) ){
				/* case 2 */
				s->color = RB_RED ;
				rep_node = p;
			}
			else{
				/* case 3 , 4  */
				if (rs && rs->color == RB_RED && ls && ls->color == RB_BLACK ){
					/* case 3   */
					rs->color = RB_BLACK ; 
					s->color = RB_RED ;
					this->right_rotate(s);
					s = p->left;
					ls = s->left;
					rs = s->right;
				}
				/* case  4  */
				s->color = p->color ;
				p->color = RB_BLACK ;
				ls->color = RB_BLACK;
				this->left_rotate(p);
				break;
			}
		}
	}
	root->color = RB_BLACK;
}

最后:有人写了个在线演示版本,挺不错的:http://sandbox.runjs.cn/show/2nngvn8w

Python爬虫教程:抓取区块链交易信息及加密货币市场数据 随着区块链技术和加密货币的迅猛发展,区块链交易和加密货币市场的数据逐渐成为金融、技术、经济研究等领域的热点。对于开发者和研究者而言,实时获取区块链交易数据和加密货币市场行情,对于投资分析、市场预测、技术研究等具有重要的参考价值。本文将通过Python爬虫技术,介绍如何抓取区块链交易信息及加密货币市场数据,详细阐述数据获取的原理、技术方案、实现方法以及抓取到的数据的存储与分析。我们将依托最新的爬虫技术,如requestsSeleniumScrapy,以及相关的数据处理库(如pandasmatplotlib。 阅读详情

相关推荐

保姆级教程:用DaVinci Configurator & Developer从零搭建AUTOSAR OS(TC2xx三核实战)

本文提供了一份详细的AUTOSAR OS配置指南,使用DaVinci Configurator和Developer工具从零开始搭建Infineon TC2xx三核系统。涵盖多核配置协同逻辑、版本兼容性陷阱及验证错误的解决方案,帮助开发者在3小时内构建可运行的多核OS框架。

weixin_33940469的博客 313

红黑树

红黑树的特性: (1)每个节点或者是黑色,或者是红色。 (2)根节点是黑色。 (3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!] (4)如果一个节点是红色的,则它的子节点必须是黑色的。 (5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。注意: (01) 特性(3)中的叶子节点,是只为空(NIL或null)的节点。 (

青箫的博客 1047

51单片机通过计时器实现倒计时

51单片机通过计时器操作两位8段阳极数码管,实现倒计时

一份耕耘,一分收获 3万+

第十四章 红黑树——C++代码实现

红黑树的介绍 红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树。红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。除了具备该特性之外,红黑树还包括许多额外的信息。 红黑树的每个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。红黑树的特性:(1) 每个节点或者是黑...

weixin_33691817的博客 436

LangChain + Milvus Lite:从零搭建本地RAG知识库

本文详细介绍了如何利用LangChain、Milvus Lite和gte-large-zh模型,在本地从零搭建一个完整的RAG知识库系统。文章涵盖了环境配置、文档处理与向量化、本地向量库构建以及检索问答实现的全流程,重点突出了完全本地化部署带来的数据隐私安全和成本优势,为开发者提供了一条实践性极强的技术路径。

weixin_29044713的博客 443

Dify视觉模型节点避坑指南:我用Qwen2.5-VL做OCR识别时踩过的3个坑(及解决方案)

本文分享了使用Dify平台和Qwen2.5-VL视觉模型进行OCR票据识别时遇到的三个常见问题及解决方案,包括文件变量传递、结构化输出和条件分支判断失效。通过实战经验,帮助开发者优化多模态模型在财务自动化中的应用,提升OCR识别精度和效率。

weixin_29284885的博客 403

红黑树性质

红黑树(red-black tree)是许多“平衡的”查找树中一种,它能保证在最坏情况下,基本的动态集合操作的时间为O(lgn)。 红黑树是一种二叉查找树,但在每个节点上增加一个存储位表示节点的颜色,可以是red或black,通过对任何一条从根到叶子节点路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而近乎平衡的。 树中每个节点包含五个域:color,key,left,...

自由 自信 不失望 2656

程序化广告行业(23/89):深度剖析广告效果评估指标

大家好,我一直希望能和大家一起在技术领域不断探索、共同进步。在之前的文章中,我们了解了程序化广告中腾讯的业务布局和广告效果评估的基础概念,今天让我们进一步深入剖析广告效果评估的各项指标,这对理解程序化广告的运作机制至关重要

yyy173611的博客 4864

算法之路——红黑树

  这儿主要给出红黑树的代码实现,和我的一些理解。具体的红黑树介绍在算法导论的163页,也可以自己google或百度。   红黑树简介:    红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:   性质1. 节点是红色或黑色。   性质2. 根节点是黑色。   性质3 每个叶节点是黑色的。   性...

weixin_30600503的博客 248

手把手教你学simulink(8.3)--实例:基于 Simulink 的自动驾驶汽车速度控制系统设计与仿真

基于 Simulink 的自动驾驶汽车速度控制系统设计与仿真。

MHD0815的博客 1208

手把手教你学Simulink——无刷直流电机(BLDC)霍尔传感器(Hall Sensor)信号处理电路仿真

在中,转子位置靠​ 给出,换相逻辑(Commutation Table)据此决定。实际霍尔输出是,且常有:比较迟滞(内置或外加分压)RC 低通(防 EMI 尖刺)与 MCU 间想让你霍尔三相 UVW 理想方波(120° each, 换相每 60°elec)经观察:原始 HU/HV/HW、滤波后波形、整形后 TTL 电平验证换相表(001→100→010…)与转子转向匹配是破局关键。

amy_mhd的博客 38

红黑树系列之一:红黑树的概述

一、红黑树的定义

lpp0900320123的专栏 4151

Orange3实战教程:文本挖掘---主题建模

主题建模通过LDA、LSI或HDP算法分析文档集合,自动发现隐含主题结构。组件输入为文档语料库,输出包括带主题权重的语料库、主题词分布及完整主题数据。不同算法特性各异:LSI允许正负权重但解释性较弱,LDA更易解释,HDP参数复杂需谨慎设置。结果可结合词云、热力图等可视化工具分析主题特征及分布。示例显示如何通过预处理文本、选择算法(如提取5个LDA主题),并利用MDS和箱线图探索主题间关系及关键词分布。

err2008的专栏 362

浅析红黑树

一、什么是红黑树???   红黑树首先是一棵搜索二叉树,树中的每一个结点的颜色不是黑色就是红色。它的特性如下:   1、根节点是黑色   2、每一个结点不是黑色就是红色   3、不能有连续的两个红色结点   4、从任意一个结点出发,到后代中空指针的路径上,均包含相同数量的黑色结点。 例如:    二、为什么要有红黑树???   最开始我们学习了搜索二叉树,但是

Sunnylunch-blog 4620

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

浅析红黑树(RBTree)原理及实现

我们在上一篇博客认识到了平衡二叉树(AVLTree),了解到AVL树的性质,其实平衡二叉树最大的作用就是查找,AVL树的查找、插入和删除在平均和最坏情况下都是O(logn)。AVL树的效率就是高在这个地方。如果在AVL树中插入或删除节点后,使得高度之差大于1。此时,AVL树的平衡状态就被破坏,它就不再是一棵二叉树;为了让它重新维持在一个平衡状态,就需要对其进行旋转处理, 那么创建一颗平衡二叉树的成...

芮萌萌的博客 9万+

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

二叉树&平衡二叉树&二叉查找树&红黑树浅析

本篇作为集合类源码浅析のJDK1.8HashMap红黑树的前置篇个人笔记,从各自的特点到演变过程,介绍数据结构中的各类树结构。

西岭千秋雪的博客 1301

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

浅析红黑树底层原理及实现

我们在上一篇博客认识到了平衡二叉树(AVLTree),了解到平衡二叉树的性质,其实平衡二叉树最大的作用就是查找,AVL树的查找、插入和删除在平均和最坏情况下都是O(logn)。AVL树的效率就是高在这个地方。如果在AVL树中插入或删除节点后,使得高度之差大于1。此时,AVL树的平衡状态就被破坏,它就不再是一棵二叉树;为了让它重新维持在一个平衡状态,就需要对其进行旋转处理, 现在呢,我们来思考一下虽...

qq_39344902的博客 4521

红黑树的定义和实现原理浅析以及手写一棵Java版红黑树

目录一、二叉搜索树1、 二叉搜索树的概念2、二叉搜索树查找节点的过程3、查找性能分析4、如何避免二叉搜索树出现查询效率过低的问题?二、AVL树1、AVL树的特点2、为什么有了AVL平衡树还需要红黑树呢?三、红黑树1、红黑树概念2、红黑树的特点3、红黑树的定义4、红黑树中的操作5、红黑树插入情景分析四、手写一棵红黑树1、代码2、插入测试 在学习红黑树的实现原理之前,首先要明白为什么会出现红黑树?它是怎么来的?它存在的意义是什么以及解决了什么问题?要搞懂这些问题,就不得不先谈谈二叉搜索树和AVL树了。 一、

A_K_L的博客 805

浅析红黑树!【建议收藏】

前些天,我们讲解了搜索二叉树和AVL树,也知道了AVL树的自平衡机制,是如何进行旋转的,也知道了对于AVL树来说,查找数值的时间复杂度在O(logN)内,也就是说整棵树的深度,就是最大的查找次数。但是在AVL树在进行自平衡旋转时,还是耗费了大量的时间,所以就有了后来的红黑树。具体红黑树是什么?我们往下看。 前期文章:二叉树的概念以及搜索二叉树。 ​ 平衡二叉树(AVL树),原来如此!!! 本期文章源码:GitHub 一、2-3查找树 首先要深刻理解红黑树的性质,我们还得来看一下2-3树是个什么情况。

x0919的博客 665

浅析红黑树算法

红黑树简介红黑树是一种自平衡二叉查找树,也有着二叉搜索树的特性,保持着右边始终大于左边结点key的特性。前面提到过的AVL树,也是二叉搜索树的一种变形,红黑树没有达到AVL树的高度平衡,换句话说,它的高度,并没有AVL树那么高的要求,但他的应用却更加的广泛,实践中是相当高效的,他可以在O(log n)的时间内做查找、插入、删除操作。在C++ STL中,set、...

weixin_34116110的博客 174

HashMap原理浅析(关于红黑树是什么?)

HashMap 1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端。 数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难; 链表 链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。 哈希表 那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希

weixin_45105261的博客 4902
上一篇: 动态规划之背包问题(一)
下一篇: 有关C++模板(template)的编译错误“error LNK2019: 无法解析的外部符号”的分析
big_bit
博客等级 码龄11年 67粉丝 40原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值