红黑树的实现及测试

news2024/12/27 13:01:47

目录

1.红黑树的结构

2.红黑树的节点

3.红黑树的实现

1)插入操作

1.u存在且为红

2.u不存在或存在且为黑(且cur 为 parent 的左)

3.u不存在或存在且为黑(且cur 为 parent 的右)

2)查找操作

3)判断是否为有效的红黑树

4)逻辑测试

完整代码:


1.红黑树的结构

其是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black

性质:

1. 每个结点不是红色就是黑色

2. 根节点是黑色的  

3. 如果一个节点是红色的,则它的两个孩子结点是黑色的  

4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 

5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

2.红黑树的节点

节点和二叉搜索树几乎一样,只不过多了个表示颜色的存储位

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _data;
	color _col;

	RBTreeNode(const pair<K, V>& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}
};

3.红黑树的实现

1)插入操作

对于红黑树来说,最重要的依旧是插入操作,与AVL树不同的是,一个是插入完后调整平衡因子,另一个是插入完后调整节点的颜色,使其保持性质

bool Insert(const pair<K, V>& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (data.first < cur->_data.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (data.first > cur->_data.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(data);
		if (parent->_data.first < data.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 颜色调整
        ......
}

关于颜色调整,一共分为三种情况:

但是要从两个大类来分析 -> 即 p 是位于左还是位于右,操作起来是一样的,只不过要分开写

这里我们只从p在左边进行分析,因为两者分析方式一致。

1.u存在且为红

这种是最简单的情况,因为就只需要去变动颜色即可:

p 和 u 变为黑色,g 变为红色

但是要谨记,根节点是一定为黑色的,如果g为根,要将其变为黑色

2.u不存在或存在且为黑(且cur 为 parent 的左)

这时候就不仅仅需要变动颜色,而且还需要进行右旋 -> RotateR(grandfather)

3.u不存在或存在且为黑(且cur 为 parent 的右)

这种情况是三种情况中最难搞的:

需要变动颜色,且需要进行双旋转->即先 RotateL(parent) 然后 RotateR(grandfather)

2)查找操作

与二叉搜索树相同:

    Node* Find(const V& value)
	{
		Node* cur = _root;
		while (cur)
		{
			if (value < cur->_data.first)
			{
				cur = cur->_left;
			}
			else if (value > cur->_data.first)
			{
				cur = cur->_right;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
3)判断是否为有效的红黑树

可以先记录最左边一条路的黑色节点个数,然后去递归每一条路,拿到每一条路径的上的黑色节点与其进行比较,若不相同,则返回false,递归同时,检查该节点是否与其父节点同时为红色,若为,则也返回false

    bool IsValid()
	{
		Node* cur = _root;
		size_t comp = 0;
		while (cur)
		{
			if (cur->_col == BLACK)
				comp++;
			cur = cur->_left;
		}

		return _IsValid(_root, 0, comp);
	}

	bool _IsValid(Node* root, size_t dist, size_t comp)
	{
		if (root == nullptr)
		{
			if (dist != comp)
			{
				cout << "存在黑色节点不相等的两条路" << endl;
				return false;
			}
			return true;
		}

		if (root->_col == BLACK)
			dist++;

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "有两个连续的红色节点->数字" << root->_data.first << endl;
			return false;
		}

		return _IsValid(root->_left, dist, comp) && _IsValid(root->_right, dist, comp);
	}

这时,基本逻辑就已经都实现完了,可以进行测试了

4)逻辑测试
void TestRBTree1()
{
	//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	RBTree<int, int> t1;
	for (auto e : a)
	{
		if (e == 8)
			int i = 0;

		t1.Insert({ e,e });

		cout << "Insert:" << e << "->" << t1.IsValid() << endl;
	}

	t1.InOrder();
}

经测试,发现插入每个数据后都平衡,因此没问题

不过,还有项更严谨的测试,即产生大量随机数,插入进去测试平衡:

void TestRBTree2()
{
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}

	size_t begin2 = clock();
	RBTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
	}
	size_t end2 = clock();

	cout << t.IsValid() << endl;
}

最后显示的结果也是平衡,那就大概率没有逻辑上的问题了

完整代码:

#pragma once
#include <iostream>
#include <vector>

