数据结构与算法(三) 红黑树与二叉查找树

红黑树

定义和性质

R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种自平衡的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。
性质:在这里插入图片描述
在这里插入图片描述
性质4也可说成:
(4)从根节点到叶子节点,不会出现两个连续的红色节点。

应用

用途
运用红黑树主要用其两个性质:
A.通过键值对方式存储信息
运用:
1、多个客户端与服务器建立链接时,此时链接为socket实质就是一个int型数字。但是每个客户端都有自己的id,此时socket与客户端id有个映射关系。若有大量客户端链接时,我们就可采用红黑树的key-value(socket,id)方式来存储这个信息。以便后期查找socket与id之间的相互查找;
2、内存中使用:
整块内存还没有没配时,mallo分配内存时将其假如红黑树中,后期free时就从红黑树找对应的块将其释放;
B.使用其中序遍历的特点(按顺序存储)
1、进程的调度:
有些进程处于等待状态(在某个时刻会运行),存入红黑树中。当在某个时刻准备运行时,利用红黑树的中序遍历,将其小于此时间段的进程拿取出来,准备执行。

红黑树的操作

创建代码

typedef int KEY_TYPE;

typedef struct _rbtree_node {
	//此为红黑树的性质
	unsigned char color;
	struct _rbtree_node *right;
	struct _rbtree_node *left;
	struct _rbtree_node *parent;
	
	KEY_TYPE key;
	void *value;
} rbtree_node;

typedef struct _rbtree {
	rbtree_node *root;
	rbtree_node *nil; //NULL 所有的叶子结点都指向他 
} rbtree;

左旋

左旋和右旋都是为了调节二叉树平衡而存在的。

    以x为节点进行左旋 
                              z
   x                          /                  
  / \      --(左旋)-->       x
 y   z                      /
                           y

对x进行左旋,意味着,将“x的右孩子”设为“x的父亲节点”;即,将 x变成了一个左节点(x成了为z的左孩子)!。 因此,左旋中的“左”,意味着“被旋转的节点将变成一个左节点”。

在这里插入图片描述
对x进行左旋,意味着"将x变成一个左节点"。

代码实现:
//左旋,T为TREE,x为待旋转的节点
//注意6根指针:x节点与父结点的两根指针,x与右节点的两根,右节点的左子节点的两根指针
void rbtree_left_rotate(rbtree *T, rbtree_node *x) {

	rbtree_node *y = x->right;  // x  --> y  ,  y --> x,   right --> left,  left --> right
								//假设的前提:x的右孩子为y
	x->right = y->left; // 将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子
	if (y->left != T->nil) { // 将 “x” 设为 “y的左孩子的父亲”,即 将β的父亲设为x
		y->left->parent = x;
	}

	y->parent = x->parent; //将 “x的父亲” 设为 “y的父亲”
	if (x->parent == T->nil) { //若x父结点为空结点,将y设为根节点
		T->root = y;
	} else if (x == x->parent->left) { //若x为其父结点的左孩子,则将Y设为“x父结点的左孩子”
		x->parent->left = y;
	} else { //(若x为其父结点的右孩子)则将y设为“x父结点的右孩子””
		x->parent->right = y;
	}

	y->left = x; //将x设为y的左孩子
	x->parent = y; //将x的父结点设为y
}

详情案例:在这里插入图片描述

右旋

左旋和右旋都是为了调节二叉树平衡而存在的。

(以x为节点进行右旋)
                              y
   x                            \                 
  / \      --(右旋)-->           x
 y   z                            \
                                   z

对x进行右旋,意味着,将“x的左孩子”设为“x的父亲节点”;即,将 x变成了一个右节点(x成了为y的右孩子)! 因此,右旋中的“右”,意味着“被旋转的节点将变成一个右节点”。

在这里插入图片描述
对x进行左旋,意味着"将x变成一个左节点"。

代码实现
//右旋
//小技巧:将rbtree_left_rotate中的左改成右,x改成y,改完后左旋就变成了右旋
void rbtree_right_rotate(rbtree *T, rbtree_node *y) {

	rbtree_node *x = y->left; // 前提:这里假设y的左孩子为x。下面开始正式操作

	y->left = x->right; // 将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子
	if (x->right != T->nil) {
		x->right->parent = y; // 将 “y” 设为 “x的右孩子的父亲”,即 将β的父亲设为y
	}

	x->parent = y->parent; // 将 “y的父亲” 设为 “x的父亲”
	if (y->parent == T->nil) {
		T->root = x; // 情况1:如果 “y的父亲” 是空节点,则将x设为根节点
	} else if (y == y->parent->right) {
		y->parent->right = x; // 情况2:如果 y是它父节点的右孩子,则将x设为“y的父节点的左孩子”
	} else {
		y->parent->left = x; // 情况3:(y是它父节点的左孩子) 将x设为“y的父节点的左孩子”
	}

	x->right = y; // 将 “y” 设为 “x的右孩子”
	y->parent = x;  // 将 “y的父节点” 设为 “x”
}

