【C++】:红黑树的应用 --- 封装map和set

news2024/11/25 16:37:20

点击跳转至文章:【C++】:红黑树深度剖析 — 手撕红黑树!

目录

  • 前言
  • 一,红黑树的改造
    • 1. 红黑树的主体框架
    • 2. 对红黑树节点结构的改造
    • 3. 红黑树的迭代器
      • 3.1 迭代器类
      • 3.2 Begin() 和 End()
  • 四,红黑树相关接口的改造
    • 4.1 Find 函数的改造
    • 4.2 Insert 函数的改造
  • 五,红黑树改造的完整代码
  • 六,set 的封装实现
  • 七,map 的封装实现

前言

map 和 set 的底层本质上还是复用通过对红黑树的改造,再分别套上一层 map 和 set 的 “壳子”,以达到 “一树二用” 的目的

在改造红黑树的过程中,我大概归纳了以下几个需要重点解决的问题:

(1) 对红黑树节点的改造。关联式容器中存储的是<key, value>的键值对,K为key的类型,如果是 set 则是 K,如果是map,则为pair<K, V>。如何用一个节点结构控制两种类型,使用类模板参数T

(2) 在插入操作时,如何完成数据的比较。由于我们的节点类型的泛型,如果是 set 则是 K,如果是map,则为pair<K, V>,而pair的比较是由 first 和 second 共同决定的,这显然不符合要求。因此插入数据时不能直接比较,要在 set 和 map 类中实现一个 KeyOfT 的仿函数,以便单独获取两个类型中的 key 数据

(3) 在红黑树中实现普通迭代器和const迭代器,再套上 “壳子”

(4) 关于 key 的修改问题。在STL库中,set 和 map 的 key 都是不能修改的,因为要符合二叉搜索树的特性,但是 map 中的 value 又是可以修改的。这个问题需要单独处理。

(5) 红黑树相关接口的改造。其中包括对 Find 和 Insert 函数的改造,特别是 Insert,因为在 map 里实现 operator[] 时需要依赖 Insert 函数。

说明:如果大家要自己动手实现封装,可以按照上面五个问题的流程进行实现。但是在本篇文章中由于展示等的原因,无法按照上面的步骤

一,红黑树的改造

1. 红黑树的主体框架

(1) K 是给find,erase用的,T 是给节点,insert用的

(2) KeyOfT 是由于下面需要比较,但是比较时不知道T的类型, set是key类型的比较,map是pair类型的比较,要统一变成key的比较

template <class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node; //节点
public:
	typedef RBTreeIterator<T, T&, T*> Iterator; //普通迭代器
	typedef RBTreeIterator<T, const T&, const T*> ConstIterator; //const 迭代器

	//其他功能的实现……
	
private:
	Node* _root = nullptr;
};

2. 对红黑树节点结构的改造

//枚举颜色
enum Colour
{
	RED,
	BLACK
};

//节点类
template <class T>
struct RBTreeNode
{
	T _data;

	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	//pair<K, V> _kv;
	Colour _col;

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

3. 红黑树的迭代器

3.1 迭代器类

//迭代器类
template <class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;

	Node* _node;

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

	bool operator!=(const Self& s)
	{
		return s._node != _node;
	}

	Ref operator*()
	{
		return _node->_data;
	}

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

