[C++] map、set的 红黑树 封装(一)

news2024/9/21 20:41:21

标题:[C++] map、set的 红黑树 封装

@水墨不写bug


 (图片来源于网络)


目录

一、红黑树与AVL树的比较(为什么容器选择红黑树)

二、map、set的封装

1.模板参数

 2.红黑树迭代器设计


 

正文开始:

一、红黑树与AVL树的比较(为什么容器选择红黑树)

        AVL树保证了二叉树的绝对平衡,也就是左右子树的高度差的小于等于1;

        红黑树保证了二叉树的相对平衡,具体是最长路径的长度不大于最短路径的2倍;

        红黑树和AVL树都是高效的二叉平衡树,唯一不同的是保证平衡的策略不同。红黑树的高度必然比AVL树高,但是红黑树拥有自己独特的优势。

        两种平衡二叉树的增删改查的时间复杂度都是O(log N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。


二、map、set的封装

1.模板参数

        C++的STL内关联性容器map、set的底层实现都是红黑树,并且是同一个类模板实例化出来的类。但是对于这一点你会或多或少有一些疑问:

        1.map和set的数据类型不同,怎么实现共用一种实现方式?

        2.红黑树的迭代器又是如何设计的?

在之前的文章中,我们详细讲解了红黑树的实现:《[数据结构] RBTree && 模拟实现RBTree》

        为了实现红黑树,我们首先实现了红黑树的节点类,对于红黑树的一个节点,根据需要实现为三叉链型,此外包含了数据和颜色:(具体如下)

namespace ddsm
{
	enum COLOR
	{
		BLACK,
		RED
	};

   //这里的模板参数是一个不确定的类型,可以是int,也可以是pair,
   //根据传入的模板参数来决定实例化出的类内部的成员在_data的类型
	template<class V>
	struct RBTreeNode
	{
		RBTreeNode<V>* _parent;
		RBTreeNode<V>* _left;
		RBTreeNode<V>* _right;
		V _data;
		COLOR _col;

		//红黑树节点的默认构造
		RBTreeNode(const V& data = V())
			:_parent(nullptr)
			,_left(nullptr)
			,_right(nullptr)
			, _data(data)
			,_col(RED)
		{}
	};
}

 以红黑树节点类为基础,我们在上面提到的文章中实现了基本的红黑树。为了解决map、set复用同一颗红黑树但是存储数据类型不同的问题,我们在实现map和set时采取了不同方法:


不着急,我们具体慢慢理解一下:      


        在设计红黑树的时候,我们提前约定好,传入的模板参数第一个K,用于find或者erase时的查找,(find和erase的查找需要根据key来查找);传入的第二个模板参数是数据域,代表存入的数据类型。而第三个模板参数是为了便于封装时复用而设计的。

        由于我们在find的时候,并不知道容器内是否有我们要查找的对象,所以我们用key来查找是理所当然的;

        同时,我们在插入的时候,也只需要表示插入的对象即可,不需要单独的指出插入对象的key(查找键值)和value(数据域),但是在insert的逻辑中,却需要我们根据key的大小关系来决定插入的位置,这就需要我们在知道value(数据域)的情况下提取出key。这个功能可以通过仿函数来实现:

         通过仿函数,我们就可以通过实例化一个仿函数对象,将数据域交给这个对象处理,这样就能够取到value对应的key。

         于是,在insert的逻辑中,我们决定插入位置时,在需要用key比较的地方,通过仿函数处理value得到key,再进行比较:

bool insert(const V& data)
{
	//对于空的特殊处理
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return true;
	}
	//找到插入位置
	Node* cur = _root, * parent = nullptr;


	KeyOfV keyofv;//创建仿函数对象,map和set各自会实例化出自己对应的类,这些类又会各
                  //自调用自己对应的仿函数类实例化出的对象

	while (cur)
	{
		if (keyofv(cur->_data) < keyofv(data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (keyofv(cur->_data) > keyofv(data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{//插入失败,有相同值
			return false;
		}
	}
	//new并连接
	cur = new Node(data);
	if (keyofv(parent->_data) < keyofv(data))
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;

	//降低平衡
	//二叉树逻辑结束,红黑树开始
	//cur为红,p为红,g为黑,
	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left)
		{
			//uncle在右侧
			//        g
			//   p          u
			//	
			Node* uncle = grandfather->_right;
			if (uncle && uncle->_col == RED)//u存在且为红
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				//继续向上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else//u不存在或者u存在且为黑
				//旋转
			{
				if (cur == parent->_left)
				{
					RotateR(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
				}
				else
				{
					RotateL(parent);
					RotateR(grandfather);
					grandfather->_col = RED;
					cur->_col = BLACK;
				}
				break;
			}
		}
		else
		{
			//uncle在左侧s
			//        g
			//   u          p
			//					
			Node* uncle = grandfather->_left;
			if (uncle && uncle->_col == RED)//uncle存在且为红
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				cur = grandfather;
				parent = grandfather->_parent;
			}
			else//uncle不存在或者存在且为黑
				//旋转
			{
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
				}
				else
				{
					RotateR(parent);
					RotateL(grandfather);
					grandfather->_col = RED;
					cur->_col = BLACK;
				}
				break;
			}
		}

	}
	_root->_col = BLACK;
	return true;
}

 2.红黑树迭代器设计

         map和set内拥有迭代器,有begin和end接口,这是实现范围for的基础。为map和set底层的红黑树实现出迭代器对于map、set容器的使用有极大的方便。

         什么是迭代器,你一定知道:迭代器是一种类似于指针的东西。指针是内置类型的,它有自己的行为逻辑:“*”表示解运用,取出指针指向的数据;“++”表示指针向后移动一个数据类型的长度(如果是线性容器的话,有意义)。“--”则相反;

        指针就是一种迭代器!在之前我们模拟实现string,vector的时候,我们就是用对应数据类型的指针来模拟迭代器的。指针是一种没有任何限制的迭代器,我们如果不满意指针的行为,就可以通过封装指针并在类内重载运算符的方法,来改变指针的内置默认行为。

        在明白这一点之后,我们实现红黑树迭代器就畅行无阻了,我们直接封装一个红黑树节点的指针作为红黑树的迭代器,内部重载了++,==,!=,*等等运算符(可以根据逻辑需求自行添加):这就是红黑树迭代器的大框架,具体实现我们后面再讨论:

//迭代器类似于指针,内置类型的指针的++,*,==,!= 运算默认成立
//红黑树迭代器类目的在于 重载++,*,==,!=运算符,红黑树迭代器按照我们想要的方式进行运算
template<class V>
struct RBTreeIterator
{
	typedef RBTreeIterator<V> Self;
	typedef RBTreeNode<V> Node;

	Node* _node;

	RBTreeIterator(Node* node)
		:_node(node)
	{}

	Self& operator++();
	
	bool operator!=(const Self& s);
	
	bool operator==(const Self& s);
	
	V& operator*();
	
};

在红黑树内为了与上层区分,迭代器名称首字母大写:

//V在于类型不确定:单个内置类型 / pair类型
template<class K,class V,class KeyOfV>
class RBTree
{
	typedef RBTreeNode<V> Node;
	typedef Node* PNode;
public:
	typedef RBTreeIterator<V> Iterator;
	Node* _root;
};

别忘记了上层的实现,我们在map和set中也各自规范一下迭代器的名称:

template<class K,class V>
class map
{
	struct MapKeyOfV
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	//封装红黑树的Iterator,在本封装层套一层壳
	typedef typename RBTree<K, pair<K, V>, MapKeyOfV>::Iterator iterator;
	
private:
	RBTree<K, pair<K, V>, MapKeyOfV> _rbtree;
};
template<class K>
class set
{
	struct SetKeyOfV
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	//封装红黑树的Iterator,在本封装层套一层壳
	typedef typename RBTree<K, K, SetKeyOfV>::Iterator iterator;
private:
	RBTree<K, K, SetKeyOfV> _rbtree;
};

待续~

未经作者同意禁止转载

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

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

相关文章

RK3588J正式发布Ubuntu桌面系统,丝滑又便捷!

本文主要介绍瑞芯微RK3588J的Ubuntu系统桌面演示&#xff0c;开发环境如下&#xff1a; U-Boot&#xff1a;U-Boot-2017.09 Kernel&#xff1a;Linux-5.10.160 Ubuntu&#xff1a;Ubuntu20.04.6 LinuxSDK&#xff1a; rk3588-linux5.10-sdk-[版本号] &#xff08;基于rk3…

【GH】【EXCEL】P7: Control

XL Label XL Dropdown XL CHECK BOX XL Button XL Scroller XL Spinner XL ListBox

RocketMQ源码分析 - 环境搭建

RocketMQ源码分析 - 环境搭建 环境搭建源码拉取导入IDEA调试1) 启动NameServer2) 启动Broker3) 发送消息4) 消费消息 环境搭建 依赖工具 JDK&#xff1a;1.8MavenIntellij IDEA 源码拉取 从官方仓库 https://github.com/apache/rocketmq clone或者download源码。 源码目录…

