C++ RBTree封装mapset

目录

RBTreeNode的声明

 RBTree结构

 map结构

 set结构

改造红黑树

迭代器类

迭代器成员函数

默认成员函数 

Insert

set

map


RBTreeNode的声明

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	T _data;
	colour _col;

	RBTreeNode(const T& data)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}
};

 RBTree结构

template<class K, class T, class KeyOfType>
class RBTree
{
	typedef RBTreeNode<T> node;
public:
    typedef  __RBTreeIterator<T, T&, T*> Iterator;
	typedef  __RBTreeIterator<T, const T&, const T*> ConstIterator;
private:
	node* _root = nullptr;
};

 map结构

namespace nineone
{
	template<class K, class V>
	class map
	{
		struct keyofmap
		{
            const K& operator()(const pair<K, V>& kv) const
			{
				return kv.first;
			}
		};
	public:
	private:
		RBTree<K, pair<const K, V>, keyofmap> rbt;
	};
}

 set结构

namespace nineone
{
	template<class K>
	class set
	{
		struct keyofset
		{
            const K& operator()(const K& lhs)  const
			{
				return lhs;
			}
		};
	public:
	private:
		RBTree<K, const K, keyofset> rbt;//这里没有const find说类型不对
	};
}
  • mapset通过组合的方式复用RBTree
  • 第二个模板参数是用来决定RBTree数的类型
  • 第三个模板参数是为了拿出T里的键,是去实例化的对象的变量里,就是node里的_data
  • 那不是有T和KeyOfType,为什么还需要第一个模板参数呢?因为作为成员函数形参的时候没法从T里拿出K,比如Find就要传进K进去

解释const

  • keyofmap是一个仿函数,用对象去调用,核心部分是operator()操作符的重载
  • 第一个const,为了防止返回的值被改变,从而对一个调用这个仿函数的函数,出现不被预期的结果
  • 第二个const,为了可以使const对象也可以调用
  • 第三个const,是为了防止 keyofmap 类成员函数的改变,那这类现在不是没有吗?这个仿函数的目的就是为了取出对应的K,建议看effectivC++

改造红黑树

