数据结构-----红黑树的插入

news2024/11/19 16:48:14

目录

前言

红黑树的储存结构

 一、节点旋转操作

左旋(Left Rotation)

右旋(Right Rotation) 

二、插入节点

1.插入的是空树

2.插入节点的key重新重复

3.插入节点的父节点是黑色

4.插入节点的父节点是红色

4.1父节点是祖父节点的左子节点

4.1.1叔叔节点是红色 

 4.1.2叔叔节点是黑色

4.1.2-1 插入节点是作左子节点

4.1.2-2插入节点是作右子节点

 4.2父节点是祖父节点的右子节点

4.2.1叔叔节点是红色

4.2.2 叔叔节点是黑色

4.2.1-1 插入节点是作左子节点

4.2.1-2 插入节点是作右子节点

三、完整代码展示


前言

        上一期我们初步学习了红黑树的基本概念和特性(上一期链接:数据结构-----红黑树简介_Gretel Tade的博客-CSDN博客 如果不了解红黑树相关性质的话建议看看这个),那么从这一期开始,我们就进入到了红黑树的深入学习,首先我通过这一期来详细介绍红黑树的插入操作实现,下面就看看怎么去把数据插入到红黑树吧!

红黑树的储存结构

 根据红黑树的要求,我们可以去定义红黑树节点和树的结构体,如下所示:

//宏定义颜色
#define red 0
#define black 1

//数据类型Datatype
typedef char Datatype;
//红黑树节点存储结构
typedef struct node {
	Datatype data;
	int color;
	int key;//排序键值,根据key大小排序
	struct node* par;//父节点指针
	struct node* left, * right;//左右子节点指针
}Node;

//红黑树的定义rbtree
typedef struct tree {
	Node* root;//指向根节点指针
	Node* nil;//叶子节点(哨兵)
}rbtree;

 一、节点旋转操作

在数据结构当中,旋转操作是一种很常见的操作,可能去实现数据结构平衡或者其他相关特性的要求,同样的的AVL树和红黑树里边也是要进行旋转操作的,通过旋转来满足平衡的特性。旋转分两种:左旋(Left Rotation)右旋(Right Rotation)

左旋(Left Rotation)

左旋是一种将某个节点的右子节点旋转上来的操作。也就是说当前节点的右子节点顶替了自己,然后自己变为右子节点的左子节点,以保持树的平衡。

操作如下:

  1. 将当前节点的右子节点设为新的父节点。
  2. 将新的父节点的左子节点设为当前节点的右子节点。
  3. 如果当前节点有父节点,将新的父节点替代当前节点的位置。
  4. 将当前节点设为新的父节点的左子节点。

 代码实现:

//左旋(以x为旋转点,向左旋转)
void left_rotate(rbtree* T, Node* x) {
	Node* y = x->right;//标记到右子节点
	x->right = y->left;//y的左子节点代替x的右子节点
	if (x->right != T->nil)
		x->right->par = x;//如果不为空(nil)其父节点指向x
	y->par = x->par;//把y的父节点指向x的父节点,此时x与y没有直接联系了
	if (x->par == T->nil) {//判断x的父节点是否为根结点
		T->root = y;//如果是的话,y就变为根结点
	}
	else {
		//y顶替x的位置
		if (x == x->par->left)
			x->par->left = y;//如果x是父节点的左边,那y就代替x成为左子节点
		else
			x->par->right = y;//如果x是父节点的右边,那y就代替x成为右子节点
	}
	//y的左子节点指向x,x的父节点指向y
	y->left = x;
	x->par = y;
}

右旋(Right Rotation) 

同样的右旋也是将左子节点顶替自己成为父节点, 然后自己成为左子节点的右子节点。

操作如下:

  1. 将当前节点的左子节点设为新的父节点
  2. 将新的父节点的右子节点设为当前节点的左子节点
  3. 如果当前节点有父节点,将新的父节点替代当前节点的位置
  4. 将当前节点设为新的父节点的右子节点

 代码实现:

//右旋(以x为旋转点,向右旋转)
void right_rotate(rbtree* T, Node* x) {
	Node* y = x->left;//标记到左子节点y
	x->left = y->right;//y的右子节点代替x的左子节点
	if (x->left != T->nil)
		x->left->par = x;
	y->par = x->par;//y的父节点指向x的父节点
	if (x->par == T->nil)
		T->root = y;//如果x是根结点的话,那么y代替x成为根结点
	else {
		if (x == x->par->left)
			x->par->left = y;
		else
			x->par->right = y;
	}
	//y的右子节点指向x,x的父节点为y
	y->right = x;
	x->par = y;
}