【微服务】微服务组件之Nacos注册中心和配置中心的使用

背景&#xff1a; 在当前的软件架构领域&#xff0c;微服务架构凭借其高度的可扩展性、灵活性和可维护性&#xff0c;已成为企业构建复杂应用的首选。微服务架构通过将应用拆分成一系列小的、独立的服务&#xff0c;实现了服务的解耦和复用&#xff0c;从而提高了应用的可扩展性…

Sass实现网页背景主题切换

Sass 实现网页背景主题切换 前言准备工作一、 简单的两种主题黑白切换1.定义主题2. 添加主题切换功能3. 修改 data-theme 属性 二、多种主题切换1. 定义主题2. 动态生成 CSS 变量1.遍历列表2.遍历映射3.高级用法 3. 设置默认主题4. 切换功能HTML 三、多种主题多种样式切换1. 定…

在 Fedora 上安装 LAMP(Linux、Apache、MySQL、PHP)的方法

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 关于 LAMP LAMP 栈是一组用于启动和运行 Web 服务器的开源软件。该缩写代表 Linux、Apache、MySQL 和 PHP。由于服务器已经在运行 Fedo…

高性能web服务器1

基础 Web 服务简介 Web 服务是互联网的核心组成部分之一&#xff0c;它允许用户通过浏览器访问信息和应用程序。一个基础的 Web 服务通常由 Web 服务器软件、静态网页内容、以及可选的动态内容生成程序组成。 Web 服务器软件 Web 服务器软件是运行在服务器上的程序&#xff…

