红黑树封装set和map(插入部分)

这篇具有很好参考价值的文章主要介绍了红黑树封装set和map(插入部分)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

之前我们实现了红黑树的插入的部分,本文主要介绍将之前实现的红黑树封装成map和set。我们是以学习的角度来封装容器,不用非要把库中容器所有功能都实现出来。我们主要目的是学习库中代码设计技巧和模板复用的思想。

1.设计大致思路

我们在实现之前还是和以前一样去看看库中是怎么实现的。这里先简单介绍一下库中容器实现的思路。库中设计的大概思路是:将红黑树设计一个类模板,我们map和set直接复用一颗红黑树,将红黑树的接口进行封装形成自己的接口。这样相当于map和set是都是复用一份代码,我们只用维护好红黑树的代码就可以实现出相关容器了。

红黑树封装set和map(插入部分)

那我们来思考一下,这个map和set最大的区别就是存储的节点。set只是存储的单一key值,而存储的是key-val键值对。因此我们知道就必须有个模板参数控制红黑树节点是存储的什么值。同时我们因为我们find和erase这些接口需要知道这个key的类型,因此还要单独有个模板参数的来标识key。

红黑树封装set和map(插入部分)

同时对于存储的节点值不确定,我们需要在进行节点key值比较的时候可以定义出仿函数用于控制比较逻辑,我们用什么值来进行比较。也就是说第三个keyOft,是用来控制比较逻辑的,在map和set中进行封装的时候传入对应的仿函数即可。

有了这个思路后,我们将之前的红黑树改成一个类模板。

2.改造封装红黑树

我们将之前的写好的红黑拿过来改造,旋转变色调整逻辑我们不用改动,我们唯一要改动的就是这个插入逻辑,我们先将红黑树的大体框架拿过来。

节点构建

enum Colour
{
	RED,
	BLACK,
};

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)
	{}
};
template<class K, class T,class KeyOft>
class RBTree
{
public:
typedef RBTreeNode<T> Node;
	Node* Find(const T& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

	void InOrder()
	{
		_InOrder(_root);
	}

	bool IsBalance()
	{
		if (_root && _root->_col == RED)
		{
			cout << "根节点颜色是红色" << endl;
			return false;
		}

		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++benchmark;
			}
			cur = cur->_left;
		}


		return _Check(_root, 0, benchmark);
	}

	int Height()
	{
		return _Height(_root);
	}

private:
	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	int _Height(Node* root)
	{
		if (root == NULL)
			return 0;

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}

	bool _Check(Node* root, int blackNum, int benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}

		if (root->_col == BLACK)
		{
			++blackNum;
		}

		if (root->_col == RED
			&& root->_parent
			&& root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return _Check(root->_left, blackNum, benchmark)
			&& _Check(root->_right, blackNum, benchmark);
	}

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

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

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

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

		Node* ppnode = parent->_parent;

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

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

			subR->_parent = ppnode;
		}
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		Node* ppnode = parent->_parent;

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

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

private:
	Node* _root = nullptr;
};

上述代码除了添加了3个模板参数其余的都没有改动,这样就是有了个大概的框架。我们接着就是实现插入逻辑和迭代器了。

1.插入节点

这里节点存储什么值,就是插入什么值。节点存储的值是由这个第二个模板参数决定的。这个插入节函数的参数就确定好了,const T&data。这个插入节点返回值我们去看看库中的实现。

红黑树封装set和map(插入部分)

这里返回值是一个pair,这个pair里面存储的是对应位置的节点的迭代器和插入情况。bool值表示是否插入成功,如果已经存在相等的key返回的迭代器就是指向这个key的迭代器,如果插入成功返回的迭代器就是指向这个新插入的节点。