二、插入节点

再讲之前,我分享一个网址给大家(链接:Red/Black Tree Visualization),这个是一个红黑树模拟器的网址,你们可以去进行红黑树插入删除遍历等操作,可以自己试试看。如下图所示:

 废话不多说了,上正文!

红黑树的插入操作分两步走:

  • 找到插入位置
  • 进行自平衡调整

 注意:插入节点初始为红色

原因分析:因为红黑树中任意一个节点到叶子节点路径所含黑色节点数量相同,也就是说如果我插入的节点为黑色的话,那么就会破坏红黑树的要求,所以插入的节点必须是红色节点,才能保证红黑树的性质。

下面就开始讨论红黑树的几种插入情况:

1.插入的是空树

这是最简单的插入情况,当插入第一个节点的时候,红黑树为空我们只需要让根节点指向这个节点即可。操作如下:

  1. 根节点指向此节点
  2. 把根节点染黑

2.插入节点的key重新重复

这种情况的话我们可以根据自己喜好去处理,如果出现了重复的key,那么就把这个key里面的值进行更新;或者我们不进行插入操作,因为key不可以重复,直接退出插入操作。

3.插入节点的父节点是黑色

这很好处理,直接插入就行了,因为父节点为黑色,插入节点为红色,所以不会影响红黑树的平衡性。

  1. 直接插入即可

4.插入节点的父节点是红色

这种情况是最为复杂的,由于父节点颜色是红色,所以要进行平衡调整,所以要去进一步的讨论才行。那具体根据什么去调整呢?是看叔叔节点的颜色来调整(父节点的兄弟节点),具体分以下几种情况:

 大的有两种情况,要看父节点是祖父节点的左边还是右边,下面我就以父节点为左子节点为例子:

 下文图标说明:

t 表示插入的节点

P表示父节点

B表示叔叔节点

PP表示祖父节点

4.1父节点是祖父节点的左子节点
4.1.1叔叔节点是红色 

如果叔叔节点的颜色是红色的话,这里不需要进行旋转操作,只需要让父节点和叔叔节点颜色变为黑色,祖父节点颜色变为红色即可。流程如下:

  • 把P 和B 节点染黑
  • 把PP节点染红

 4.1.2叔叔节点是黑色

这里的话又要去分两种情况:

  1. 插入节点是父节点的左子节点
  2. 插入节点是父节点的右子节点
4.1.2-1 插入节点是作左子节点

 如果插入的节点是父节点的左子节点的话,那么要进行以下操作:

  • 把P染黑
  • 把PP染红
  • 对PP进行右旋

4.1.2-2插入节点是作右子节点

 如果插入节点是作为父节点的右子节点的话,要进行以下操作:

  • 对P进行左旋
  • 把t 染黑
  • 把PP染红
  • 对PP进行右旋

 4.2父节点是祖父节点的右子节点

这里的操作跟4.1基本上是一模一样的,只是对称过去是了,但是我还是想详细列出来吧,下面接着看。

4.2.1叔叔节点是红色

操作步骤如下:

  • 把B(叔叔节点)和P(父节点)然黑
  • 把PP(祖父节点)染红

4.2.2 叔叔节点是黑色

同样的也是分以下两种情况讨论: 

4.2.1-1 插入节点是作左子节点
  •  对P 进行右旋
  • 将t 染黑
  • 将PP 然红
  • 对PP 进行左旋

4.2.1-2 插入节点是作右子节点
  •  将P 染黑
  • 将PP 然红
  • 对PP进行左旋

 以上这些就是红黑树的插入全部可能了,是不是很多啊,其实还好啦!只要我们把这些情况一个一个分类,然后思路捋一捋很容易弄明白的,后面讲到红黑树的删除还有更多种情况呢!还有就是,这些图片是我自己画的,呃画得不太好,不好意思哈。

三、完整代码展示

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

//宏定义颜色
#define red 0
#define black 1

//数据类型Datatype
typedef char Datatype;
//红黑树节点存储结构
typedef struct node {
	Datatype data;
	int color;
	int key;
	struct node* par;//父节点指针
	struct node* left, * right;//左右子节点指针
}Node;

//红黑树的定义rbtree
typedef struct tree {
	Node* root;//指向根节点指针
	Node* nil;//叶子节点(哨兵)
}rbtree;