	//从局部的某一个过程考虑
	Self& operator++()
	{
		if (_node->_right)
		{
			//右不为空,右子树的最左节点就是下一个访问的节点
			Node* leftMost = _node->_right;
			while (leftMost->_left)
				leftMost = leftMost->_left;

			_node = leftMost;
		}
		else
		{
			//右为空,代表当前节点所在的子树已经访问完了,下一个访问的节点是祖先
			//沿着到根节点的那个路径查找,孩子是父亲左的那个祖先节点就是下一个访问的节点

			Node* cur = _node;
			Node* parent = cur->_parent;
			//循环找祖先
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}
};

迭代器中最复杂的就是 operator++()的实现,它与原先的 vector/list 不同,红黑树的迭代器是要完成二叉树的中序遍历

为了完成二叉树的中序遍历,我们需要从局部的某一过程考虑,就是假设 it 已经走到了某一节点,要找到下一个访问的节点,分为两种情况:

(1) 当前节点的右子树不为空

如下图,假设 it 已经到达了13节点,说明此时13的左子树已经访问完了,右子树不为空,下一个要访问的节点就是右子树的最左节点15

在这里插入图片描述

(2) 当前节点的右子树为空

如下图,假设 it 此时到达了15节点,它的右子树为空,下一个访问哪个节点呢?有些人单纯的认为是15的父亲17,其实是错误的

那假设 it 到达6节点上呢,6的右为空,但是根据中序遍历的顺序,6的父亲1已经访问过了。

在这里插入图片描述

其实此时是要找当前节点的祖先,父亲也是祖先之一

右为空,代表当前节点所在的子树已经访问完了,下一个访问的节点是祖先,是哪个祖先呢?沿着到根节点的那个路径查找,孩子是父亲左的那个祖先节点就是下一个访问的节点

(a) 假设此时走到了15节点,下一个访问的节点是17,cur 是 parent 的左,parent 就是下一个要访问的那个祖先节点;

在这里插入图片描述

(b) 假设此时走到了6节点,下一个访问的节点是8,但是此时 cur 是 parent 的右,不满足条件,继续向上查找,cur = parent,parent = parent->_parent,这时 cur 在1,parent 在8,cur 是 parent 的左,parent 就是下一个要访问的那个祖先节点

在这里插入图片描述

3.2 Begin() 和 End()

//中序遍历,找树的最左节点
Iterator Begin()
{
	//Node* cur = _root;
	Node* leftMost = _root;

	while (leftMost->_left)
		leftMost = leftMost->_left;

	return Iterator(leftMost);
}

Iterator End()
{
	return Iterator(nullptr);
}

ConstIterator Begin()const
{
	//Node* cur = _root;
	Node* leftMost = _root;

	while (leftMost->_left)
		leftMost = leftMost->_left;

	return ConstIterator(leftMost);
}

ConstIterator End()const
{
	return ConstIterator(nullptr);
}

Iterator Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_data > key)
			cur = cur->_left;
		else if (cur->_data < key)
			cur = cur->_right;
		else
			return Iterator(cur);
	}
	return End();
}

四,红黑树相关接口的改造

4.1 Find 函数的改造

查找成功,返回查找到的那个节点的迭代器,查找失败,就返回 nullptr。

Iterator Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_data > key)
			cur = cur->_left;
		else if (cur->_data < key)
			cur = cur->_right;
		else
			return Iterator(cur);
	}
	return End();
}

4.2 Insert 函数的改造

map 里的 operator[] 需要依赖 Insert 的返回值

