【C++】map和set的使用及其模拟实现

news2024/11/18 22:42:55

文章目录

  • 一、map和set的使用
    • 1. 关联式容器
    • 2. 键值对
    • 3. 关联式容器的使用
      • 3.1 set
      • 3.2 multiset
      • 3.3 map
      • 3.4 multimap
  • 二、map和set的模拟实现
    • 1. 红黑树的实现(封装map和set版本)
      • 1.1 节点的实现
      • 1.2 KeyOfT(仿函数)
      • 1.3 红黑树的插入Insert
      • 1.4 迭代器iterator
    • 2. set的模拟实现
    • 3. map的模拟实现
    • 4. RbTree的完整源码


一、map和set的使用

1. 关联式容器

在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?

关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。

💕 树形结构的关联式容器:

根据应用场景的不桶,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结构的关联式容器主要有四种:mapsetmultimapmultiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。


2. 键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量keyvaluekey代表键值,value表示与key对应的信息。

比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。

SGI-STL中关于键值对的定义:

template <class T1, class T2>
struct pair
{
	typedef T1 first_type;
	typedef T2 second_type;
	T1 first;
	T2 second;
	pair(): first(T1()), second(T2())
	{}
	pair(const T1& a, const T2& b): first(a), second(b)
	{}
};

这里我们可以看到,pair类中的first就是键值key,second就是key对应的信息 value;所以以后在定义KV模型的时候,只需要在容器/容器中的每一个节点中定义一个pair对象即可。

💕 make_pair函数

由于 pair 是类模板,所以我们通常是以 显式实例化 + 匿名对象 的方式来进行使用,但是由于显式实例化比较麻烦,所以 C++ 还提供了 make_pair 函数,其定义如下:

在这里插入图片描述

make_pair函数的内部实现如下:

template <class T1,class T2>
pair<T1,T2> make_pair (T1 x, T2 y)
{
  return ( pair<T1,T2>(x,y) );
}

make_pair 返回的是一个 pair 的匿名对象,匿名对象会自动调用 pair 的默认构造完成初始化;但由于 make_pair 是一个函数模板,所以模板参数的类型可以根据实参来自动推导完成隐式实例化,这样我们就不用每次都显式指明参数类型了。


3. 关联式容器的使用

3.1 set

set是一种key模型的容器,也就是说,set中只有键值key,而没有对应的value,并且每个key都是唯一的,set容器中的元素不允许修改,因为那样会破坏搜索树的规则。但是set允许插入和删除。

在这里插入图片描述
T: set中存放元素的类型,实际在底层存储<value, value>的键值对。
Compare: set中元素默认按照小于来比较
Alloc: set中元素空间的管理方式,使用STL提供的空间配置器管理

总结:

  • set 是K模型的容器,所以 set 中插入元素时,只需要插入 key 即可,不需要构造键值对;
  • set中的元素不可以重复,因此可以使用set进行去重;
  • 由于 set 底层是搜索树,所以使用 set 的迭代器遍历 set 中的元素,可以得到有序序列,即 set 可以用来排序;
  • set 默认使用的仿函数为 less,所以 set 中的元素默认按照小于来比较;
  • 由于 set 底层是平衡树搜索树,所以 set 中查找某个元素,时间复杂度为 O(logN);
  • set 中的元素不允许修改,因为这可能破坏搜索树的结构;
  • set 中的底层使用平衡二叉搜索树 (红黑树) 来实现。

💕 set的使用

构造

在这里插入图片描述

迭代器

在这里插入图片描述

修改

在这里插入图片描述

其他操作

在这里插入图片描述

set使用范例

void TestSet()
{
	// 用数组array中的元素构造set
	int array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	set<int> s(array, array + sizeof(array) / sizeof(array[0]));
	cout << s.size() << endl;

	// 正向打印set中的元素,从打印结果中可以看出:set可去重
	for (auto& e : s)
		cout << e << " ";
	cout << endl;

	// 使用迭代器逆向打印set中的元素
	for (auto it = s.rbegin(); it != s.rend(); ++it)
		cout << *it << " ";
	cout << endl;

	// set中值为3的元素出现了几次
	cout << s.count(3) << endl;
}

在这里插入图片描述


3.2 multiset