迭代器类

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> node;
	typedef  __RBTreeIterator<T, Ref, Ptr> Self;
	node* _node;

	__RBTreeIterator( node *const n) 
		:_node(n)
	{}
	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	bool operator!=(const Self& self)
	{
		return _node != self._node;
	}
	
	Self operator++(int)
	{
		Self ret = *this;
		++*this;
		return ret;
	}
	Self operator++()
	{
		if (_node && _node->_right != nullptr)
		{
			node* minright = _node->_right;
			while (minright->_left)
			{
				minright = minright->_left; //果然,这里写错成_right了
			}
			_node = minright;
		}
		else
		{
			node* cur = _node;
			node* parent = cur->_parent;
			while (parent && parent->_right == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
};
  • Ref对应的是T& 或者const T&;Ptr对应的是T* 或者const T*
  • 这是用一个类来封装node*指针,用类对象来使用迭代器
  • 构造里的const;指针是有两部分,第一是指针自身,第二是自己所指向的内容,★也就是说读写的有两部★;一个总结,const往左边找,左边没有往右边找,靠近哪个,哪个就是常量;node* const n这样写是为了防止传进来的指针被改变;如果是const node* n或node const * n那么是指针所指向的内容不能被改变,关键点是:这个指针所指向的内容是常量;而_node是一个读写指针指针,_node所指的内容和指针自身是可以改变的

operator++

  • 走到中序的下一个位置
  • 中序是左子树 根 右子树 ;过当前节点右不为空,就要去找右树的最左节点;如果为空,就说明这个节点走完了,回去看父亲,如果父亲的左指向他,那么父亲这个节点还没走,如果父亲的右指向他,说明父亲节点走完,要向上找,知道找到一个节点,使得parent的left等于cur
  • 注意 对指针的解引用之前,最好提前判空

迭代器成员函数

public:
	Iterator Begin()
	{
		node* minleft = _root;
		while (minleft && minleft->_left)
		{
			minleft = minleft->_left;
		}
		return Iterator(minleft);
	}

	Iterator End()
	{
		return Iterator(nullptr); //要有构造
	}

	ConstIterator Begin() const
	{
		node* minleft = _root;
		while (minleft && minleft->_left)
		{
			minleft = minleft->_left;
		}
		return ConstIterator(minleft);
	}

	ConstIterator End() const
	{
		return ConstIterator(nullptr); //要有构造
	}

默认成员函数 

public:
	RBTree() = default;

	RBTree(const RBTree<K, T, KeyOfType>& copy)
	{
		_root = Copy(copy._root);
	}

	RBTree& operator=(RBTree<K, T, KeyOfType> rbt)
	{
		swap(_root, rbt._root);
		return *this;
	}

	~RBTree()
	{
		Destory(_root);
		_root = nullptr;
	}
private:
	void Destory(node* root)
	{
		if (root == nullptr)
			return;

		Destory(root->_left);
		Destory(root->_right);
		delete root;
	}	
	node* Copy(node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}

		node* newnode = new node(root->_data);
		newnode->_col = root->_col;

		newnode->_left = Copy(root->_left);
		if (newnode->_left)
			newnode->_left->_parent = newnode;
		newnode->_right = Copy(root->_right);
		if (newnode->_right)
			newnode->_right->_parent = newnode;
		return newnode;
	}

类和对象 

  • 这里必须要提供默认构造,因为成员变量声明的时候不能使用()来初始化;为了提供更清晰的内存管理和初始化顺序,成员变量的初始化必须在初始化列表或者构造函数体内
  • 构造函数一定不能是const函数,因为他就是为了初始化用的
  • 赋值重载 这里一定要是swap(_root, rbt._root);不能是 _root = rbt._root;因为这个rbt是一个传值传参,出了这个函数就会被调析构;一定要传引用返回,不然又会调拷贝构造;一定要有返回值,目的是为了可以连续赋值

代码

  • 析构的时候用后序析构
  • 拷贝的时候,不要忘记子连接回父

Insert

pair<Iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new node(data);
			_root->_col = BLACK;
			return make_pair(Iterator(_root), true);
		}
		KeyOfType keyoftype;

		node* cur = _root;
		node* parent = cur;
		while (cur)
		{
			if (keyoftype(cur->_data) < keyoftype(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (keyoftype(cur->_data) > keyoftype(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(Iterator(cur), false);
			}
		}

		cur = new node(data);
		node* newnode = cur;
		if (keyoftype(parent->_data) < keyoftype(data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			node* pparent = parent->_parent;
			if (pparent->_left == parent)
			{
				node* uncle = pparent->_right;
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					pparent->_col = RED;

					cur = pparent;
					parent = pparent->_parent;
				}
				else
				{
					if (parent->_left == cur)
					{
						rotateR(pparent);

						//cur->_col = RED;
						parent->_col = BLACK;
						pparent->_col = RED; //这个之前一定是黑
					}
					else
					{
						rotateL(parent);
						rotateR(pparent);

						cur->_col = BLACK;//这竟然写错了
						pparent->_col = RED;
					}
					break;
				}
			}
			else
			{
				node* uncle = pparent->_left;
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = BLACK;
					parent->_col = BLACK;
					pparent->_col = RED;
				}
				else
				{
					if (parent->_right == cur)
					{
						rotateL(pparent);

						parent->_col = BLACK;
						pparent->_col = RED;
					}
					else
					{
						rotateR(parent);
						rotateL(pparent);

						cur->_col = BLACK;
						pparent->_col = RED;
					}
					break;
				}
			}
			_root->_col = BLACK;
		}
		return { Iterator(newnode), true };
	}
  • 返回值的变化,和取值的变化
  • 取值可以用  KeyOfType  的匿名对象来取

代码

  • return 也可以:pair<Iterator, bool>(newnode, true);  or  make_pair(Iterator(newnode), true)  or make_pair(newnode, true),不建议最后一种写法;effectiv C++建议:应该尽量避免隐式类型转换,以防止潜在的错误和难以维护的代码;回过来看,第三种因为 iterator是一个node*的封装,而不是node* 
  • 列表初始化:一个重要特性是防止窄化转换,比如 防止double 变成 int ;防止隐式类型转换还是要使用explicit
  • explicit只能用于修饰构造函数转换运算符;explicit operator int() const{}这是转换运算符(隐式转换运算符)
  • 不能传引用返回,因为return的是一个临时对象,出作用域会被销毁,导致悬空引用;make_pair也是传值返回,因为他是返回的是pair的匿名对象

set

namespace nineone
{
	template<class K>
	class set
	{
		struct keyofset
		{
			const K& operator()(const K& lhs)  const
			{
				return lhs;
			}
		};
	public:
		typedef typename RBTree<K, const K, keyofset>::Iterator iterator;
		typedef typename RBTree<K, const K, keyofset>::ConstIterator const_iterator;

		iterator begin()
		{
			return rbt.Begin();
		}

		iterator end()
		{
			return rbt.End();
		}

		const_iterator begin() const
		{
			return rbt.Begin();
		}

		const_iterator end() const
		{
			return rbt.End();
		}

		pair<iterator, bool> insert(const K& k)
		{
			return rbt.Insert(k);
		}

		iterator find(const K& key)
		{
			return rbt.Find(key);
		}
	private:
		RBTree<K, const K, keyofset> rbt;
	};
}

 解释

  • 通过组合的方式,用RBTree来封装set,隐藏RBTree类实现细节,提供用户简洁的接口
  • typedef前面一定要加 typename;没有实例化的类模板去取内嵌类型,是有异议的,编译器不知道这是成员函数名还是成员变量;iterator一定要是public,因为要在类外使用,对于RBTree里的别名同理

解释:两个别名和成员变量的第二个模板参数必须一样

  • 前提要点,不同类型参数实例化的类模板被视为不同类型,所以不能直接转换(编译器不提供隐式类型转换);
  • 这三个只要有一个不一样就会报类型无法转换的错误;报错的地方在insert或者find return的地方
  • 我写文章的时候再次调试,报错的地方是对的,在const_iterator end() const这里报错,就是这里,返回语句的地方;因为这个rbt对象构建的树节点的K,是const K类型;但是const_iterator的树节点类型是K,导致返回类型与返回语句的类型不一样,且类型不兼容★
  • 如果函数的返回类型与声明里的返回类型无法隐式类型转换类型不兼容),编译器就会报错