【Java 数据结构】PriorityQueue介绍

优先级队列 回顾二叉树堆堆是什么堆的实现初始化堆的创建向下调整建堆复杂度插入向上调整建堆复杂度删除 PriorityQueue类介绍PriorityQueue是什么PriorityQueue使用构造方法常用方法 PriorityQueue源码介绍Top-K问题 回顾二叉树 上一次我们简单的了解了二叉树这个数据结构, 但…

每天五分钟深度学习框架pytorch:神经网络工具箱nn的介绍

本文重点 我们前面一章学习了自动求导,这很有用,但是在实际使用中我们基本不会使用,因为这个技术过于底层,我们接下来将学习pytorch中的nn模块,它是构建于autograd之上的神经网络模块,也就是说我们使用pytorch封装好的神经网络层,它自动会具有求导的功能,也就是说这部…

夏晖WMS是什么?夏晖WMS怎么与金蝶云星空进行集成?

在数字化浪潮席卷全球的今天&#xff0c;企业对于业务流程的高效管理和数据集成的需求愈发迫切。夏晖WMS作为一款领先的仓库管理系统&#xff0c;与金蝶云星空ERP的集成成为了众多企业提升管理效率的关键环节。 夏晖WMS是什么? 夏晖WMS是一款由夏晖物流&#xff08;上海&…