multiset 也是 K模型 的容器,它和 set 唯一的区别在于 multiset 中允许存在重复的 key 值节点 ,所以 multiset 可以用来排序和查找,但是不能用来去重

在这里插入图片描述

💕 multiset的使用

multiset 的使用和 set 几乎一样,这里我们需要注意两点即可:

  • 由于 multiset 中允许存在重复 key 值的节点,所以 multiset 中 count 函数就有作用了,我们可以通过 count 函数来统计同一 key 中在 multiset 中的数量。
  • multiset 中 find 函数的使用也和 set 有所区别 – 由于 set 中没有重复的节点,所以 find 时要么返回该节点位置的迭代器,要么返回 end();而 multiset 中可能有重复的节点,所以 find 时返回的是同一 key 值中的哪一个节点呢?实际上 find 返回的是中序遍历过程中第一个匹配的节点位置的迭代器。

multiset的使用范例

void multiset_test() {
	// 构造multiset
	int array[] = { 1, 3, 5, 7, 3, 2, 4, 6, 8, 0, 1, 3, 5, 7, 3, 2, 4, 6, 8, 3 };
	multiset<int> s(array, array + sizeof(array) / sizeof(array[0]));
	cout << s.size() << endl;

	// 正向打印multiset中的元素,multiset允许重复元素的出现
	for (const auto& e : s)
		cout << e << " ";
	cout << endl;

	//如果查找的key在multiset中有多个,则返回中序遍历中遇到的第一个节点的迭代器
	multiset<int>::iterator pos = s.find(3);
	while (pos != s.end()) {
		cout << *pos << " ";
		++pos;
	}
	cout << endl;

	// 查找+删除
	cout << s.count(3) << endl; //输出key为3的节点的个数
	pos = s.find(3);
	if (pos != s.end())
		s.erase(pos);
	cout << s.count(3) << endl;
}

在这里插入图片描述


3.3 map

map 和 set 一样都是按照一定次序存储元素的容器,其底层也是一棵平衡二叉搜索树;和 set 不同的是,map 是 KV模型 的容器在map 中,键值 key 通常用于排序和惟一地标识元素,而值 value中 用于存储与此键值 key 关联的内容,键值 key 和值value的类型可以不同; 在 map 内部,key-value 通过成员类型 pair 绑定在一起,也就是我们文章开始提到的键值对

在这里插入图片描述

需要注意的是:map 中的元素是按照键值 key 进行比较排序的,而与 key 对应的 value 无关,同时,map 中也不允许有重复 key 值的节点,也不允许修改key,但是它可以修改节点中key对应的value;map 也可用于排序、查找和去重,且 map 查找的时间复杂度也为 O(logN)。

💕 map的使用

构造

在这里插入图片描述

迭代器

在这里插入图片描述

修改

在这里插入图片描述

元素访问

在这里插入图片描述

[ ]运算符重载的函数定义如下:

mapped_type& operator[] (const key_type& k) 
{
	(*((this->insert(make_pair(k, mapped_type()))).first)).second;
}
//拆解后的函数
V& operator[] (const K& key)
{
	pair<iterator, bool> ret = insert(make_pair(key, V()));
	//return *(ret.first)->second;
	return ret.first->second;
}

下面我们来分析一下这个函数:

  • operator[] 函数先向map容器中插入key:如果map中有与该值相等的节点,则插入失败,返回的pair中存放着该节点位置的迭代器和false;如果map中没有与该节点相等的节点,则插入成功,该节点的value值为 V 默认构造的缺省值。
  • 然后,operator[] 会取出pair迭代器(ret,first)然后对迭代器进行解引用得到一个pair<k,v>对象,最后再返回排pair对象中的second的引用,即key对应的value的引用。因此我们可以直接在函数外部修改key对应的value的值。

map的使用案例

void map_test() {
	map<string, string> m;

	m.insert(pair<string, string>("peach", "桃子"));

	//为了方便,我们建议直接用make_pair函数来构造键值对
	m.insert(make_pair("banan", "香蕉"));

	m["apple"] = "苹果";

	for (auto& e : m)
		cout << e.first << ":" << e.second << endl;
	cout << endl;

	// map中的键值对key一定是唯一的,如果key存在将插入失败
	auto ret = m.insert(make_pair("peach", "桃色"));
	if (ret.second)
		cout << "<peach, 桃色>不在map中, 已经插入" << endl;
	else
		cout << "键值为 peach 的元素已经存在:" << ret.first->first << "--->" << ret.first->second << " 插入失败" << endl;

	// 删除key为"apple"的元素
	m.erase("apple");
	if (1 == m.count("apple"))
		cout << "apple还在" << endl;
	else
		cout << "apple被吃了" << endl;
}