//创建初始化红黑树
rbtree* Create_inittree() {
	rbtree* T = (rbtree*)malloc(sizeof(rbtree));
	assert(T);
	T->nil = (Node*)malloc(sizeof(Node));
	assert(T->nil);
	//T->nil是不储存数据的节点,作为空节点代替NULL,也就是哨兵节点(表示空)
	T->nil->color = black;
	T->nil->par = NULL;
	T->nil->left = T->nil->right = NULL;
	T->root = T->nil;
	return T;
}

//创建一个节点
Node* Create_node(rbtree*T ,Datatype data, int key) {
	Node* new_node = (Node*)malloc(sizeof(Node));
	assert(new_node);
	new_node->data = data;
	new_node->color = red;//初始化颜色红色
	//左右父节点为nil哨兵节点
	new_node->left=new_node->right = T->nil;
	new_node->par = T->nil;
	new_node->key = key;
	return new_node;
}

//左旋(以x为旋转点,向左旋转)
void left_rotate(rbtree* T, Node* x) {
	Node* y = x->right;//标记到右子节点
	x->right = y->left;//y的左子节点代替x的右子节点
	if (x->right != T->nil)
		x->right->par = x;//如果不为空(nil)其父节点指向x
	y->par = x->par;//把y的父节点指向x的父节点,此时x与y没有直接联系了
	if (x->par == T->nil) {//判断x的父节点是否为根结点
		T->root = y;//如果是的话,y就变为根结点
	}
	else {
		//y顶替x的位置
		if (x == x->par->left)
			x->par->left = y;//如果x是父节点的左边,那y就代替x成为左子节点
		else
			x->par->right = y;//如果x是父节点的右边,那y就代替x成为右子节点
	}
	//y的左子节点指向x,x的父节点指向y
	y->left = x;
	x->par = y;
}
//右旋(以x为旋转点,向右旋转)
void right_rotate(rbtree* T, Node* x) {
	Node* y = x->left;//标记到左子节点y
	x->left = y->right;//y的右子节点代替x的左子节点
	if (x->left != T->nil)
		x->left->par = x;
	y->par = x->par;//y的父节点指向x的父节点
	if (x->par == T->nil)
		T->root = y;//如果x是根结点的话,那么y代替x成为根结点
	else {
		if (x == x->par->left)
			x->par->left = y;
		else
			x->par->right = y;
	}
	//y的右子节点指向x,x的父节点为y
	y->right = x;
	x->par = y;
}

//插入后平衡调整
void Insert_adjust(rbtree* T, Node* t) {
	//如果父节点的颜色是红色那就进行调整操作了
	if (t->par->color == red) {
		Node* p = t->par;
		Node* pp = p->par;
		//01 p节点是pp左子节点
		if (p == pp->left) {
			Node* uncle = pp->right;
			//01-1 叔叔节点颜色是红色
			if (uncle->color == red) {
				p->color = black;
				uncle->color = black;
				pp->color = red;
				t = pp;
			}
			//01-2 叔叔节点颜色是黑色
			else {
				//01-2-1 插入节点t是p的左子节点
				if (t == p->left) {
					p->color = black;
					pp->color = red;
					right_rotate(T, pp);
					t = p;
				}
				//01-2-2 插入节点t是p的右子节点
				else if(t==p->right){
					left_rotate(T, p);
					t->color = black;
					pp->color = red;
					right_rotate(T, pp);
				}
			}
		}
		//02 p节点是pp的右子节点
		else {		
			Node* uncle = pp->left;
			//02-1 叔叔节点颜色是红色
			if (uncle->color == red) {
				pp->color = red;
				p->color = black;
				uncle->color = black;
				t = pp;
			}
			//02-2 叔叔节点颜色是黑色
			else {
				//02-2-1 插入节点t是p的右子节点
				if (t == p->right) {
					p->color = black;
					pp->color = red;
					left_rotate(T,pp);
					t = p;
				}
				//02-2-2 插入节点t是p的左子节点
				else {
					right_rotate(T, p);
					t->color = black;
					pp->color = red;
					left_rotate(T, pp);
				}
			}
		}
	}
	//根节点标记黑色
	T->root->color = black;
}