pair<Iterator, bool> Insert(const T& data)
{
	if (_root == nullptr)
	{
		_root = new Node(data);
		return make_pair(Iterator(_root), true);
	}

	KeyOfT kot;
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		if (kot(cur->_data) > kot(data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (kot(cur->_data) < kot(data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else
			return make_pair(Iterator(cur), false);
	}
	cur = new Node(data);
	Node* newnode = cur;

 	//此处省略变色+旋转部分的代码……

五,红黑树改造的完整代码

说明:由于代码太长,影响展示效果,所以插入部分的 变色+旋转 的代码此处省略,和红黑树的基本一模一样,请前往【C++】:红黑树深度剖析 — 手撕红黑树!

RBTree.h

#include <iostream>
#include <assert.h>
using namespace std;

//枚举颜色
enum Colour
{
	RED,
	BLACK
};

//节点类
template <class T>
struct RBTreeNode
{
	T _data;

	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	//pair<K, V> _kv;
	Colour _col;

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

//迭代器类
template <class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;

	Node* _node;

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

	bool operator!=(const Self& s)
	{
		return s._node != _node;
	}

	Ref operator*()
	{
		return _node->_data;
	}

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

	//从局部的某一个过程考虑
	Self& operator++()
	{
		if (_node->_right)
		{
			//右不为空,右子树的最左节点就是下一个访问的节点
			Node* leftMost = _node->_right;
			while (leftMost->_left)
				leftMost = leftMost->_left;

			_node = leftMost;
		}
		else
		{
			//右为空,代表当前节点所在的子树已经访问完了,下一个访问的节点是祖先
			//沿着到根节点的那个路径查找,孩子是父亲左的那个祖先节点就是下一个访问的节点

			Node* cur = _node;
			Node* parent = cur->_parent;
			//循环找祖先
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}
};


//K 是给find,erase用的,T 是给节点,插入用的
// KeyOfT 是由于下面需要比较,但是比较时不知道T的类型,
// set是key类型的比较,map是pair类型的比较,要统一变成key的比较

template <class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef RBTreeIterator<T, T&, T*> Iterator;
	typedef RBTreeIterator<T, const T&, const T*> ConstIterator;


	//中序遍历,找树的最左节点
	Iterator Begin()
	{
		//Node* cur = _root;
		Node* leftMost = _root;

		while (leftMost->_left)
			leftMost = leftMost->_left;

		return Iterator(leftMost);
	}

	Iterator End()
	{
		return Iterator(nullptr);
	}

	ConstIterator Begin()const
	{
		//Node* cur = _root;
		Node* leftMost = _root;

		while (leftMost->_left)
			leftMost = leftMost->_left;

		return ConstIterator(leftMost);
	}

	ConstIterator End()const
	{
		return ConstIterator(nullptr);
	}

	Iterator Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_data > key)
				cur = cur->_left;
			else if (cur->_data < key)
				cur = cur->_right;
			else
				return Iterator(cur);
		}
		return End();
	}

	pair<Iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			return make_pair(Iterator(_root), true);
		}

		KeyOfT kot;
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else
				return make_pair(Iterator(cur), false);
		}
		cur = new Node(data);
		Node* newnode = cur;

		//新增节点,颜色为红色
		cur->_col = RED;
		
		//此处省略变色+旋转部分的代码……
		
private:
		Node* _root = nullptr;

};

六,set 的封装实现

set的底层为红黑树,因此只需在set内部封装一棵红黑树,即可将该容器实现出来。

为了解决 set 中 key 值不能修改的问题,在传给 RBTree 的第二个模板参数前加 const 即可

MySet.h

#include "RBTree.h"