在这里插入图片描述


3.4 multimap

同样,multimap与map的区别和multiset与set的区别是一样的。find返回的是中序遍历中遇到的第一个节点的迭代器。count返回和key值相等的节点的个数。

这里我们需要注意的是:multimap中没有重载[]运算符,因为multimap中的key值是可以重复的。如果使用 [] 运算符,会导致无法确定访问哪个哪一个元素。


二、map和set的模拟实现

我们知道,map和set容器的底层都是使用红黑树实现的。但是,set是K模型的的容器,而map是KV模型的容器,他们却都能使用红黑树来封装。下面,我们先来看一下源码中是如何实现的。

在这里插入图片描述

这里我们发现,set头文件中包含了 stl_set.hstl_multiset.h ,而map头文件中包含了 stl_map.hstl_multimap.h,而stl_tree.h应该就是用来封装实现他们的红黑树。

在这里插入图片描述

这里我们可以看到:set和map的增删查改功能都是封装的同一个红黑树的接口。

对于 map来说,key_type 就是我们平常所理解的 K,但是 value_type 却是 pair 类型,它的两个参数分别是模板参数 _Key 和 _Tp,也就是我们认为的传递给 map 的 K 和 V;

而对于 set 来说,key_type 也是我们平时认为的 K,但是我们发现 set 中居然还有一个变量 value_type,并且 value_type 的类型就是 key_type 的类型。

下面我们来看一下红黑树的源码,看一下为什么对于这种K模型还需要设计一个value_type变量呢?

//stl_tree.h
//红黑树的基类
struct __rb_tree_node_base
{
  typedef __rb_tree_color_type color_type;
  typedef __rb_tree_node_base* base_ptr;

  color_type color; 
  base_ptr parent;
  base_ptr left;
  base_ptr right;
};

//红黑树的节点结构
template <class Value>
struct __rb_tree_node : public __rb_tree_node_base
{
  typedef __rb_tree_node<Value>* link_type;
  Value value_field;
};

//红黑树
template <class Key, class Value, class KeyOfValue, class Compare,
          class Alloc = alloc>
class rb_tree {
protected:
  typedef __rb_tree_node_base* base_ptr;
  typedef __rb_tree_node<Value> rb_tree_node;
public:
  typedef Key key_type;
  typedef Value value_type;
  typedef rb_tree_node* link_type;
}

set、maprbtree之间的关系:

在这里插入图片描述

通过这张图我们就可以理解,为什么set和map是不同模型的容器,但是他们却都可以使用同一棵树红黑树作为底层容器了。

如果是 map,则红黑树的模板参数 _Val 被实例化为 pair\u003CK,V>,那么红黑树节点中存储的数据的类型也是 pair\u003CK,V>;如果是 set,则红黑树的模板参数 _Val 被实例化为 K,此时红黑树节点中存储的数据的类型就为 K;

也就是说,红黑树完全可以根据第二个模板参数 _Val 的实参类型的不同实例化出符合 set 要求和 符合 map 要求的不同的两个类。这里我们来解答一下传递第一个模板参数 _Key 的作用?

这是因为某些函数需要使用 key,比如 find 函数就只能通过 key 进行查询,即在 map 的 find 中不能通过 _Val 即 pair 类型来进行查询。


1. 红黑树的实现(封装map和set版本)

现在,我们需要参考源码中的方式将我们自己实现的红黑树改造一下,使得它既能够封装map,又能够封装set。

1.1 节点的实现

//创建红黑树的节点
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 RBTree
{
	typedef RBTreeNode<T> Node;
private:
	Node* _root = nullptr;
};

1.2 KeyOfT(仿函数)

我们在 insert 时需要比较 key 的大小来确定插入位置,在之前模拟实现的红黑树中,我们直接将节点定义为了pair<K,V> 类型,所以我们可以直接取 kv.first 进行比较;但是现在,节点中的数据既可能是 pair 类型,也可能是 key 的类型,所以我们不能再用 data.first,同时由于stl 中实现的 pair 比较函数并不是单纯比较 first,而是先比较 first,再比较 second。