//插入节点
void Insert_node(rbtree* T, Datatype data,int key) {
	assert(T);
	Node* t = Create_node(T ,data, key);
	Node* root = T->root;//快指针
	Node* cur=T->nil;//慢指针
	//1.如果根节点为空
	if (T->root==T->nil) {
		T->root = t;//根结点指向新创建的节点
	}
	else {
		while (root != T->nil) {
			cur = root;//cur标记为root的上一个节点(父节点)
			if (t->key > root->key)
				root = root->right;
			else if (t->key < root->key)
				root = root->left;
			//如果出现插入重复的key值,就退出,不进行插入操作
			else {
				printf("Don't insert the same key!\n");
				free(t);
				t = NULL;
				return;
			}
		}
	}
	//判断插入的位置
	if (key < cur->key)
		cur->left = t;//小的话就插入左边
	else
		cur->right = t;//大的话就插入右边
	t->par = cur;//新插入的父节点指针指向cur
	Insert_adjust(T, t);//平衡调整
}

 单单值考虑插入操作就有两百多行代码,后面还有删除操作,查找操作,总共的话大概400行代码,这里就先发今天所讲的插入操作内容的代码,注释很详细,慢慢看哈,我相信你一点看得懂的!

以上就是本期的全部内容了,我们下一期讲红黑树的删除操作,下次见!

分享一张壁纸: 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1099132.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

“之江创客”亚洲赛区完美收官 助力浙亚经贸高质量发展

10月13日下午&#xff0c;由商务部中国国际电子商务中心指导&#xff0c;浙江省商务厅等十个部门主办&#xff0c;浙江省电子商务促进中心承办的“之江创客”2023全球电子商务创业创新大赛亚洲赛区决赛成功举办。浙江省商务厅党组成员、副厅长张钱江出席活动并致辞&#xff0c;…

DDoS攻击与CC攻击:网络安全的两大挑战

在今天的数字时代&#xff0c;网络安全问题越来越突出&#xff0c;其中分布式拒绝服务攻击&#xff08;DDoS攻击&#xff09;和HTTP洪泛攻击&#xff08;CC攻击&#xff09;是两种常见的网络威胁。本文将探讨这两种攻击的概念、原理以及如何有效地应对它们。 1. DDoS攻击&…

Python的logging模块(日志、DEBUG、INFO、WARNING、ERROR、CRITICAL)

1. 前言 logging 是 Python 标准库中用于记录日志的模块。它提供了一种灵活且可配置的方式来在应用程序中记录各种信息&#xff0c;包括调试信息、警告和错误消息。无论是写框架代码还是业务代码&#xff0c;都离不开日志的记录&#xff0c;它能给我们定位问题带来极大的帮助。…

哈夫曼树的建立(C++,最优树)

介绍&#xff1a; 哈夫曼树&#xff08;Huffman Tree&#xff09;是一种用于数据压缩的树形数据结构。它是由刚特哈夫曼于1952年发明的。 哈夫曼树的特点是&#xff1a;对于一个长度为n的字符集&#xff0c;它可以将每个字符在树上表示为一个唯一的二进制编码。在哈夫曼树中&am…

C语言——编译全过程的那些事

C语言——编译全过程的那些事 一、C语言的编译过程二、编译的详细过程2.1预编译过程2.2编译过程2.3 汇编过程2.4链接过程 三、编译全过程 一、C语言的编译过程 1.C语言的编译过程通常可以分为两个大的部分&#xff0c;编译和链接。 2.在ANSI C的任何一种实现中&#xff0c;存…

Vue检测数据的原理

Vue能够对用户的数据进行响应式&#xff0c;也就是你在data中写了什么&#xff0c;你在模板中用到data的部分就会渲染成什么&#xff0c;那么Vue是怎么知道用户修改了data中的数据变化并对模板重新进行解析的呢&#xff1f; 在Vue将数据存储为自身的_data之前&#xff0c;Vue会…

《中国工业经济》企业数字化转型与供应链配置—集中化还是多元化

文章利用2010-2021年A股上市公司数据&#xff0c;从供应链治理角度系统验证了企业数字化转型对供应链配置的影响及其作用机制 研究发现&#xff0c;企业数字化转型显著降低了供应链上游供应商、下游客户以及供应链整体集中度&#xff0c;推动供应链配置多元化&#xff1b;该推…

python使用dataset快速使用SQLite

目录 一、官网地址 二、安装 三、 快速使用 一、官网地址 GitHub - pudo/dataset: Easy-to-use data handling for SQL data stores with support for implicit table creation, bulk loading, and transactions. 二、安装 pip install dataset 如果是mysql&#xff0c;则…

基于冠状病毒群体免疫优化的BP神经网络(分类应用) - 附代码

基于冠状病毒群体免疫优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于冠状病毒群体免疫优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.冠状病毒群体免疫优化BP神经网络3.1 BP神经网络参数设置3.2 冠…