解释:第二个模板参数

  • 这个模板参数作用到的是节点和迭代器
  • 但是在ConstIterator里后面两个模板参数不是有const了;编译器会自动去掉多余的const,但是你自己多加会编译报错
  • 第二个参数是const,那么迭代器里,_node的类型是RBTreeNode<const T>*,这是一个普通指针;于指针而言,可以指向不同的RBTreeNode<const T>对象;与对象而言,对象里有部分是可以改变的,只有_data是const;const RBTreeNode<const T>*这样才是一个常量指针
  • 所以_node不会因为T为const导致++不能使用

map

namespace nineone
{
	template<class K, class V>
	class map
	{
		struct keyofmap
		{
			const K& operator()(const pair<K, V>& kv) const
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<const K, V>, V>::Iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, V>::ConstIterator const_iterator;
		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return rbt.Insert(kv);
		}

		iterator find(const K& key)
		{
			return rbt.Find(key);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = rbt.Insert(make_pair(key, V()));
			return ret.first->second;
		}

		iterator begin()
		{
			return rbt.Begin();
		}

		iterator end()
		{
			return rbt.End();
		}

		const_iterator begin() const
		{
			return rbt.Begin();
		}

		const_iterator end() const
		{
			return rbt.End();
		}

	private:
		RBTree<K, pair<const K, V>, keyofmap> rbt;
	};
}

 解释insert

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