为了实现在插入操作中既可以比较Key模型的Key值,又可以比较KV模型的Key值,我们可以自己写一个仿函数来完成红黑树中节点数据 _data大小的比较了。当然,源码中也是这样实现的。

//set
namespace cjl
{
	template<class K>	
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}
//map
namespace cjl
{
	template<class K, class V>
	class map
	{
		struct MapKeyofT
		{
			const K& operator()(const pair<const K,V>& kv)
			{
				return kv.first;
			}
		};
	private:
		RBTree<K, pair<const K,V>, MapKeyofT> _t;
	};
}

1.3 红黑树的插入Insert

template<class K, class T, class KeyOfT>
class RBTree {
	typedef RBTreeNode<T> Node;
public:
	bool insert(const T& data) {
		//判断根节点是否为空
		if (_root == nullptr) {
			_root = new Node(data);
			_root->_col = BLACK;  //根节点的颜色一定为黑
			return true;
		}

		//寻找插入位置
		KeyOfT kot;  //仿函数对象
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur) {
			if (kot(data) < kot(cur->_data)) {
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(data) > .kot(cur->_data)) {
				parent = cur;
				cur = cur->_right;
			}
			else {
				return false;  //不允许重复节点
			}
		}
		
		Node* newNode = new Node(data);
		if (kot(data) < kot(parent->_data))
			parent->_left = newNode;
		else
			parent->_right = newNode;
		newNode->_parent = parent;  //修改父节点指向
        
        //调整节点
        //...
        return true;
    }
private:
    Node* _root;
};

1.4 迭代器iterator

💕 begin() 迭代器

begin()迭代器的位置是红黑树中序遍历的第一个节点,也就是整棵树的最左节点。

💕 end() 迭代器

end()迭代器是红黑树中序遍历最后一个元素的下一个位置。为了让 end() 能够指向最右节点的下一个节点, stl 源码中增加了一个哨兵位的头结点,该节点的 left 指向这棵树的最左节点,也就是begin(),right 指向这棵树的最右节点,parent 指向 nullptr,然后让根的父节点指向它,最右节点的 right 也指向它;所以在 stl 源码的实现中这个哨兵位的头结点就是 end();

在这里插入图片描述

这里我们直接将end()定义为nullptr即可。

💕 迭代器的++ 与 - -

因为红黑树的中序遍历是一个有序序列,所以++就是跳转到红黑树中序遍历的下一个节点的位置。--则是反过来跳转到中序遍历中上一个节点的位置。

在这里插入图片描述

+ +情况的分类

  • 若当前节点有右孩子,那么迭代器++跳转到右子树的最左节点。
  • 若当前节点没有右孩子,则迭代器++跳转到当前节点为父节点

- - 情况的分类

  • 若当前节点有左孩子,那么迭代器 - - 跳到左子树的最右节点;
  • 若当前节点没有左孩子,则迭代器 - - 跳到 当前节点为 父节点的右孩子 的那一个祖先节点;
//迭代器的实现
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;
	}

	//重载 ++it
	Self& operator++()
	{
		//本质上是找中序遍历的下一个元素——分为两种情况:right == nullptr 和 right != nullptr

		if (_node->_right) // right != nullptr
		{
			Node* parent = _node->_right;
			
			while (parent->_left)
			{
				parent = parent->_left;
			}
			_node = parent;
		}
		else //right == nullptr
		{

			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && cur == parent->_right)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	//重载 --it
	Self& operator--()
	{
		if (_node->_left) // left != nullptr
		{
			Node* parent = _node->_left;

			while (parent->_right)
			{
				parent = parent->_right;
			}
			_node = parent;
		}
		else //left == nullptr
		{

			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

};

2. set的模拟实现

set插入查询函数直接使用适配器模式,调用红黑树对应的函数即可完成,这里最重要的是迭代器的复用。

在这里插入图片描述

当我们直接复用红黑树的迭代器时,会发现一个问题,我们可以使用迭代器来修改key值,但是如果这样的话必定会破坏搜索树的结构。

在这里插入图片描述

我们直接让 set 的普通迭代器和 const 迭代器都使用红黑树的 const 迭代器来进行封装即可,这样 set 迭代器解引用得到的就是 const K& 了;但是这样做之后发现虽然的确不能通过迭代器来修改 key 的值了,但是这里又发生了新的错误:

在这里插入图片描述

其实这是因为在insert中返回的是红黑树的普通迭代器,但是接受返回值的却是红黑树的const迭代器。

要解决这个问题的方法其实也并不难,我们需要在红黑树的迭代器中实现一个拷贝构造函数。

  • 当模板实例化为 <T, T&, T*> 时,iterator 和 Self 相同,此时这是一个拷贝构造函数;
  • 当模板实例化为 <T, const T&, const T*> 时,Self 是 const 迭代器,而 iterator 是普通迭代器,此时这是一个构造函数,它可以用普通迭代器构造出一个 const 迭代器。

所以我们可以通过红黑树迭代器类中构造函数来实现用普通迭代器来构造 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)
	{}

	__RBTreeIterator(const __RBTreeIterator<T,T&,T*>& iterator)
		:_node(iterator._node)
	{}
	//运算符的重载...
};