Golang | Leetcode Golang题解之第355题设计推特

题目&#xff1a; 题解&#xff1a; type Twitter struct {Tweets []intUserTweets map[int][]intFollows map[int][]intIsFollowMy map[int]bool }/** Initialize your data structure here. */ func Constructor() Twitter {// 每一次实例化的时候&#xff0c;都重新分配一次…

C语言 | Leetcode C语言题解之第354题俄罗斯套娃信封问题

题目&#xff1a; 题解&#xff1a; int cmp(int** a, int** b) {return (*a)[0] (*b)[0] ? (*b)[1] - (*a)[1] : (*a)[0] - (*b)[0]; }int maxEnvelopes(int** envelopes, int envelopesSize, int* envelopesColSize) {if (envelopesSize 0) {return 0;}qsort(envelopes, …

宜佰丰超市进销存管理系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a; Java 数据库&#xff1a; MySQL 技术&#xff1a; JavaMysql 工具&#xff1a; IDEA/Eclipse、Navicat、Maven 系统展示 首页 管理员功能模块…

接口测试及常用接口测试工具(postman/jmeter)详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 首先&#xff0c;什么是接口呢&#xff1f; 接口一般来说有两种&#xff0c;一种是程序内部的接口&#xff0c;一种是系统对外的接口。 系统对外的接口&#x…

【Alibaba Cola 状态机】重点解析以及实践案例

【Alibaba Cola 状态机】重点解析以及实践案例 1. 状态模式 状态模式是一种行为型设计模式&#xff0c;允许对象在内部状态改变时改变其行为&#xff0c;简单地讲就是&#xff0c;一个拥有状态的context对象&#xff0c;在不同状态下&#xff0c;其行为会发生改变。看起来是改…

Spring项目:文字花园(四)

一.实现登录 传统思路: • 登陆⻚⾯把⽤⼾名密码提交给服务器. • 服务器端验证⽤⼾名密码是否正确, 并返回校验结果给后端 • 如果密码正确, 则在服务器端创建 Session . 通过 Cookie 把 sessionId 返回给浏览器. 问题: 集群环境下⽆法直接使⽤Session. 原因分析: 我们开…

渐变纹理的使用

1、渐变纹理的使用 通过单张纹理和凹凸纹理相&#xff0c;我们知道图片中存储的数据不仅仅可以是颜色数据&#xff0c;还可以是高度、法线数据。 理论上来说&#xff0c;图片中存储的数据我们可以自定义规则&#xff0c;我们可以往图片中存储任何满足 我们需求的数据用于渲染。…

原神4.8版本抽到角色和重点培养数据表

<!DOCTYPE html> <html lang"zh-cn"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>原神4.8版本抽到角色和重点培养数据表</title…

vue-element-admin——<keep-alive>不符合预期缓存的原因

vue-element-admin——<keep-alive>不符合预期缓存的原因 本文章&#xff0c;以现在中后台开发用的非常多的开源项目vue-element-admin为案例。首先&#xff0c;列出官方文档与缓存<keep-alive>相关的链接&#xff08;请认真阅读&#xff0c;出现缓存<keep-ali…

MSR配置

公钥私钥 网页上提供的脚本安装客户端??去掉跳板机 history | grep azcopy 44 azcopy 47 azcopy cp --recursive --log-level NONE --overwrite true https://singularitywor9084471172.blob.core.windows.net/yifanyang/thinking.py\?sv\2023-01-03\&st\2024-…