心理咨询系统|心理咨询系统开发|心理咨询软件开发

在快节奏的现代生活中&#xff0c;心理健康问题越来越受到人们的关注。为了有效应对这些问题&#xff0c;心理咨询系统应运而生&#xff0c;它为人们提供了一个安全、便捷的平台&#xff0c;以寻求心理帮助和支持。本文将详细介绍心理咨询系统的功能、优势以及未来发展趋势。 …

vue项目实战 - 如果高效的实现防抖和节流

在Vue项目中&#xff0c;处理高频事件的优化至关重要&#xff0c;直接影响用户体验和应用性能。防抖&#xff08;Debounce&#xff09;和节流&#xff08;Throttle&#xff09;是两种常用且有效的方法&#xff0c;可以控制事件触发频率&#xff0c;减少不必要的资源消耗。如何在…

使用Word表格数据快速创建图表

实例需求&#xff1a;Word的表格如下所示&#xff0c;标题行有合并单元格。 现在需要根据上述表格数据&#xff0c;在Word中创建如下柱图。如果数据在Excel之中&#xff0c;那么创建这个图并不复杂&#xff0c;但是Word中就没用那么简单了&#xff0c;虽然Word中可以插入图表&a…

免费撸gpt-4o和各种大模型实用经验分享

项目 Github: https://github.com/MartialBE/one-api 先贴两张图&#xff1a; 说明 免费撸AI大模型,各位可以对照下面我给出的大模型记录表来填&#xff0c;key需要自己去拿&#xff0c;国内都需要手机号验证&#xff0c;如果你不介意。另外我在自己的博客放出免费API给大家…

自定义RedisTemplate序列化器

大纲 RedisSerializerFastJsonRedisSerializer自定义二进制序列化器总结代码 在《RedisTemplate保存二进制数据的方法》一文中&#xff0c;我们将Java对象通过《使用java.io库序列化Java对象》中介绍的方法转换为二进制数组&#xff0c;然后保存到Redis中。实际可以通过定制Red…

[emailprotected](2)核心概念-JSX

目录 1&#xff0c;什么是 jsx2&#xff0c;空标签3&#xff0c;通过大括号使用 js4&#xff0c;防止注入攻击5&#xff0c;元素的不可变性 官方文档 1&#xff0c;什么是 jsx Facebook 起草的 js 扩展语法。本质上是 js 对象&#xff0c;会被 babel 编译&#xff0c;最终转换…

根据多个坐标经纬度获取到中心点的经纬度,scala语言

文章目录 前言scala 代码 总结 前言 Scala 语言 通过多个经纬度坐标点, 计算出中心点, 这里使用的是 Scala 语言,其他的语言需要自行转换。求出来的并不是原有的点&#xff0c;而是原有点的中心位置的点。 scala 代码 package com.dw.process.midimport java.lang.Double.pa…

【test】Windows11下通过sshfs挂载远程服务器目录

下载安装下面三个软件&#xff1a; sshfs-win&#xff1a;https://github.com/billziss-gh/sshfs-win/releases winfsp&#xff1a;https://github.com/billziss-gh/winfsp/releases SSHFS-Win Manager&#xff1a;https://github.com/evsar3/sshfs-win-manager/releases 安装…

数据结构---优先级队列(堆)

博主主页: 码农派大星. 数据结构专栏:Java数据结构 关注博主带你了解更多数据结构知识 1. 优先级队列 1.1 概念 前面介绍过队列&#xff0c;队列是一种先进先出(FIFO)的数据结构&#xff0c;但有些情况下&#xff0c;操作的数据可能带有优先级&#xff0c;一般出队 列时&am…

python低阶基础100题(上册)

** python低阶基础100题&#xff08;上册&#xff09; ** 1. 请打印出字符串 Hello World print("Hello World")2. 请打印出字符串 爸爸妈妈&#xff0c;你们辛苦啦 print("爸爸妈妈&#xff0c;你们辛苦啦")3. 请打印出字符串 人生苦短&#xff0c;我…