在这里插入图片描述

💕 set模拟实现源码:

namespace cjl
{
	template<class K>	
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:	

		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator 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);
		}

	public:
		
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}

3. map的模拟实现

namespace cjl
{
	template<class K, class V>
	class map
	{
		struct MapKeyofT
		{
			const K& operator()(const pair<const K,V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::const_iterator 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<const K, V>& kv)
		{
			return _t.Insert(kv);
		}

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

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

	private:
		RBTree<K, pair<const K,V>, MapKeyofT> _t;
	};
}

4. RbTree的完整源码

#pragma once

//枚举类型,枚举红黑树的颜色红色和黑色
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 T,class Ref,class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;

	//迭代器的构造函数
	__RBTreeIterator(Node* node)
		:_node(node)
	{}

	__RBTreeIterator(const __RBTreeIterator<T,T&,T*>& iterator)
		:_node(iterator._node)
	{}

	//重载*
	Ref operator*()
	{
		return _node->_data;
	}
	
	//重载->
	Ptr operator->()
	{
		return &(_node->_data);
	}

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

	//重载 ++it
	Self& operator++()
	{
		//本质上是找中序遍历的下一个元素——分为两种情况:right == nullptr 和 right != nullptr

		if (_node->_right) // right != nullptr
		{
			Node* parent = _node->_right;
			
			while (parent->_left)
			{
				parent = parent->_left;
			}
			_node = parent;
		}
		else //right == nullptr
		{

			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && cur == parent->_right)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	//重载 --it
	Self& operator--()
	{
		if (_node->_left) // left != nullptr
		{
			Node* parent = _node->_left;

			while (parent->_right)
			{
				parent = parent->_right;
			}
			_node = parent;
		}
		else //left == nullptr
		{

			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

};

//创建红黑树
template<class K, class T ,class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	//析构
	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

	//迭代器的实现
	typedef __RBTreeIterator<T, T&, T*> iterator;
	typedef __RBTreeIterator<T, const T&, const T*> const_iterator;

	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 iterator(cur);
	}

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

	//查询
	iterator Find(const K& key)
	{
		KeyOfT kot;
		Node* cur = _root;
		while (cur)
		{
			if (key > kot(cur->_data))
				cur = cur->_right;
			else if (key < kot(cur->_data))
				cur = cur->_left;
			else
				return iterator(cur);
		}
		return iterator(nullptr);
	}

	//插入
	pair<iterator, bool> Insert(const T& data)
	{
		//搜索树的插入过程
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);
		}