enum color
{
	BLACK,
	RED
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _data;
	color _col;

	RBTreeNode(const pair<K, V>& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}
};

template<class K, class V>
class RBTree
{
public:
	typedef RBTreeNode<K, V> Node;
public:
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppNode = parent->_parent;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (_root == parent)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}

			subL->_parent = ppNode;
		}
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* ppNode = parent->_parent;


		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (parent == _root)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppNode->_right == parent)
			{
				ppNode->_right = subR;
			}
			else
			{
				ppNode->_left = subR;
			}
			subR->_parent = ppNode;
		}
	}

	bool Insert(const pair<K, V>& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (data.first < cur->_data.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (data.first > cur->_data.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(data);
		if (parent->_data.first < data.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 改颜色
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;

				// cur为红,p为红,g为黑,u存在且为红
				if (uncle && uncle->_col == RED)
				{
					//   g
					//  p  u
					// c
					grandfather->_col = RED;
					parent->_col = uncle->_col = BLACK;

					cur = grandfather;
					parent = cur->_parent;
				}
				else if (!uncle || (uncle && uncle->_col == BLACK))	// cur为红,p为红,g为黑,u不存在或存在且为黑
				{
					if (cur == parent->_left)
					{
						//     g             p
						//   p   u   ->    c   g   
						// c                     u
						grandfather->_col = RED;
						parent->_col = BLACK;
						RotateR(grandfather);
					}
					else
					{
						//   g             g                                g
						// p   u   ->    c   u  (变成上述情况去解决) ->   p   u              
						//   c         p                                c      
						grandfather->_col = RED;
						cur->_col = BLACK;
						RotateL(parent);
						RotateR(grandfather);
					}
					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)
				{
					//   g
					//  u  p
					//       c
					grandfather->_col = RED;
					parent->_col = uncle->_col = BLACK;

					cur = grandfather;
					parent = cur->_parent;
				}
				else if (!uncle || (uncle && uncle->_col == BLACK))
				{
					if (cur == parent->_right)
					{
						//     g             p
						//   u   p   ->    g   c   
						//         c     u        
						grandfather->_col = RED;
						parent->_col = BLACK;
						RotateL(grandfather);
					}
					else
					{
						//   g             g                         
						// u   p   ->    u   p  (变成上述情况去解决)             
						//   c                  c                          
						grandfather->_col = RED;
						cur->_col = BLACK;
						RotateR(parent);
						RotateL(grandfather);
					}
					break;
				}
			}
		}	
		_root->_col = BLACK;
		return true;
	}
	
	Node* Find(const V& value)
	{
		Node* cur = _root;
		while (cur)
		{
			if (value < cur->_data.first)
			{
				cur = cur->_left;
			}
			else if (value > cur->_data.first)
			{
				cur = cur->_right;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

	// 获取红黑树最左侧节点
	Node* LeftMost()
	{
		Node* cur = _root;
		while (cur->_left)
		{
			cur = cur->_left;
		}
		return cur;
	}

	// 获取红黑树最右侧节点
	Node* RightMost()
	{
		Node* cur = _root;
		while (cur->_right)
		{
			cur = cur->_right;
		}
		return cur;
	}

	// 遍历 打印
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	// 判断是否为有效红黑树
	bool IsValid()
	{
		Node* cur = _root;
		size_t comp = 0;
		while (cur)
		{
			if (cur->_col == BLACK)
				comp++;
			cur = cur->_left;
		}

		return _IsValid(_root, 0, comp);
	}

private:
	bool _IsValid(Node* root, size_t dist, size_t comp)
	{
		if (root == nullptr)
		{
			if (dist != comp)
			{
				cout << "存在黑色节点不相等的两条路" << endl;
				return false;
			}
			return true;
		}

		if (root->_col == BLACK)
			dist++;

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "有两个连续的红色节点->数字" << root->_data.first << endl;
			return false;
		}

		return _IsValid(root->_left, dist, comp) && _IsValid(root->_right, dist, comp);
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_data.first << ":" << root->_data.second << endl;
		_InOrder(root->_right);
	}

private:
	Node* _root = nullptr;
};

void TestRBTree1()
{
	//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	RBTree<int, int> t1;
	for (auto e : a)
	{
		if (e == 8)
			int i = 0;

		t1.Insert({ e,e });

		cout << "Insert:" << e << "->" << t1.IsValid() << endl;
	}

	t1.InOrder();
}

void TestRBTree2()
{
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
		//cout << v.back() << endl;
	}

	size_t begin2 = clock();
	RBTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
		//cout << "Insert:" << e << "->" << t.IsBalance() << endl;
	}
	size_t end2 = clock();

	cout << t.IsValid() << endl;
}

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

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