如何使用Studio 3T导出MongoDB数据成excel?

导出MongoDB查询集合数据成excel 1. 新建查询页面&#xff0c;输入指定的查询语句&#xff0c;执行查询获取结果。 这里以查询集合accountbackLogger表中的reqTime字段日期是2024年5月的数据为列。 db.getCollection("accountbackLogger").find({reqTime:{$gte: IS…

UE4/UE5像素流送云推流:多人访问不稳定、画面糊、端口占用多等

UE4/UE5想要实现网页访问&#xff0c;很多工程师会选择guan方的像素流送。但这个技术要求在模型开发初期就接入。对于一些已有UE模型是无法进行流化的。虽然也可以解决新UE模型的网页访问问题&#xff0c;但在实际的应用中&#xff0c;点量云流也收到很多反馈说&#xff0c;使用…

LeetCode题练习与总结:从中序与后序遍历序列构造二叉树--106

一、题目描述 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7], postorder [9,15,7,20,3] 输出…

【百度云千帆AppBuilder】诗词达人:AI引领的诗词文化之旅

文章目录 写在前面&#xff1a;百度云千帆AppBuilder诗词达人&#xff1a;AI引领的诗词文化之旅功能介绍&#xff1a;诗词达人智能体的深度体验1. 诗词接龙学习2. 诗词深度解析3. 互动式问答4. 诗词创作辅助 技术特点详解&#xff1a;"诗词达人"智能体的创新技术零代…

【论文笔记】Layer-Wise Weight Decay for Deep Neural Networks

Abstract 本文为了提高深度神经网络的训练效率&#xff0c;提出了逐层权重衰减(layer-wise weight decay)。 本文方法通过逐层设置权重衰减稀疏的不同值&#xff0c;使反向传播梯度的尺度与权重衰减的尺度之比在整个网络中保持恒定。这种设置可以避免过拟合或欠拟合&#xff0…

胶原蛋白流失大揭秘:你的肌肤还年轻吗?

&#x1f343;当我们谈及胶原蛋白&#xff0c;不少女生眼中都会闪过一丝光芒。为什么呢&#xff1f;因为胶原蛋白是维持我们肌肤弹性、水润的秘密武器啊&#xff01;但是&#xff0c;随着岁月的流逝&#xff0c;你是否发现自己的肌肤开始变得松弛、无弹性&#xff0c;甚至出现了…

亚马逊测评技术自己掌控:打造爆款产品,快速突破销量瓶颈

不管新老店铺来说&#xff0c;出单都是至关重要的&#xff0c;在我们的理解当中测评应该是一种成长剂&#xff0c;是一个加快店铺成长的工具&#xff0c;因为它在店铺的破0、突破瓶颈期、引爆爆款以及在后期店铺的一个补量上都会有一个明显的作用 测评有什么意义&#xff1f; …

Vue实现二维码的展示及下载

个人介绍 hello hello~ &#xff0c;这里是 code袁~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的…

amtlib.dll打不开怎么办?一键修复丢失amtlib.dll方法

电脑丢失amtlib.dll文件是什么情况&#xff1f;出现amtlib.dll打不开怎么办&#xff1f;这样的情况有什么解决方法呢&#xff1f;今天就和大家聊聊amtlib.dll文件同时教大家一键修复丢失amtlib.dll方法&#xff1f;一起来看看amtlib.dll文件丢失会有哪些方法修复&#xff1f; a…

新手做抖音小店应该注意哪些问题?怎么正确的做抖音小店?

大家好&#xff0c;我是电商花花。 我们想做好一家抖音小店&#xff0c;想长期持久的做好一家抖店&#xff0c;一定要注意下面这些问题&#xff0c;只有避开这些做店的坑&#xff0c;我们才能稳稳的出单&#xff0c;稳稳的赚钱。 做抖音小店不能无脑铺货&#xff0c;要做精细…