		KeyOfT kot;

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kot(data) > kot(cur->_data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(data) < kot(cur->_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)// 情况1:u存在且为红,变色处理,并继续往上处理
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					//继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2 + 3:u不存在/u存在且为黑 --- 旋转 + 变色
				{
					//     g
					//   p   u
					// c 
					if (cur == parent->_left)
					{
						//右单旋
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//     g
						//   p   u
						//     c
						//先对parent节点进行左单旋,在对grandfather进行右单旋
						RotateL(parent);
						RotateR(grandfather);
						//调整颜色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else // (grandfather->_right == parent)
			{
				// u存在且为红,变色处理,并继续往上处理
				//    g
				//  u   p
				//        c
				Node* uncle = grandfather->_left;

				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					//    g
					//  u   p
					//    c
					if (cur == parent->_left)
					{
						RotateR(parent);
						RotateL(grandfather);

						//调整颜色
						cur->_col = BLACK;
						grandfather->_col = RED;

					}
					else
					{
						//    g
						//  u   p
						//        c
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;

		return make_pair(iterator(newnode), true);
	}

	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	//判断是否为红黑树
	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:
	//红黑树的高度计算
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		int leftHight = _Height(root->_left);
		int rightHight = _Height(root->_right);

		return leftHight > rightHight ? leftHight + 1 : rightHight + 1;
	}

	bool _Check(Node* root, int blackNum, int benchmark)
	{
		if (root == nullptr)
		{
			if (blackNum != benchmark)
			{
				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 _Destroy(Node* root)
	{
		if (root == nullptr)
			return;
		_Destroy(root->_left);
		_Destroy(root->_right);

		delete root;
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		KeyOfT kot;
		_InOrder(root->_left);
		cout << kot(root->_data) << " ";
		_InOrder(root->_right);
	}
	//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;

		//这里我们需要注意一下,subRL有可能为空
		if (subRL)
			subRL->_parent = parent;

		Node* ppnode = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;

		//判断父节点是否为_root节点
		if (parent == _root) {
			_root = subR;
			_root->_parent = nullptr;
		}
		else {
			if (parent == ppnode->_left)
				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;

		//保存parent节点的_parent节点
		Node* ppnode = parent->_parent;

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

		//判断parent节点是否为_root节点
		if (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == ppnode->_left)
				ppnode->_left = subL;
			else
				ppnode->_right = subL;
			subL->_parent = ppnode;
		}
	}

private:
	Node* _root = nullptr;
};

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

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

相关文章

【分布式】分片存储服务器

完成了配置路由服务器之后&#xff0c;我们来到了对存储服务器的重构 在我们加入配置服务器组之后&#xff0c;我们发现我们的kvserver&#xff0c;也就是存储服务器还是单点的&#xff1a; 同样&#xff0c;我们可以借助之前实现的raft对单点进行拓展&#xff0c;我们可以设置…

【LeetCode热题100】打卡第7天:盛最多水的容器

文章目录 盛最多水的容器⛅前言&#x1f512;题目&#x1f511;题解 盛最多水的容器 ⛅前言 大家好&#xff0c;我是知识汲取者&#xff0c;欢迎来到我的LeetCode热题100刷题专栏&#xff01; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合…

自制Arduino 风格开发板 - HK32F030MF4P6 紧凑开发板

模仿Arduino Nano 做一个HK32F030M 的紧凑开发板&#xff0c;排针间距和Arduino Nano 相同&#xff0c;整体尺寸略小&#xff0c;适合插在面包板上。兼容HK32F030MF4P6 和0301M&#xff0c;板载CH340N 串口和DS1307 时钟模块。开源工程地址&#xff1a;HK32F030MF4P6 紧凑开发板…

Python学习36:文本分析与加密

类型&#xff1a;字符串‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬ 描述‪‬‪…

测试百科:白盒测试用例的设计

正文 语句覆盖&#xff1a;每条语句至少执行一次。判定覆盖&#xff1a;每个判定的所有可能结果至少出现一次。&#xff08;又称“分支覆盖”&#xff09;条件覆盖&#xff1a;每个条件的所有可能结果至少执行一次。判定/条件覆盖&#xff1a;一个判定中的每个条件的所有可能结…

PyTorch-网络模型的保存和读取

1. 模型的保存 方法一&#xff1a;保存模型的结构模型的参数 陷阱&#xff1a;需要让文件访问到你自己的模型定义方式&#xff0c;可以用import的方式引入先前的模型定义。 model_save.py import torch import torchvisionvgg16 torchvision.models.vgg16(weightsNone) # …

Linux— 网络编程套接字

目录 预备知识 认识端口号 理解源端口号和目的端口号 认识TCP协议 认识UDP协议 网络字节序 socket编程接口 socket 常见API sockaddr结构 sockaddr 结构​编辑 sockaddr_in 结构 in_addr结构 地址转换函数 简单的UDP网络程序 实现一个简单的英译汉的功能 简易的远程…

注解-反射-XML配置原理

java刚开始原本是直接在方法中创建对象执行程序等&#xff0c;部分代码重复率高&#xff0c;后来就发展成方法封装调用&#xff0c;再后来出现的像spring框架等&#xff0c;引入了XML配置&#xff0c;使得程序更加简洁&#xff0c;方便等&#xff0c;其中XML配置也是基于java反…

java 线程安全和多线程

文章目录 前言一、ThreadLocal是什么&#xff1f;二、synchronized 和 ReentrantLock 都是 Java 中提供的可重入锁&#xff0c;二者的主要区别有以下 5 个&#xff1a;三、线程安全的集合类有哪些&#xff1f;四、说一下你对CompletableFuture的理解四、项目中是如何创建线程池…

R语言生物群落(生态)数据统计分析与绘图(从数据整理到分析结果展示)

R 语言作的开源、自由、免费等特点使其广泛应用于生物群落数据统计分析。生物群落数据多样而复杂&#xff0c;涉及众多统计分析方法。以生物群落数据分析中的最常用的统计方法回归和混合效应模型、多元统计分析技术及结构方程等数量分析方法为主线&#xff0c;通过多个来自经典…

C++类和对象三

文章目录 类和对象三初始化列表用途与特性 explicit关键字再谈构造函数static成员static的特性 友元友元函数友元函数特性 友元类友元类特性 内部类概念特性 匿名对象拷贝对象时的一些编译器优化 类和对象三 初始化列表 初始化列表&#xff1a;以一个冒号开始&#xff0c;接着…

【源码解析】Spring Bean生命周期源码解析

Spring启动核心 AbstractApplicationContext#refresh&#xff0c;Spring刷新容器的核心方法。最关键的就是 AbstractApplicationContext#invokeBeanFactoryPostProcessors&#xff0c;扫描BeanAbstractApplicationContext#finishBeanFactoryInitialization&#xff0c;生成Be…

【MySql】InnoDB一棵B+树可以存放多少行数据?

文章目录 背景一、怎么得到InnoDB主键索引B树的高度&#xff1f;二、小结三、最后回顾一道面试题总结参考资料 背景 InnoDB一棵B树可以存放多少行数据&#xff1f;这个问题的简单回答是&#xff1a;约2千万。为什么是这么多呢&#xff1f;因为这是可以算出来的&#xff0c;要搞…

[C语言实现]带你手撕带头循环双链表

目录 什么是双链表&#xff1f; 带头结点的优势: 双链表的实现: 什么是循环双链表&#xff1f; 众所周知&#xff0c;顺序表的插入和删除有时候需要大量移动数据&#xff0c;并且每次开辟空间都可能会浪费大量内存和CPU资源&#xff0c;于是我们有了链表&#xff0c;我们之…

【实用篇】SpringCloud01

SpringCloud01 1.认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢&#xff1f; 1.0.学习目标 了解微服务架构的优缺点 1.1.单体架构 单体架构&#xff…

基于复旦微 JFM7K325T 全国产FPGA的高速数据采集、图像处理方案

板卡概述 PCIE-XM711 是一款基于 PCIE 总线架构的高性能数据预处理 FMC载板&#xff0c;板卡采用复旦微的 JFM7K325T FPGA 作为实时处理器&#xff0c;实现 各个接口之间的互联。该板卡可以实现 100%国产化。 板卡具有 1 个 FMC&#xff08;HPC&#xff09;接口&#xff0c;1 路…

这10道测试用例面试题,面试官肯定会问到

前言 软件测试面试中&#xff0c;测试用例是非常容被问到的一个点&#xff0c;今天小编就给大家把最常见的测试用例方面的问题给大家整理出来&#xff0c;希望对大家的面试提供帮助。 1、 什么是测试用例‍ 答&#xff1a;测试用例的设计就是如何覆盖所有软件表现出来的状态…

硬盘有多少图片取决我的网速, Python获取硬盘少女图集

目录 前言开发环境:案例实现的步骤:代码展示尾语 &#x1f49d; 前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 图片数据在网络中随处可见, 如果需要一些图片素材 一睹为快把! ! ! 开发环境: 解释器: python 3.8 编辑器: pycharm 2022.3 专业版 内置模块使用 os &g…