相关文章

63、ELK安装和部署

一、ELK日志系统 1.1、ELK平台的定义 ELK平台是一套完整的日志集中处理解决方案&#xff0c;将ElasticSearch、Logstash和Kiabana 三个开源工具配合使用&#xff0c;完成更强大的用户对日志的查询、排序、统计需求 E:elasticsearch ES分布式索引型非关系数据库&#xff0c;存…

Mybatis学习(2)

分页 目的&#xff1a;减少数据的处理量 方式一&#xff1a;使用limit实现分页&#xff0c;核心SQL sql语法&#xff1a;select * from user limit startIndex&#xff0c;pageSize; 步骤&#xff1a; 1、接口 2、Mapper.xml 3、测试 方式二&#xff1a;使用注解开发 1、…

Java同城货运搬家货运车小程序系统源码

&#x1f69a;同城搬家不头疼&#xff01;揭秘“同城货运搬家货运车小程序”的省心秘籍 &#x1f4f1;开篇&#xff1a;一键下单&#xff0c;搬家新风尚 告别传统搬家的繁琐与不便&#xff0c;今天给大家种草一款超实用的生活神器——“同城货运搬家货运车小程序”&#xff0…

【设计模式:工厂模式】

目录 工厂模式的特点&#xff1a; 工厂模式种类 简单工厂模式&#xff1a; 简单工厂模式的创建步骤&#xff1a; 简单工厂代码&#xff1a; 工厂模式 &#xff1a; 特点&#xff1a; 工厂模式代码&#xff1a; 抽象工厂模式 抽象工厂的主要组成部分&#xff1a; 抽象…

unity中实现流光效果——世界空间下