namespace cc
{
	template<class K>
	class set
	{
		// 获取 set 里面的 key
		struct SetOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	public:
		typedef typename RBTree<K, const K, SetOfT>::Iterator iterator;
		typedef typename RBTree<K, const K, SetOfT>::ConstIterator const_iterator;

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

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

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

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

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

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

//使用 const 迭代器 
void Print(const cc::set<int>& s)
{
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

//测试代码
void Test_set()
{
	//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	int a[] = { 16,3,7,11,9,26,18,14,15 };
	cc::set<int> s;

		for (auto e : a)
			s.insert(e);

		for (auto e : s)
			cout << e << " ";

		cout << endl;

		Print(s);
}

在这里插入图片描述

七,map 的封装实现

map的底层结构就是红黑树,因此在map中直接封装一棵红黑树,然后将其接口包装下即可。

map 中 pair 的 first 不能修改,second 可以修改,在 pair 的第一个参数前加 const 即可

MyMap.h

#include "RBTree.h"

namespace cc
{
	template<class K, class V>
	class map
	{
		//获取 pair 中的 key
		struct MapOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<const K, V>, MapOfT>::Iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, MapOfT>::ConstIterator const_iterator;


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

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

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

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

		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _t.Insert(kv);
		}

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

		//给一个key,返回value的引用
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		RBTree<K, pair<const K,V>, MapOfT> _t;
	};
}


//测试代码
void Test_map()
{
	cc::map<string, string> dict;

	dict.insert({ "left","左边" });
	dict.insert({ "right","右边" });
	dict.insert({ "insert","插入" });

	dict["left"] = "剩余,左边";

	cc::map<string, string>::iterator it = dict.begin();
	while (it != dict.end())
	{
		//it->first += 'x'; //err
		it->second += 'y'; //ok

		cout << it->first << ":" << it->second << endl;
		++it;
	}

	cout << endl;
}

在这里插入图片描述

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

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

相关文章

Qt基础 | 自定义界面组件 | 提升法 | 为UI设计器设计自定义界面组件的Widget插件 | MSVC2019编译器中文乱码问题

文章目录 一、自定义 Widget 组件1.自定义 Widget 子类2.自定义 Widget 组件的使用 二、自定义 Qt Designer 插件1.创建 Qt Designer Widget 插件项目2.插件项目各文件的功能实现3.插件的编译与安装4.使用自定义插件5.使用 MSVC 编译器输出中文的问题 一、自定义 Widget 组件 当…

【React】详解受控表单绑定

文章目录 一、受控组件的基本概念1. 什么是受控组件&#xff1f;2. 受控组件的优势3. 基本示例导入和初始化定义函数组件处理输入变化处理表单提交渲染表单导出组件 二、受控组件的进阶用法1. 多个输入框的处理使用多个状态变量使用一个对象管理状态 2. 处理选择框&#xff08;…

leetcode-104. 二叉树的最大深度

题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a; 输入&#xff1a;root [1,n…

24款美规奔驰GLS450更换中规高配主机系统,提升车辆功能和使用体验

平行进口奔驰GLS450 语音小助手要说英语 十分的麻烦 而且也没有导航&#xff0c;原厂记录仪也减少了 很不方便 那要怎么解决呢 往下看 其实很简单&#xff0c;我们只需要更换一台中规的新主机就可以实现以下功能&#xff1a; ①中国地图 ②语音小助手&#xff08;你好&#…

C++编译jsoncpp库

下载https://github.com/hailong0715/jsoncpp/tree/master windows编译工程 jsoncpp-master\makefiles\vs71 1.msvcprtd.lib(MSVCP140D.dll) : error LNK2005 解决办法&#xff1a; (1).工程(Project)->属性(Properties)->配置属性(Configuration Properties)->c/c-…

OZON打开哈萨克斯坦市场,OZON测试开通哈萨克斯坦市场中国产品

在全球化日益深入的今天&#xff0c;跨境电商成为了连接不同国家和地区消费者的重要桥梁。2024年7月26日&#xff0c;Ozon Global宣布了一项重大扩展计划&#xff0c;正式将中国卖家的销售版图拓展至哈萨克斯坦市场&#xff0c;为中国企业打开了新的增长机遇之门。 OZON哈萨克斯…

2024AGI面试官 常问的问题以及答案(附最新的AI大模型算法面试大厂必考100题 )

前言 在这个人工智能飞速发展的时代&#xff0c;AI大模型已经成为各行各业创新与变革的重要驱动力。从自动驾驶、医疗诊断到金融分析&#xff0c;AI大模型的应用场景日益广泛&#xff0c;为我们的生活带来了前所未有的便捷。作为一名程序员&#xff0c;了解并掌握AI大模型的相…

移植QT项目出现无法找到 v143 的生成工具(平台工具集 =“v143”)。若要使用 v143 生成工具进行生成,请安装 v143 生成工具。

由于使用的是visual studio2019&#xff0c;在扩展里没找到msvc v143的工具集&#xff0c;这时候可能需要升级下版本&#xff0c;比如换用visual studio2022 或者在三个地方更改所使用的工具集&#xff0c;一般来讲只要v143编译能通过的v142编译也能通过&#xff0c;所以换用v…

ctfshow-web入门-php特性(web147-web150_plus)

目录 1、web147 2、web148 3、web149 4、web150 5、web150_plus 1、web147 ^&#xff1a;匹配字符串的开头。 $&#xff1a;匹配字符串的结尾&#xff0c;确保整个字符串符合规则。 [a-z0-9_]&#xff1a;表示允许小写字母、数字和下划线。 *&#xff1a;匹配零个或多个前面…

c++入门----类与对象(中)

OK呀&#xff0c;家人们承接上文&#xff0c;当大家看过鄙人的上一篇博客后&#xff0c;我相信大家对我们的c已经有一点印象了。那么我们现在趁热打铁再深入的学习c入门的一些知识。 类的默认成员函数 首先我们学习的是我们的默认函数。不知道大家刚读这个名词是什么反应。默认…

一下午连续故障两次,谁把我们接口堵死了?!

唉。。。 大家好&#xff0c;我是程序员鱼皮。又来跟着鱼皮学习线上事故的处理经验了喔&#xff01; 事故现场 周一下午&#xff0c;我们的 编程导航网站 连续出现了两次故障&#xff0c;每次持续半小时左右&#xff0c;现象是用户无法正常加载网站&#xff0c;一直转圈圈。 …

2020 CSP第一题:数字拆分

2020 CSP第一题&#xff1a;数字拆分 示例1 输入 6 输出 4 2 题意&#xff1a; 实质就是将一个偶数转化为二进制数&#xff0c;然后分别用十进制逆序输出每一项 数据约束&#xff1a; n最大在10的七次方左右&#xff0c;int类型够了&#xff0c;十进制转化为二进制后&#x…

重生之“我打数据结构,真的假的?”--3.栈和队列

1.栈和队列的基本概念 1.1 栈 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则…

鸿蒙开发——axios封装请求、拦截器

描述&#xff1a;接口用的是PHP&#xff0c;框架TP5 源码地址 链接&#xff1a;https://pan.quark.cn/s/a610610ca406 提取码&#xff1a;rbYX 请求登录 HttpUtil HttpApi 使用方法

开源模型应用落地-LangChain实用小技巧-ChatPromptTemplate的partial方法(一)

一、前言 在当今的自然语言处理领域&#xff0c;LangChain 框架因其强大的功能和灵活性而备受关注。掌握一些实用的小技巧&#xff0c;能够让您在使用 LangChain 框架时更加得心应手&#xff0c;从而更高效地开发出优质的自然语言处理应用。 二、术语 2.1.LangChain 是一个全方…

TCP/IP协议(全的一b)应用层,数据链层,传输层,网络层,以及面试题

目录 TCP/IP协议介绍 协议是什么,有什么作用? 网络协议为什么要分层 TCP/IP五层网络协议每层的作用 应⽤层 DNS的作用及原理 DNS工作流程 数据链路层 以太⽹帧格式 MAC地址的作用 ARP协议的作⽤ ARP协议的工作流程 MTU以及MTU对 IP / UD / TCP 协议的影响 传输层…

MySQL(持续更新中)

第01章_数据库概述 1. 数据库与数据库管理系统 1.1 数据库相关概念 DB&#xff1a;数据库&#xff08;Database&#xff09;即存储数据的“仓库”&#xff0c;其本质是一个文件系统。它保存了一系列有组织的数据DBMS&#xff1a;数据库管理系统&#xff08;Database Manageme…

2024年【广东省安全员B证第四批(项目负责人)】考试报名及广东省安全员B证第四批(项目负责人)模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 广东省安全员B证第四批&#xff08;项目负责人&#xff09;考试报名根据新广东省安全员B证第四批&#xff08;项目负责人&#xff09;考试大纲要求&#xff0c;安全生产模拟考试一点通将广东省安全员B证第四批&#x…

AFast and Accurate Dependency Parser using Neural Networks论文笔记

基本信息 作者D Chendoi发表时间2014期刊EMNLP网址https://emnlp2014.org/papers/pdf/EMNLP2014082.pdf 研究背景 1. What’s known 既往研究已证实 传统的dp方法依存句法分析特征向量稀疏&#xff0c;特征向量泛化能力差&#xff0c;特征计算消耗大&#xff0c;并且是人工构…

UE5 with plugins AirSim in Windows ROS in WSL2-Ubuntu 20.04配置过程记录

一、概述 因为需要使用到Windows系统下的UE5和插件AirSIm进行研究&#xff0c;所以在Windows环境下进行配置。但又因为需要使用到ros进行操作&#xff0c;所以&#xff0c;在通过对诸多资源进行考察过后&#xff0c;因为UE5plugins AirSim已经配置成功。只需要考虑跟ROS的通信以…