详情案例:
在这里插入图片描述
仔细观察上面"左旋"和"右旋"的示意图。我们能清晰的发现,它们是对称的。无论是左旋还是右旋,被旋转的树,在旋转前是二叉查找树,并且旋转之后仍然是一颗二叉查找树。
在这里插入图片描述

添加

注意:
1、添加前的树已经满足了红黑树的性质;
2、添加的节点为红色,这样更容易满足红黑树性质;
3、节点添加后,会有可能其父层、祖父层甚至更往上调整,所以整个是一个迭代的过程,且迭代一次后要将记录z指向下一次迭代的节点,一般是最高层。

为了满足红黑树的5条性质,一般插入结点时,将结点设置为红色,这样不会改变黑色的高度,这样更容易满足性质。
若当前结点为红色,待插入的位置其父结点也为红色,就要进行调整,recolor。

需要调整分为三种情况:
在这里插入图片描述

  1. (Case 1)叔叔是红色

1.1 现象说明
当前节点(即,被插入节点)的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。

1.2 处理策略
(01) 将“父节点”设为黑色。
(02) 将“叔叔节点”设为黑色。
(03) 将“祖父节点”设为“红色”。
(04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。
在这里插入图片描述
2. (Case 2)叔叔是黑色,且当前节点是右孩子

2.1 现象说明
当前节点(即,被插入节点)的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子

2.2 处理策略
(01) 将“父节点”作为“新的当前节点”。
(02) 以“新的当前节点”为支点进行左旋。
在这里插入图片描述
3. (Case 3)叔叔是黑色,且当前节点是左孩子

3.1 现象说明
当前节点(即,被插入节点)的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子

3.2 处理策略
(01) 将“父节点”设为“黑色”。
(02) 将“祖父节点”设为“红色”。
(03) 以“祖父节点”为支点进行右旋。
在这里插入图片描述


//在插入之前就已经是一颗红黑树了,插入是要红色容易满足性质,
//黑色会改变此路径下黑色节点的高度,就需要不断调整
//什么情况下需要调整?插入红节点会违背性质4,也就是当前节点是红色,同时父结点也是红色的,此时需要调整

void rbtree_insert(rbtree *T, rbtree_node *z) {
//红黑树只能在叶子节点插入
	rbtree_node *y = T->nil;
	rbtree_node *x = T->root;

	while (x != T->nil) { //查找待插入的位置
		y = x;
		if (z->key < x->key) {
			x = x->left;
		} else if (z->key > x->key) {
			x = x->right;
		} else { //Exist
			return ;
		}
	}//找到了待插入的叶子节点处,为y

	z->parent = y; //
	if (y == T->nil) { //将z结点插入其中
		T->root = z;//设为根节点
	} else if (z->key < y->key) {
		y->left = z;//设为Y的左子树
	} else {
		y->right = z;//设为y的右子树
	}

	//插入的点性质修改,因为两个节点之间的有两个指针联系,都要记得修改
	z->left = T->nil;
	z->left = T->nil;
	z->right = T->nil;
	z->color = RED; //新结点置成红色

	rbtree_insert_fixup(T, z); //调整结点
}

//相邻的节点不可能为一黑一红 ,	//调整过程中,z始终是为红色
void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {
	//在调整过程中,还需要看其上层是否满足性质,直到根节点,所以要while循环来迭代
	while (z->parent->color == RED) { //z ---> RED 违背了性质4,根节点到叶子节点路径不可有连续的红节点
		if (z->parent == z->parent->parent->left) { //若parent为祖父节点的左子树,便于区分左旋右旋 //父红,且为左子树
			rbtree_node *y = z->parent->parent->right; //获取z的叔父结点
			if (y->color == RED) {//情况1:若叔父结点为红色,(若z的父节点和叔父结点都为红色,则祖父结点一定为黑色,红黑树性质决定的) //此时叔为红色节点,父红,违背了性质4,要将父、叔改为黑,祖父改为红 
			
				z->parent->color = BLACK; //将父结点/叔父结点置黑色
				y->color = BLACK;
				z->parent->parent->color = RED; //祖父结点置红

				z = z->parent->parent; //z --> RED //注意:此为迭代的关键://迭代的关键,下次迭代判断是否满足红黑树性质从其祖父节点开始
				//所以z一直是红色的
			} else { //情况2: 若叔父结点为黑色,叔父的黑高比z,违背了性质5
				if (z == z->parent->right) { //若z为父的右子树,将其父节点进行左转
					z = z->parent; //
					rbtree_left_rotate(T, z); //左旋,
				}
				//z为父的左子树时,将父结点转黑,祖父节点转红,对祖父节点进行右转。
				z->parent->color = BLACK;
				z->parent->parent->color = RED;
				rbtree_right_rotate(T, z->parent->parent);
			}
		}else { //父结点为祖父节点的右子树,父为红节点
			rbtree_node *y = z->parent->parent->left; //叔父结点
			if (y->color == RED) { //若叔父结点为红色,此时父,叔都为红色
				z->parent->color = BLACK; //此时修改方案是:叔、父改为黑色,祖父为红色
				y->color = BLACK;
				z->parent->parent->color = RED;

				z = z->parent->parent; //z --> RED 重点:因为是继续迭代的,z要指向修改后的,下次要迭代的节点,也就是祖父节点
			} else { //若叔父节点为黑色,父结点为祖父的右子树,父为红节点
				if (z == z->parent->left) {
					z = z->parent;
					rbtree_right_rotate(T, z);
				}

				z->parent->color = BLACK;
				z->parent->parent->color = RED;
				rbtree_left_rotate(T, z->parent->parent);
			}
		}
		
	}

	T->root->color = BLACK;
}

删除

二叉排序树

性质

二叉查找树(BST)具备什么特性呢?

1.左子树上所有结点的值均小于或等于它的根结点的值。

2.右子树上所有结点的值均大于或等于它的根结点的值。

3.左、右子树也分别为二叉排序树。

优点

利用二分查找的思想,使得查找所需的最大次数等于二叉查找树的高度。插入结点时,也是一层一层的比较大小,找到新结点适合插入的位置。

缺点

当插入的值几乎都比根节点小/大时,此时的二叉树就类似与链表的结构,这样效率就大打折扣。
在这里插入图片描述
为了解决 :二叉查找树多次插入结点可能导致不平衡的问题 ,就创建了自平衡的二叉查找树,也就是红黑树。

实现代码

创建二叉树:

typedef int KEY_VALUE;

struct bstree_node {
	KEY_VALUE data;
	struct bstree_node *left;
	struct bstree_node *right;
};

struct bstree {
	struct bstree_node *root;
};

struct bstree_node *bstree_create_node(KEY_VALUE key) {
	struct bstree_node *node = (struct bstree_node*)malloc(sizeof(struct bstree_node));
	if (node == NULL) {
		assert(0);
	}
	node->data = key;
	node->left = node->right = NULL;

	return node;
}

二叉树的插入:

int bstree_insert(struct bstree *T, int key) {

	assert(T != NULL);

	if (T->root == NULL) {//若为空树,直接赋值
		T->root = bstree_create_node(key);
		return 0;
	}

	struct bstree_node *node = T->root;
	struct bstree_node *tmp = T->root;

//查找该插入的位置
	while (node != NULL) { //直到Node为底层了
		tmp = node; //先遍历确定待插入的位置
		if (key < node->data) {
			node = node->left;
		} else {
			node = node->right;
		}
	}

	if (key < tmp->data) {
		tmp->left = bstree_create_node(key);
	} else {
		tmp->right = bstree_create_node(key);
	}
	
	return 0;
}

二叉树的中序遍历:

int bstree_traversal(struct bstree_node *node) {
	//先序,中序,后序遍历,这个是代表根节点的顺序(每个节点会遇到三次)
	//先序:跟节点都会遇到3次,遇到第一次打印;
	//中序:根节点遇到第二次打印;
	//后序:根节点遇到第三次打印;
	if (node == NULL) return 0;
	
	//printf("%4d ", node->data); //先序
	bstree_traversal(node->left);
	printf("%4d ", node->data); //中序
	bstree_traversal(node->right);
	//printf("%4d ", node->data); //后序
}

红黑树的定义:
在这里插入图片描述
参考资料:
参考资料一
参考资料二

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值