7.memchr函数

#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h>int main() {int* ans NULL;char arr[] "abcdef";int len 3;/*共查找len个字节*/char ch d;ans memchr(arr, ch, len);if (NULL ans){printf("前%d个字符中&#xff0c…

数据结构与算法课后题-第六章(图的基本概念)

文章目录 1 图的基本概念2 基本概念及术语1 有向图2 无向图3 简单图、多重图4 完全图5 子图6 连通 、连通图和连通分量7 强连通图、强连通分量8 生成树、生成森林 参考博客&#xff1a; 数据结构&#xff1a;图(Graph)【详解】 1 图的基本概念 2 基本概念及术语 1 有向图 2 无…

深度分析c+引用的本质以及引用与指针的区别

文章目录 引用的概念引用的定义引用的特性引用的权限问题引用的使用方式引用作参数引用作返回值指针的本质引用和指针的区别 引用的概念 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0c;编译器不会为引用变量开辟内存空间&#xff0c;它和它引用…

Oracle数据库SQL*Plus命令行执行SQL语句时,中文乱码报错解决方法

文章目录 问题背景解决方案1. 设置NLS_LANG环境变量2. 修改SQL*Plus字符集设置3. 使用Unicode字符集 常见错误和解决方法结语 &#x1f389;欢迎来到Java学习路线专栏~Oracle数据库SQL*Plus命令行执行SQL语句时&#xff0c;中文乱码报错解决方法 ☆* o(≧▽≦)o *☆嗨~我是IT陈…

【jmeter】接口测试流程

1、Jmeter简介 Jmeter是由Apache公司开发的一个纯Java的开源项目&#xff0c;即可以用于做接口测试也可以用于做性能测试。 Jmeter具备高移植性&#xff0c;可以实现跨平台运行。 Jmeter可以实现分布式负载。 Jmeter采用多线程&#xff0c;允许通过多个线程并发取样或通过独…

NTFS磁盘格式读写工具Tuxera NTFS 2023 for Mac中文破解版v2023含最新激活序列号

Tuxera NTFS 2023 Mac 是一个NTFS文件系统驱动程序&#xff0c;为解决Mac上不能对NTFS格式硬盘进行访问和读写而生。实现苹果Mac OS X系统读写Microsoft Windows NTFS文件系统&#xff0c;在硬盘、U盘等外接设备中进行全面访问、删除、修改等相关操作。 tuxera ntfs 2023 破解…

计算机网络第2章-CDN(4)

视频流和内容分发网 HTTP流和DASH 在HTTP流中&#xff0c;视频只是存储在HTTP服务器中作为一个普通的文件&#xff0c;每个文件有有一个特定的URL。当用户要看视频时&#xff0c;客户与服务器之间创建一个TCP连接并发送HTTP GET请求。 HTTP流具有严重缺陷&#xff0c;即所有…

扩大减产,NAND Flash市场迎来涨价潮 | 百能云芯

根据TrendForce的最新研究&#xff0c;NAND Flash市场即将面临一轮全面涨价的浪潮。供应商们通过严格控制产出量&#xff0c;将在第四季实施合约价的涨幅&#xff0c;预计在8%到13%之间。明年除非原厂仍能维持减产策略&#xff0c;且需留意服务器领域对Enterprise SSD需求是否回…

【Qt高阶】linux下编译提示找不到依赖的库【2023.10.16】

现象 提示找不到一些库文件。 排查方法 查看qmake出来的 makefile文件&#xff0c;相对路径是按照makefile文件所在路径进行查找。 命令行编译正常&#xff0c;拿QtCreator编译不过 把下面的勾去掉&#xff0c;直接在当前目录构建。

4.Vue-Vue调用第三方接口

题记 用vue调用第三方接口&#xff0c;以下是全部代码和操作流程。 寻找第三方接口网站 推荐&#xff1a;免费API - 提供免费接口调用平台 (aa1.cn) 下面的代码以下图中的接口为例 调用第三方接口代码 TestView.vue文件如下&#xff1a; <template> <div > <…

[nlp] chathome—家居装修垂类大语言模型的开发和评估

ChatHome: Development and Evaluation of a Domain-Specific LanguageModel for Home Renovation ChatHome: 家居装修垂类大语言模型的开发和评估 1、摘要: 我们的方法包括两个步骤:首先,使用广泛的家庭装修数据集(包括专业文章、标准文档和网络内容)对通用模型进行后预训…