pair< iterator, bool> Insert(const T& data)
	  { 
		 KeyOft kot;
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);
		}

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

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

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{

					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else // (grandfather->_right == parent)
			{

				Node* uncle = grandfather->_left;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{

					if (cur == parent->_right)
					{
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{

						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;
		return make_pair(iterator(newnode), true);
	}

红黑树封装set和map(插入部分)
红黑树封装set和map(插入部分)

这个唯一不同的就是把这个newnode提前保存一下,因为这个节点可能会被调整,保存后通过这个节点构造一个匿名对象迭代器插入到pair中并且返回。这里迭代器并没有实现出来,我们先这样写,之后再实现出迭代器即可。这里返回bool值是为了更清楚知道插入节点的情况。

红黑树封装set和map(插入部分)
红黑树封装set和map(插入部分)

这里还需要注意的情况就是这个第三个模板参数,这个第三模板参数是用来控制比较逻辑的,其实就是重载这个()这个运算符,在通过对应的对象来控制这个比较逻辑。因此凡是比较的地方我们通过第三个参数来加以控制。

2.迭代器的实现

迭代器的实现结合我们之前实现链表的迭代器也是采用实现迭代器类模板这种方式来解决。我们的const迭代器和普通迭代器都可以复用这一套模板。结合之前的经验,我们还是采用节点指针来模拟原生指针的行为,我们需要3个模板参数,一个参数用来确定节点中存储值的类型,一个参数是为了模拟原生指针->的操作,作为重载->的返回值,还有一个参数是用来模拟&原生指针的操作,作为操作&的返回值。因为我们想让const迭代器也复用这段代码,所以采用模板实现。

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)
	{}
	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	bool operator!=(const Self& s )
	{
		return _node != s._node;
	}
	Self& operator++()
	{
		if (_node->_right)
		{
			Node* subRight = _node->_right;
			//右子树中的最左节点
			while (subRight->_left)
			{
				subRight=subRight->_left;
			}
			_node = subRight;
		}
		else
		{
			Node* cur = _node;
			Node* parent = _node->_parent;
			while (parent&& parent->_right == cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
	Self& operator--()
	{
		if (_node->_left)
		{
			Node* subLeft  = _node->_left;
			//左子树中的最右节点
			while (subLeft->_right)
			{
				subLeft = subLeft->_right;
			}
			_node = subLeft;
		}
		else
		{
			Node* cur = _node;
			Node* parent = _node->_parent;
			while (parent&& parent->_left = cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}


};

这里->和*以及&没啥好说的,重点在于这个前置++和前置--的操作.关于这个++和–我们还是需要上图来分析一下。

红黑树封装set和map(插入部分)

这里++操作需要结合图去看,将图看懂了代码就很明了。这个++it还是根据这个二叉搜索树的特性来确定这个节点指针的移动方向的。

红黑树封装set和map(插入部分)

这个–操作和++操作其实刚好是对称的,对于这个节点移动我们可以先将最好分析的先分析出来,在结合图去移动指针。比如这个it指向的节点有左子树,这就是最好分析一种情况。

红黑树封装set和map(插入部分)

这个迭代器类实现好以后,我们在红黑树的模板类中申明重命名一下这个迭代器类型。

iterator begin()
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}

		return iterator(cur);
	}

	iterator end()
	{
		return iterator(nullptr);
	}
	const_iterator begin() const
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return const_iterator(cur);
	}

	const_iterator end() const
	{
		return const_iterator(nullptr);
	}
	

然后我们实现一下对应的迭代器接口即可。这个begin是指向红黑树中最小的元素的,也就是红黑树左子树中最左节点。找到这个节点后将其构造对应的迭代器返回即可。这个end接口,我们将其设置为空就行了,通过空指针来构造对应的迭代器。这样的话,我们就将红黑树的类模板给实现好了,map和set直接进行简单的封装复用即可。

3.map和set的封装

1.代码实现

这里map和set的封装其实就是复用一下红黑树这个类模板,让这个类模板实例化出对应的容器

template<class K,class V>
class Map
{
	
public:
	struct MapKeyOft
	{
		const K &operator()(const pair<K,V>&kv)
		{
			return kv.first;
		}
	};
public:
	typedef typename RBTree<K, pair<const K, V>, MapKeyOft>::iterator iterator;
	iterator begin()
	{
		return _t.begin();
	}

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

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

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

};

map的话直接复用红黑树的接口即可。我们定义一个内部类来重载()将这个类作为第三个实例化的模板参数传入红黑树中。我们在对迭代器重命名的时候加上一个typename进行修饰,告诉编译器这是一个类型而不是类中的一个变量。这里map重点实现了这个[ ]重载,这里是调用的insert函数来实现的,这样即可以查找对应的val值,还可以插入新的键值对,同时也可以修改对应的val值,简直是妙不可言。

template<class K>
class Set
{

public:
	struct SetKeyOft
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};

public:
  typedef typename RBTree<K, K, SetKeyOft>::iterator iterator;
  iterator begin()
  {
	  return _t.begin();
  }

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

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

Set同样也是如此,复用红黑树的接口即可。这里同样实现了一个内部类进行作为红黑树的实例化的第三个参数用于来控制这个比较逻辑。这里map和set和内部类是控制红黑树中节点中谁和谁进行比较,set的话只有key这个肯定是key和key进行比较,但是map是键值对,这里就是用来控制红黑树中的pair节点是通过key来进行比较的。如果我们想要实现比较逻辑的话,我们还可以加上一个模板参数,用来接收比较的仿函数。

2.简单测试

#include<iostream>
#include"Map.h"
#include"Set.h"
using namespace std;
void test_Set1()
{
	int a[] = { 11, 1, 7, 10, 14, 11, 22, 14, 15,89 };
	Set<int> s;
	for (auto e : a)
	{
		s.insert(e);
	}

	Set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
}
void test_Map1()
{
	Map<string, string> dict;
	dict.insert(make_pair("sort", "排序"));
	dict.insert(make_pair("string","字符串"));
	dict.insert(make_pair("count", "计数"));
	dict.insert(make_pair("left", "左边")); 

	Map<string, string>::iterator it = dict.begin();
	while (it != dict.end())
	{
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	
	cout << endl;

	for (auto& kv : dict)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;
}

void test_Map2()
{
	string arr[] = { "苹果", "苹果", "梨子", "梨子", "香蕉", "香蕉", "香蕉", "哈密瓜", "草莓", "火龙果" };
	Map<string, int> countMap;
	for (auto& e : arr)
	{
		countMap[e]++;
	}

	for (auto& kv : countMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
}
int main()
{
	test_Map1();
	test_Map2();
	test_Set1();
}

红黑树封装set和map(插入部分)

从打印结果上来看,我们封装的map和set基本实现了插入节点的功能,迭代器也正确实现出来了。以上便是对map和set的简单封装,总的来说就是模板套一层模板的意思。最里面是红黑树的壳子,通过这套壳子来实例化出不同的容器。这里的模板复用技巧非常值得我们学习,比如红黑树模板参数的确定以及相关的意义。为啥要这么设计,都是值得我们细细揣摩的。

以上内容,如有问题,欢迎指正!文章来源地址https://www.toymoban.com/news/detail-468573.html

到了这里,关于红黑树封装set和map(插入部分)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++ 改造红黑树,封装map和set

    set是Key模型的红黑树 map是Key-Value模型的红黑树 我们之前已经把红黑树实现并测试过了 这是Key-Value模型的红黑树 RBTree.h 要想用红黑树来封装set和map,我们首先想到的是在搞一份Key模型的红黑树, 然后用Key模型红黑树来封装set,Key-Value模型红黑树封装map 但是STL库中set和map的设计却

    2024年03月17日
    浏览(50)
  • C++【一棵红黑树封装 set 和 map】

    ✨个人主页: 北 海 🎉所属专栏: C++修行之路 🎃操作环境: Visual Studio 2019 版本 16.11.17 红黑树的基本情况我们已经在上一篇文章中学习过了,本文主要研究的是红黑树的实际应用:封装实现 set 和 map ,看看如何通过一棵红黑树满足两个不同的数据结构;在正式封装之前,

    2024年02月11日
    浏览(40)
  • C++ | 红黑树以及map与set的封装

    目录 前言 一、红黑树 1、红黑树的基本概念 2、红黑树相关特性 3、红黑树结点的定义 4、红黑树的查找 5、红黑树的插入 6、二叉树的拷贝构造与析构 7、红黑树的检测 8、红黑树总结 二、map与set的封装 1、红黑树的结点 2、红黑树迭代器 3、set的封装 4、map的封装 三、源码仓库

    2024年02月14日
    浏览(37)
  • 【C++】map与set容器——红黑树底层封装

    💭STL中,容器大概可分为两种类型——序列式容器和关联式容器。在前面的系列文章中,我们已经介绍了诸多序列式容器,如:vector、list、stack、queue等,它们以序列的形式存储数据。 💭而关联式容器也是一种非常重要的容器。标准的STL关联式容器分为set(集合)和map(映

    2023年04月11日
    浏览(40)
  • 【C++】使用红黑树进行封装map和set

    🌇个人主页:平凡的小苏 📚学习格言:命运给你一个低的起点,是想看你精彩的翻盘,而不是让你自甘堕落,脚下的路虽然难走,但我还能走,比起向阳而生,我更想尝试逆风翻盘 。 🛸 C++专栏 : C++内功修炼基地 家人们更新不易,你们的👍点赞👍和⭐关注⭐真的对我真

    2024年02月07日
    浏览(46)
  • 【C++】用红黑树迭代器封装map和set

    封装有点难 - . - 文章目录 前言 一、红黑树原先代码的修改 二、红黑树迭代器的实现 总结 因为我们要将红黑树封装让map和set使用,所以我们要在原来的基础上将红黑树代码进行修改,最主要的是修改模板参数,下面我们直接进入正题: 首先我们拿出STL中的源代码,看看大佬

    2024年02月06日
    浏览(42)
  • 【C++】用一棵红黑树同时封装出map和set

    苦厄难夺凌云志,不死终有出头日。 1. 在源码里面,对于map和set的实现,底层是用同一棵红黑树封装出来的,并不是用了两棵红黑树,一个红黑树结点存key,一个红黑树结点存key,value的键值对,这样的方式太不符合大佬的水准了,实际上在红黑树底层中用了一个模板参数Va

    2023年04月13日
    浏览(43)
  • Learning C++ No.23【红黑树封装set和map】

    北京时间:2023/5/17/22:19,不知道是以前学的不够扎实,还是很久没有学习相关知识,对有的知识可以说是遗忘了许多,以该篇博客有关知识为例,我发现我对迭代器和模板的有关知识的理解还不够透彻,不知道是对以前知识的遗忘,还是现在所学确实有难度,反正导致我很懵

    2024年02月05日
    浏览(50)
  • 【C++】STL——用一颗红黑树封装出map和set

    我们都知道set是K模型的容器,而map是KV模型的容器,但是它俩的底层都是用红黑树实现的,上篇博文中我们模拟实现了一颗红黑树,接下来将对其进行改造,继而用一颗红黑树封装出map和set。 本质上map和set其内部的主要功能都是套用了红黑树现成的接口,只是稍作改动即可

    2023年04月15日
    浏览(35)
  • 【C++】红黑树 --- map/set 底层

    红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是 Red 或 Black . 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的;如下图: 每个结点不是红色就是黑色;

    2024年02月04日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包