Properties{_MainTex ("Texture", 2D) "white" {}_FlowColor ("Flow Color", Color) (1, 1, 1, 1) // 流光颜色_FlowFrequency ("Flow Frequency", Float) 1.0 // 流光频率_FlowSpeed ("Flow Speed", Float) 1.0 // 流光…

二维码门楼牌管理应用平台建设:实有单位采集管理

文章目录 前言一、实有单位信息采集&#xff1a;构建城市信息基石二、快速查询功能&#xff1a;精准定位&#xff0c;一触即达三、单位详情全面展示&#xff1a;历史轨迹&#xff0c;一目了然四、信息核实机制&#xff1a;确保数据准确无误五、单位注销流程&#xff1a;灵活管理…

Jenkins保姆笔记(1)——基于Java8的Jenkins安装部署

前言 记录分享下Jenkins的相关干货知识。分2-3篇来介绍Jenkins的安装部署以及使用。还是和以前一样&#xff0c;文章不介绍较多概念和细节&#xff0c;多介绍实践过程&#xff0c;以战代练&#xff0c;来供大家学习和理解Jenkins 概念 Jenkins是一个开源的自动化服务器&…

7.31 Day13 DHCP服务器的配置与管理

DHCP服务及其工作原理 DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09;提供了动态配置IP地址的功能。在DHCP网络中&#xff0c;客户端不再需要自行输入网络参数&#xff0c;而是由DHCP服务器向客户端自动分配。 DHCP服务工作端…

拓扑未来物联网平台简介

拓扑未来物联网平台是基于Thingsboard二次开发的面向产业互联和智慧生活应用的物联网PaaS平台&#xff0c;支持适配各种网络环境和协议类型&#xff0c;可实现各种传感器和智能硬件的快速接入。有效降低物联网应用开发和部署成本&#xff0c;满足物联网领域设备连接、智能化改造…

day27——homework

1、使用两个线程完成两个文件的拷贝&#xff0c;分支线程1拷贝前一半&#xff0c;分支线程2拷贝后一半&#xff0c;主线程回收两个分支线程的资源 #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <fcntl.h> #include <uni…

释放自动化测试潜能:性能优化策略与实战技巧!

引言 在当今追求软件快速迭代的环境下&#xff0c;自动化测试的性能瓶颈正成为制约开发流程加速的主要障碍。本文将深入探讨如何通过策略和实践&#xff0c;优化自动化测试的性能&#xff0c;实现测试执行速度的质的飞跃。 自动化性能瓶颈的识别与突破 首先&#xff0c;识别并…

Day4

请求与响应 请求和响应是Web应用的基本组成部分&#xff0c;它们处理客户端&#xff08;通常是浏览器&#xff09;和服务器之间的交互。 注意三个常见的请求和响应就行&#xff1a; 【注意】&#xff1a; 关于重定向&#xff1a; 浏览器向某个网站发来请求&#xff0c;该网…

在Windows系统上生成SSH秘钥

SSH秘钥是指在SSH协议中使用的加密密钥&#xff0c;用于实现安全认证和数据加密。SSH秘钥通常指的是一对密钥&#xff1a;公钥&#xff08;Public Key&#xff09;和私钥&#xff08;Private Key&#xff09;。 使用以下命令生成SSH密钥 输入下面命令行生成SSH秘钥&#xff0c…

slot 插槽的方式

当组件作为标签使用时&#xff0c;子组件中用slot标签占一个位置&#xff0c;父组件可以在此填充数据&#xff0c;父组件未填充数据&#xff0c;有默认数据 则显示默认数据 具名插槽&#xff1a;子组件中slot标签有name属性&#xff0c;如name“title”&#xff0c;父组件可以用…

Spring面试篇章——Spring基本概述

Spring 的基本概述 Spring学习的核心内容—一图胜千言 IOC&#xff1a;控制反转&#xff0c;可以管理 Java 对象AOP&#xff1a;切面编程JDBCTemplate&#xff1a;是Spring提供一套访问数据库的技术&#xff0c;应用性强&#xff0c;相对好理解声明式事务&#xff1a;基于IOC …

操作系统篇--八股文学习第十一天|进程调度算法你了解多少,进程间有哪些通信方式,解释一下进程同步和互斥,以及如何实现进程同步和互斥

进程调度算法你了解多少&#xff1f; 答&#xff1a; 先来先服务&#xff1a;按照请求的顺序进行调度。 这种调度方式简单&#xff0c;但是能导致较长作业阻塞较短作业。最短作业优先&#xff1a;非抢占式的调度算法&#xff0c;按估计运行时间最短的顺序进行调度。 但是如果…

selenium之批量上传文件

在UI自动化测试过程经常会有上传附件的操作&#xff0c;针对附件上传的场景&#xff0c;通常可以归为几类&#xff1a; 场景一&#xff1a;只上传一个附件场景二&#xff1a;上传固定几个附件场景三&#xff1a;上传动态数量的附件 2.针对以上几种场景&#xff0c;以动态文件数…

【iOS】APP仿写——学生管理系统

前言 学生管理系统和C语言学生管理系统要求相似&#xff0c;需要实现增删改查排序功能&#xff0c;这个仿写比较简单&#xff0c;但通过这个仿写中的限制输入的要求&#xff0c;初步了解了正则表达式。 注册、登陆界面 这个界面和3G share相同&#xff0c;这里就不多做解释了…

C语言 | Leetcode C语言题解之第309题买卖股票的最佳时机含冷冻期

题目&#xff1a; 题解&#xff1a; int maxProfit(int* prices, int pricesSize) {if (pricesSize 0) {return 0;}int f0 -prices[0];int f1 0;int f2 0;for (int i 1; i < pricesSize; i) {int newf0 fmax(f0, f2 - prices[i]);int newf1 f0 prices[i];int newf2…

芋道源码yudao-cloud 二开笔记(Feign服务调用,如何定义一个 API 接口)

在yudao-cloud如何定义一个Api接口&#xff0c;提供给另一个服务去调用&#xff1f;下面是 yudao-module-syetem系统服务 调用 yudao-module-infra文件服务的示例&#xff1a; 首先需要在服务提供者yudao-module-infra定义好对应的api&#xff0c; 第1步&#xff1a; cn.iocod…