【C++ 第十五章】map 和 set 的封装(封装红黑树)

news2024/9/22 13:40:24

1. map 和 set 的介绍

⭐map 与 set 分别是STL中的两种序列式容器;

它们是一种树形数据结构的容器,且其的底层构造为一棵红黑树;

而在上一篇文章中提到,其实红黑树本身就是一棵二叉搜索树,是基于二叉搜索树的性质对其增加了平衡的属性来提高其综合性能

⭐当然也提到了红黑树与AVL树的区别:

1、AVL树保证了严格的平衡,其树高不会很高,使其查找效率较高,但是就是因为要不断旋转保证平衡,因此 当插入和删除时,较多的旋转会影响效率

2、红黑树不用保证严格的平衡,查找的时间复杂度为 O(logN) 级别(和AVL树)在 插入和删除 中,只需要较少的旋转,因此 插入和删除 效率较高

综合考虑 map 和 set 使用 红黑树作为底层容器

2. map 和 set 的结构

在 map 与 set 的使用过程中,由于 set 容器的底层树节点存储着数据为 key (T 类型)

而 map 的底层树节点存储着数据为一个键值对 key/value; (pair类型)

所以可能会联想到在STL中的这两个容器是否使用的是不同的红黑树?

而实际在 STL 的源码中可以看到,对于这两个容器而言所使用的是同一个红黑树(即调用同一棵红黑树),并且利用泛型的特性来控制两个容器中所使用的对应的参数;

那么既然是同一棵红黑树,应该如何对这棵树进行修改 使得该树能够同时兼容 map 的 key/value 键值对数据存储 和 set 的 key 数据存储  呢?

3、对红黑树的进一步修改

在我们的上一章节 讲解了红黑树的各种基础构造,本章对红黑树进一步修改,融入 迭代器 以及 泛型化使其更加适配 map 与 set

(1)修改一:将节点数据类型换成 T (泛型的思想)

将节点中数据变量 换成 类型T

当 T == key 类型时,该节点对应 set 容器

当 T == pair<key, value> 类型时,该节点对应 map 容器

这样就初步实现,同一节点类模板,可以对应 多种数据类型,适应 set 和 map

template<class T>
struct RBTreeNode
{
	T _data;  // 泛型化思想:_data 可以是 Key 类型,也可以是 pair<Key, Value> 类型
    
    // .....
};

先前 set 和 map 要设计两套 红黑树类

为了适应 set:

template<class Key>
class RBTree
{}

为了适应 map:

template<class Key, class Value>
class RBTree
{}

现在节点类泛型化了,红黑树类也要对应修改

template<class T>
class RBTree
{}

(2)修改二:应用仿函数 修改 插入函数 insert 内部比较逻辑

⭐之前没修改时,为了适应 set 和 map ,要设计两种传参类型

为了适应 set:

bool insert(const K& key)
{}

为了适应 map:

bool insert(const pair<K, V>& kv)
{}

⭐insert 函数内部比较键值大小的部分 也有两套设计

 当 T == key 时,对应 set,insert 函数内部比较键值大小的部分:直接比较键值 key

if (cur->_key < key) {
    // .....
}

当 T == key/value 时,对应 map,insert 函数内部比较键值大小的部分:还要指定键值对的 first

if (cur->_kv.first < kv.first) {
    // .....
}

现在 节点数据泛型为 T,函数 insert 传参类型和内部某些比较逻辑都需要做调整

⭐ 修改传参类型

bool insert(const T& data)
{}

⭐内部某些比较逻辑:使用仿函数

因为我们那里的就是要用 键值 key 比较,因此 set 可以直接用节点数据 key 比较,而 map 需要用 节点数据pair的first 比较,这里就有区别,因此需要仿函数“统一化”

(1)set :

        使用仿函数时,当操作数类型为 K 类型,则直接识别使用 set 的仿函数 set_KeyOfT 中的 operator() 函数,返回 key(即返回一个键值)

template<class K>
class set
{
	// set 中的仿函数
	struct set_KeyOfT {
		const K& operator()(const K& key) {
			return key;
		}
	};
	
	// ....... 其他补充
private:
	RBTree<K, set_KeyOfT> _tree;   
};

(2)map

        当操作数类型为 pair<key, value> 键值对类型,则直接识别为 map 的仿函数 map_KeyOfT 中的 operator() 函数,返回 pair<key, value> 的 first (即也返回一个 键值)

template<class K, class V>
class map
{
	struct map_KeyOfT {
		const K& operator()(const pair<K, V>& kv) {
			return kv.first;
		}
	};
    // ....... 其他补充

private:
	RBTree<pair<const K, V>, map_KeyOfT> _tree;
};

在 红黑树类模板中添加 仿函数的类型:class KeyOfT

template<class T, class KeyOfT>
class RBTree
{}

仿函数的应用

KeyOfT kot; // 创建一个仿函数类对象
if (kot(cur->_data) < kot(data)) 

// data 可能是 key类型,可能是 pair<key, value> 类型
// 使用仿函数,调用operator() 自动识别 data 的类型,放回 键值 key 进行比较

(3)修改三:给 红黑树类模板 再加一个 类型 class K

        我们实现 find 和 insert 函数时,insert 函数参数类型是 T ,表示 data 可以是 key 类型,也可以是 pair< key, value > 类型

但是 find 函数参数可以是 T 类型吗??

不可以!,无论是 map or set,find 函数都是查找 键值 key,固定要用 key

        若 find 的参数是 T 类型,当 T == key 时,直接可以使用,当 T == pair< key, value > 时,就不能直接使用 T 来查找,就要使用 T.first,就使得两个类型造就两种使用逻辑

因此 我们可以额外传一个 键值(既然是固定要用的,就多传一个)

template<class K, class T, class KeyOfT>
class RBTree
{}


至此,我们红黑树类模板中 第一个参数 K 用于传键值key,第二个参数 T 为泛型用于 接收两种类型(兼容set 和 map 两种),第三个参数为 仿函数 


(4)修改四:插入函数 insert 的 返回值 改为 pair 类型

在STL中,无论是 map 容器还是 set 容器,其插入函数Insert()函数的返回值都是为一个pair<iterator,bool>;

1、若是插入成功则返回新插入节点的迭代器位置 与 true; (迭代器的实现将在下文中提到)

2、若是插入失败则返回与需要插入的数据相同的节点位置  false

例如 STL 库中的 map 的 insert 函数源码

pair<iterator,bool> insert (const value_type& val);

value_type 就是 pair< K, V>

pair 是一个类模板

我们的 插入函数 返回值修改为:

pair<iterator, bool> insert(const T& data)

4. 迭代器

因为需要将 红黑树封装进容器 map 和 set 中,容器会涉及各种操作,需要迭代器

因此我们先实现 红黑树的迭代器

因为 map 和 set 的底层是 红黑树,因此 他们的 迭代器就是将一个树节点指针封装成一个类

1.1 基础类框架和函数

基础函数:重载点操作符、重载箭头操作符、重载不等于操作符、构造函数

template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;

	Node* _p;

	Ref operator*() {
		return _p->_data;
	}
	Ptr operator->() {
		return &(_p->_data);
	}

	// 两个迭代器比较相等 就是直接比较 两个指针
	bool operator!=(const Self& x) {
		return _p != x._p;
	}

	
	RBTreeIterator(Node* p)
		:_p(p)
	{}
};

1.2 重载前置++ 和 前置--

关于前置++

在 二叉搜索树中 前置++,需要使迭代器指向 中序遍历的 下一个位置

因此,设计前置++ 就需要模拟中序遍历找到下一个节点


中序遍历顺序是:左孩子 --->  根  --->  右孩子

若当前节点为 节点 5,按照中序,下一个位置是 节点 7,即 根节点(从左孩子到根节点)

若当前节点为 节点 7,按照中序,下一个位置是 节点 9,即 右孩子(从根节点到右孩子)

若当前节点为 节点 9,按照中序,右孩子为 空,证明已经走完一个中序(左根右),应该回溯到 祖先节点,寻找 当前节点为 父亲的左孩子的关系,即 从节点9 一直到 节点 7,此时 节点7 是父亲节点 11 的左孩子(这样满足 从左孩子到根节点 ,即节点 9 的下一个位置就是 节点 11)

若当前节点为 节点 11,按照中序,下一个位置是 节点 12,即右子树的 最左节点(在右子树,循环向下找到右子树的最左节点)

综合上面几种情况,可以得出以下逻辑

1、右不为空,则 自己就是 根:右子树最左节点就是中序下一个,while() 循环 找 右子树的 最左节点

2、右为空,则 cur 和 parent 沿着到根节点的路径向上查找,直到 孩子 cur 是父亲 parent 的 left

此时 parent 就是中序的下一个节点

伪代码:

定义 cur = 当前位置

if(cur 的 右不为空)

        循环找右子树的最左节点

else if(cur 的 右为空) 

        定义 parent 

        while(parent 不为 空 且 parent 的左 不为 cur)

        循环结束,parent 就是 中序的写一个节点       

 

实际代码:

Self& operator++() {
	Node* cur = _p;
	if (cur->_right) {
		cur = cur->_right;
		while (cur->_left) {  // 注意这里是 cur->_left 我们目的是到最左节点,不是 空节点!
			cur = cur->_left;
		}
	}
	else {
		Node* parent = cur->_parent;
		while (parent && parent->_left != cur) {
			cur = parent;
			parent = parent->_parent;
		}
		cur = parent;
	}
	_p = cur;
	return *this;
}

关于前置--

重载前置-- 也 同理,就是倒着中序遍历(右 根 左)

注意:前置-- 的第一个节点可能为 end(),本文中我们将 end() 设置为 最后一个节点的下一个节点,即为 NULL

当对 end() == NULL 前置-- 时,可能导致空指针访问的错误,因此需要特殊处理

end() 的前一个即为 二叉树的 最后一个节点(中序遍历的最后一个:右子树的最右节点) 

Node* cur = _p;
// 如果 cur 为 nullptr 代表现在指向 end()
// 特殊处理
if (cur == nullptr) {
	cur = _root;
	while (cur && cur->_right) {
		cur = cur->_right;
	}
	//_p = cur;
}

前置-- 的代码:

// 减减 和 加加的 逻辑刚好相反
Self& operator--() {
	Node* cur = _p;
	// 如果 cur 为 nullptr 代表现在指向 end()
	// 特殊处理
	if (cur == nullptr) {
		cur = _root;
		while (cur && cur->_right) {
			cur = cur->_right;
		}
		//_p = cur;
	}


	// 下面的逻辑和 前置++ 差不多:镜像反转理解即可
	else if (cur->_left) {
		cur = cur->_left;
		while (cur->_right) {
			cur = cur->_right;
		}
	}
	else {
		Node* parent = cur->_parent;
		while (parent && parent->_right != cur) {
			cur = parent;
			parent = parent->_parent;
		}
		cur = parent;
	}

	_p = cur;
	return *this;
}

1.3 将 迭代器封装进 红黑树

这里定义了:begin() / end() (且为 iterator 和 const_iterator 两个版本)

此处:红黑树的 begin 是整棵二叉搜索树的 最左边的节点(即中序遍历的第一个节点),因此需要循环操作

template<class K, class T, class KeyOfT>
class RBTree
{
public:
	typedef RBTreeNode<T> Node;

	// 定义迭代器
	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, _root);
	}
	iterator end() {
		return iterator(nullptr, _root);
	}

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

	

private:

	Node* _root = nullptr;
};

就是上面这一部分封装了迭代器,其他的红黑树代码部分上一章都实现了,这里暂不赘述

1.4 ⭐ 迭代器类 完整代码

注释都有解释了,若不明白,可以评论区讨论或私信

// T :节点中的数据类型
// Ref:引用类型 T&  或  const T&
// Ptr:指针类型 T*  或  const T*
template<class T, class Ref , class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;    // 重定义节点类命名
	typedef RBTreeIterator<T, Ref, Ptr> Self;  // 对自己的迭代器类型重命名

	Node* _p;  // 迭代器中指向节点的指针


	Ref operator*() {
		return _p->_data;
	}
	Ptr operator->() {
		return &(_p->_data);
	}

	// 两个迭代器比较相等 就是直接比较 两个指针
	bool operator!=(const Self& x) {
		return _p != x._p;
	}
	bool operator==(const Self& x) const {
		return _p == x._p;
	}


	// 我写的函数 cur 有点冗余,其实代码可以更加精简
	Self& operator++() {
		Node* cur = _p;
		if (cur->_right) {
			cur = cur->_right;
			while (cur->_left) {  // 注意这里是 cur->_left 我们目的是到最左节点,不是 空节点!
				cur = cur->_left;
			}
		}
		else {
			Node* parent = cur->_parent;
			while (parent && parent->_left != cur) {
				cur = parent;
				parent = parent->_parent;
			}
			cur = parent;
		}
		_p = cur;
		return *this;
	}

	// 减减 和 加加的 逻辑刚好相反
	Self& operator--() {
		Node* cur = _p;
		// 如果 cur 为 nullptr 代表现在指向 end()
		// 特殊处理
		if (cur == nullptr) {
			cur = _root;
			while (cur && cur->_right) {
				cur = cur->_right;
			}
			//_p = cur;
		}


		// 下面的逻辑和 前置++ 差不多:镜像反转理解即可
		else if (cur->_left) {
			cur = cur->_left;
			while (cur->_right) {
				cur = cur->_right;
			}
		}
		else {
			Node* parent = cur->_parent;
			while (parent && parent->_right != cur) {
				cur = parent;
				parent = parent->_parent;
			}
			cur = parent;
		}
		
		_p = cur;
		return *this;
	}

	RBTreeIterator(Node* p, Node* root)
		:_p(p)
	{}
};

5. ⭐红黑树 完全体(含迭代器+适配 map 和 set 的泛型)
 

含有

红黑树节点类

迭代器类

红黑树类:拷贝构造、赋值运算符重载、析构、插入函数、4种旋转函数、查找 find、中序遍历、求树的高度、求树的节点个数、判断树是否平衡

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

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

template<class T>
struct RBTreeNode
{
	typedef RBTreeNode<T> Node;

	T _data;  // 泛型化思想:_data 可以是 Key 类型,也可以是 pair<Key, Value> 类型
	Node* _left;
	Node* _right;

	Node* _parent;
	Colour _col;

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

// T :节点中的数据类型
// Ref:引用类型 T&  或  const T&
// Ptr:指针类型 T*  或  const T*
template<class T, class Ref , class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;    // 重定义节点类命名
	typedef RBTreeIterator<T, Ref, Ptr> Self;  // 对自己的迭代器类型重命名

	Node* _p;  // 迭代器中指向节点的指针
	Node* _root;  


	Ref operator*() {
		return _p->_data;
	}
	Ptr operator->() {
		return &(_p->_data);
	}

	// 两个迭代器比较相等 就是直接比较 两个指针
	bool operator!=(const Self& x) {
		return _p != x._p;
	}
	bool operator==(const Self& x) const {
		return _p == x._p;
	}


	// 我写的函数 cur 有点冗余,其实代码可以更加精简
	Self& operator++() {
		Node* cur = _p;
		if (cur->_right) {
			cur = cur->_right;
			while (cur->_left) {  // 注意这里是 cur->_left 我们目的是到最左节点,不是 空节点!
				cur = cur->_left;
			}
		}
		else {
			Node* parent = cur->_parent;
			while (parent && parent->_left != cur) {
				cur = parent;
				parent = parent->_parent;
			}
			cur = parent;
		}
		_p = cur;
		return *this;
	}

	// 减减 和 加加的 逻辑刚好相反
	Self& operator--() {
		Node* cur = _p;
		// 如果 cur 为 nullptr 代表现在指向 end()
		// 特殊处理
		if (cur == nullptr) {
			cur = _root;
			while (cur && cur->_right) {
				cur = cur->_right;
			}
			//_p = cur;
		}


		// 下面的逻辑和 前置++ 差不多:镜像反转理解即可
		else if (cur->_left) {
			cur = cur->_left;
			while (cur->_right) {
				cur = cur->_right;
			}
		}
		else {
			Node* parent = cur->_parent;
			while (parent && parent->_right != cur) {
				cur = parent;
				parent = parent->_parent;
			}
			cur = parent;
		}
		
		_p = cur;
		return *this;
	}

	RBTreeIterator(Node* p, Node* root)
		:_p(p)
		,_root(root)
	{}
};

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


	RBTree() = default;

	~RBTree() {
		destory(_root);
		_root = nullptr;
	}

	// 拷贝构造
	RBTree(const RBTree<K, T, KeyOfT>& t) {
		_root = CopyTree(t._root);
	}

	// 赋值重载
	RBTree<K, T, KeyOfT>& operator=(const RBTree<K, T, KeyOfT>& t) {
		RBTree tmp(t);
		std::swap(_root, tmp._root);
		return *this;
	}

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





	// 查找
	iterator find(const K& key) const {
		Node* cur = _root;
		while (cur) {
			if ((cur->_data).first < key) {
				cur = cur->_right;
			}
			else if ((cur->_data).first > key) {
				cur = cur->_left;
			}
			else return iterator(cur, _root);
		}
		return end();
	}

	// 插入
	// 插入成功就是 true,迭代器指向新插入的节点
	// 插入失败就是 false,迭代器指向已存在的那个节点
	pair<iterator, bool> insert(const T& data) {
		if (_root == nullptr) {
			_root = new Node(data);
			_root->_col = BLACK; // 根节点一定是黑的
			return make_pair(iterator(_root, _root), true);
		}

		// 利用仿函数
		KeyOfT kot;


		Node* cur = _root;
		Node* parent = cur;
		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, _root), false);
		}


		// 在 cur 的位置插入该节点
		cur = new Node(data);
		cur->_col = RED;  // 新增节点给 红的
		Node* newNode = cur;  // 这里记录以下初始的 cur,避免下面各种操作改变 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;
			/*
						g
					p     u
			*/
			// 父亲是  爷爷 的左孩子
			if (parent == Grandfather->_left) {
				Node* Uncle = Grandfather->_right;

				// 叔叔是 红色:三人变色,cur指爷
				if (Uncle && Uncle->_col == RED) {
					parent->_col = BLACK;
					Uncle->_col = BLACK;
					Grandfather->_col = RED;

					cur = Grandfather;
					parent = cur->_parent;
				}
				// 叔叔是 黑色:旋转后变色
				else if (Uncle == nullptr || Uncle->_col == BLACK) {
					// 看 cur 的位置:决定单旋 or 双旋
					
					if (cur == parent->_left) { 
						/*  右单旋 + 变色
									 g
								 p       u
							 c
						*/
						rotateLL(Grandfather);
						// 爷变红,父变黑
						Grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else if (cur == parent->_right) {
						/*  双旋(先左旋后右旋) + 变色
									  g
								 p        u
									c
						*/
						rotateRR(parent);  // p 先 左旋
						rotateLL(Grandfather);  //  g 再右旋
						// 爷变红,cur 变黑
						Grandfather->_col = RED;
						cur->_col = BLACK;
					}
					break;
				}
			}

			// 父亲是  爷爷 的右孩子
			else if (parent == Grandfather->_right) {
				Node* Uncle = Grandfather->_left;

				// 叔叔是 红色:三人变色,cur指爷
				if (Uncle && Uncle->_col == RED) {
					parent->_col = BLACK;
					Uncle->_col = BLACK;
					Grandfather->_col = RED;

					cur = Grandfather;
					parent = cur->_parent;
				}
				// 叔叔是 黑色:旋转后变色
				else if (Uncle == nullptr || Uncle->_col == BLACK) {
					// 看 cur 的位置:决定单旋 or 双旋

					if (cur == parent->_right) {
						/*  左单旋 + 变色
									 g
								 u       p
							                  c
						*/
						rotateRR(Grandfather);
						// 爷变红,父变黑
						Grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else if (cur == parent->_left) {
						/*  双旋(先右旋后左旋) + 变色
							   	       g
								  u         p
							              c
						*/
						rotateLL(parent);  // p 先 右旋
						rotateRR(Grandfather);  //  g 再左旋
						// 爷变红,cur 变黑
						Grandfather->_col = RED;
						cur->_col = BLACK;
					}
					break;
				}
			}
		}

		// 修改一:根节点强制变色
		_root->_col = BLACK;

		return make_pair(iterator(newNode, _root), true);
	}

	// RR型:左单旋
	void rotateRR(Node* parent) {
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* parentParent = parent->_parent;

		// 1、subRL变成parent的右孩子
		parent->_right = subRL;
		// subRL 是有可能为 空的
		if (subRL) {
			subRL->_parent = parent;
		}

		// 2、parent变成subR的左孩子
		subR->_left = parent;
		parent->_parent = subR;


		// 3、subR变成当前子树的根
		// parentParent 是指 刚开始的 parent 的父亲:若 parent 是 _root 则 parentParent 为空,否则不为空,则该树就是子树
		if (parentParent) {
			if (parent == parentParent->_right)
				parentParent->_right = subR;
			else parentParent->_left = subR;

			subR->_parent = parentParent;
		}
		// 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空
		else {
			_root = subR;
			subR->_parent = nullptr;
		}
	}

	// LL型:右单旋
	void rotateLL(Node* parent) {
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* parentParent = parent->_parent;

		// 1、subLR变成parent的左孩子
		parent->_left = subLR;
		// subRL 是有可能为 空的
		if (subLR) {
			subLR->_parent = parent;
		}



		// 2、parent变成subL的右孩子
		subL->_right = parent;
		parent->_parent = subL;


		// 3、subL 变成当前子树的根
		// parentParent 是指 刚开始的 parent 的父亲:若 parent 是 _root 则 parentParent 为空,否则不为空,则该树就是子树
		if (parentParent) {
			if (parent == parentParent->_right)
				parentParent->_right = subL;
			else parentParent->_left = subL;

			subL->_parent = parentParent;
		}
		// 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空
		else {
			_root = subL;
			subL->_parent = nullptr;
		}
	}

	// LR 型:subL 先 左旋, parent 右旋
	void rotateLR(Node* parent) {
		rotateRR(parent->_left);
		rotateLL(parent);
	}

	// RL 型:subR 先 右旋, parent 左旋
	void rotateRL(Node* parent) {
		rotateLL(parent->_right);
		rotateRR(parent);
	}

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


	// 获取根节点
	Node* GetRoot() {
		return _root;
	}

	// 获取该树的高度
	int Height() {
		return _Height(_root);
	}

	// 获取节点个数
	int Size() {
		return _Size(_root);
	}
	 
	// 判断是否是 红黑树
	bool IsValidRBTree() {
		if (_root == nullptr) return false;
		else if (_root && _root->_col == RED) return false;

		// 遍历一条路,记录一条路上一共固定有多少个黑色节点
		int cnt = 0;
		Node* cur = _root;
		while (cur) {
			if (cur->_col == BLACK) cnt++;
			cur = cur->_left;
		}
		return _IsValidRBTree(_root, 0, cnt);
	}

private:

	//  判断是否是 红黑树
	bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
	{
		// 1、看根节点是否是 黑的
		// 2、看每条路径的 黑色节点数量是否相同
		// 3、检查是否有连续的红节点:遇到一个红节点就判断其父亲是否是 红的


		//走到null之后,判断 k 和 blackCount 是否相等:即一条路径上的 黑色节点数量是否为固定值
		if (pRoot == nullptr)
		{
			if (k != blackCount)
			{
				cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}
			return true;
		}

		// 统计黑色节点的个数
		if (pRoot->_col == BLACK)
			k++;

		// 检测当前节点与其双亲是否都为红色
		Node* pParent = pRoot->_parent;
		if (pParent && pParent->_col == RED && pRoot->_col == RED)
		{
			cout << "违反性质三:没有连在一起的红色节点" << endl;
			return false;
		}
		return _IsValidRBTree(pRoot->_left, k, blackCount) && _IsValidRBTree(pRoot->_right, k, blackCount);
	}


	int _Size(Node* pRoot) {
		if (pRoot == nullptr) return 0;
		//if (pRoot->_left == nullptr && pRoot->_right == nullptr) return 1;

		return 1 + _Size(pRoot->_left) + _Size(pRoot->_right);
	}
	int _Height(Node* pRoot) {
		if (pRoot == nullptr)
			return 0;

		return 1 + max(_Height(pRoot->_left), _Height(pRoot->_right));
	}


	// 销毁一棵树:后序遍历
	void destory(Node* root) {
		if (root == nullptr) {
			return;
		}

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

	// 拷贝一棵树
	Node* CopyTree(const Node* root) {
		if (root == nullptr) {
			return nullptr;
		}

		Node* newRoot = new Node(root->_kv);
		newRoot->_left = CopyTree(root->_left);
		newRoot->_right = CopyTree(root->_right);
		return newRoot;
	}

	void _InOrder(const Node* root) {
		if (root == nullptr) {
			return;
		}
		_InOrder(root->_left);
		cout << (root->_kv).first << " : " << (root->_kv).second << '\n';
		_InOrder(root->_right);
	}

	Node* _root = nullptr;
};



6. ⭐封装 set 完整代码

红黑树的代码 前面已经实现,在 set 中直接调用一个红黑树即可(记得在 set.h 要放入 红黑树的头文件 )

template<class K>
class set
{
	struct set_KeyOfT {
		const K& operator()(const K& key) {
			return key;
		}
	};
public:
	typedef typename RBTree<K, const K, set_KeyOfT>::iterator iterator;
	typedef typename RBTree<K, const K, set_KeyOfT>::const_iterator const_iterator;

	// 直接调用红黑树的 插入函数
	pair<iterator, bool> insert(const K& key) {
		return _tree.insert(key);
	}

	// 迭代器:都是直接调用底层红黑树的 函数
	iterator begin() {
		return _tree.begin();
	}
	iterator end() {
		return _tree.end();
	}
	const_iterator begin() const {
		return _tree.begin();
	}
	const_iterator end() const {
		return _tree.end();
	}

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

private:
	RBTree<K, const K, set_KeyOfT> _tree;
};

7. ⭐封装 map 完整代码

红黑树的代码 前面已经实现,在 map 中直接调用一个红黑树即可(记得在 map .h 要放入 红黑树的头文件 )

template<class K, class V>
class map
{
	struct map_KeyOfT {
		const K& operator()(const pair<K, V>& kv) {
			return kv.first;
		}
	};
public:
	typedef typename RBTree<K, pair<const K, V>, map_KeyOfT>::iterator iterator;
	typedef typename RBTree<K, pair<const K, V>, map_KeyOfT>::const_iterator const_iterator;

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

	// 迭代器
	iterator begin() {
		return _tree.begin();
	}
	iterator end() {
		return _tree.end();
	}
	const_iterator begin() const {
		return _tree.begin();
	}
	const_iterator end() const {
		return _tree.end();
	}

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

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

private:
	RBTree<K, pair<const K, V>, map_KeyOfT> _tree;
};

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

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

相关文章

ip地址冲突的原因及其解决方法是什么

在当今的信息化时代&#xff0c;网络已成为我们生活和工作中不可或缺的一部分。然而&#xff0c;随着网络设备数量的不断增加&#xff0c;网络管理中的问题也日益凸显&#xff0c;其中IP地址冲突便是常见问题之一。IP地址冲突不仅会导致网络通信不稳定&#xff0c;甚至可能使设…

详解栈和队列

目录&#xff1a; 1.栈 2.队列 一、 栈&#xff08;Stack&#xff09; 1.1 概念&#xff1a; 栈是一种特殊的线性表&#xff0c;只允许在固定的一端进行插入和删除元素的操作。进行插入元素的一端叫做栈顶&#xff0c;另一端叫做栈底。从数据结构的角度出发&#xff0c;栈中…

k8s-deployment控制器

k8s-deployment控制器 1、yaml文件标签学习-指定pod在哪台节点上创建 强制指定pod在指定节点上创建&#xff1a; --- apiVersion: apps/v1 kind: Deployment metadata:name: my-deployment spec:replicas: 3selector:matchLabels:app: web1template:metadata:labels:app: web1…

流媒体服务器二 3学习 librtmp 库的配置使用

librtmp 库是个啥&#xff1f; librtmp是一个开源的基于C语言的库&#xff0c;提供了一个连接RTMP服务器&#xff0c;发送和接收RTMP流的API。 它可以用来开发流媒体播放器&#xff0c;网络直播等应用。它的主要特点是快速、稳定和低延迟。 librtmp支持RTMP&#xff0c;RTMPS…

超越 RAG 基础:AI 应用的高级策略

作者&#xff1a;来自 Elastic Platform Team 我们最近与 Cohere 举办的线上活动深入探讨了检索增强生成 (Retrieval Augmented Genereation - RAG) 的世界&#xff0c;重点讨论了在概念验证阶段之后构建 RAG 应用程序的关键注意事项。我们的演讲者是 Elastic 的首席解决方案架…

使用 Python和 FFmpeg 批量截图视频到各自文件夹中

在这篇博客中&#xff0c;我们将创建一个简单的图形用户界面 (GUI) 工具&#xff0c;利用 wxPython 和 FFmpeg 来从视频文件中批量生成截图。这个工具能够让用户选择一个文件夹&#xff0c;遍历其中的所有视频文件&#xff0c;按照视频长度将其分为四等分&#xff0c;然后为每个…

鸿蒙HarmonyOS之使用ArkTs语言实现自定义Tab菜单栏分页主页面UI

一、效果 显示为3个Tab菜单栏&#xff0c;中间可以滑动 二、实现步骤 1、Index.ets 示例代码中用到的颜色、文字、图片等资源可以自行替换 import { Tab_1 } from ./Tab_1; import { Tab_2 } from ./Tab_2; import { Tab_3 } from ./Tab_3;//主页面 Entry Component stru…

Excel数字中间指定位置插入符号——以120120加*为例

设置单元格格式——自定义 更多阅读Excel数字中间指定位置插入符号_哔哩哔哩_bilibili

B码对时案例分享,基于RK3568J+Logos-2,让电力设备轻松实现“高精度授时”!

本文主要介绍瑞芯微RK3568J紫光同创Logos-2的B码对时案例&#xff0c;开发环境如下&#xff1a; Windows开发环境&#xff1a;Windows 7 64bit、Windows 10 64bit Pango Design Suite(PDS)&#xff1a;PDS_2022.2-SP3 IRIG-B码对时典型应用 IRIG-B码对时可应用于继电保护装…

台球厅自动控制无人台球之智能开关-SAAS本地化及未来之窗行业应用跨平台架构

一、无人台球厅优点 1. 降低人力成本&#xff1a;无需雇佣大量员工&#xff0c;如收银员、服务员等&#xff0c;节省了工资、福利和管理成本。 2. 24 小时营业&#xff1a;不受人工营业时间限制&#xff0c;可以随时满足顾客的需求&#xff0c;增加营业时长和收入。 3. 便捷高…

不良图片检测

OpenNSFW模型简介 OpenNSFW是一个由 Yahoo 研究院开源的深度学习模型&#xff0c;用于识别和区分网络上的正常内容与不适宜内容&#xff08;Not Safe For Work&#xff09;。 项目地址&#xff1a;https://github.com/yahoo/open_nsfw OpenNSFW主要基于Caffe框架实现&#x…

邮票孔拼版制作方法

邮票孔拼版制作方法 拼版后的局部图:(中间用连接桥的方式&#xff0c;此方式能最少程度上减少残留) 2&#xff09;拼版后的效果图 3&#xff09;邮票孔拼版规则: 拼板与板间距1.2MM或者1.6MM 等邮票孔&#xff1a;8个0.55MM的孔,孔间距0.2MM加两排&#xff0c;邮票孔伸到…

KubeSphere核心实战_kubesphere部署redis01_为redis指定配置文件_指定存储卷_配置服务---分布式云原生部署架构搭建047

然后我们再来,部署一下redis,可以看到,首先去容器官网去找到对应的redis的镜像然后 可以看到镜像中都有说的,如何启动,以及 --appendonly yes 是指定持久化.然后 /data表示数据存储的位置. 可以看到数据存储位置 然后还有配置文件的位置. 可以看到,我们首先去创建配置文件,然后…

【专题】2024全数驱动 致胜未来-数字化敏捷银行白皮书报告合集PDF分享(附原数据表)

原文链接&#xff1a; https://tecdat.cn/?p37404 政策明确发展使命&#xff0c;新时代商业银行应坚持党建引领&#xff0c;秉持高质量发展理念。数字经济已成大势&#xff0c;商业银行需构建数字基础设施能力&#xff0c;强化顶层战略规划。当前商业银行数字化发展面临诸多挑…

改变自己·心情治愈

今早起床了 看镜子里的我 忽然发现我发型睡得有点kuso 一点点改变&#xff0c;有很大的差别 你我的力量也能改变世界 最近比较烦 最近情绪很down 每天看新闻都会很想大声尖叫 但脏话没用 大家只会嫌凶 我改变自己发现大有不同 新一代的朋友我们好好地加油 大家一起大…

QT:QWebEngineView基本使用

QWebEngineView介绍 添加库 QT core gui webenginewidgets出现问题 解决 提升类 Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//ui->web_widget->load(QUrl("http://www.baidu.com"));ui->…

Rimworld边缘世界使用服务器开服联机教程

订阅联机模组RimWorld Together (MULTIPLAYER)&#xff08;百度莱卡云&#xff09; 打开创意工坊搜索RimWorld Together (MULTIPLAYER) 订阅联机模组与它的依赖模组 打开游戏➡点击Mod配置 双击左边灰色的模组&#xff0c;到右边启用 还需要排一下顺序&#xff0c;顺序不对会…

iPhone变身万能钥匙,iOS 18.1让你的手机解锁一切

Apple 近日发布新闻稿宣布&#xff0c;在即将推出的 iOS 18.1 更新中&#xff0c;将开放全新的 NFC&#xff08;近场通信&#xff09;和 SE&#xff08;安全元件&#xff09;API 给第三方开发者使用。这一举措将使得开发者能够开发出更加多样化和实用的应用程序&#xff0c;如车…

Redis清空缓存

Windows环境下使用命令行进行redis缓存清理 1、redis安装目录下输入cmd 2、redis-cli -p 端口号 3、flushdb 清除当前数据库缓存 4、flushall 清除整个redis所有缓存

Dubbo源码深度解析(七)

接上一篇博客《Dubbo源码深度解析(六)》&#xff0c;上篇博客主要从服务消费方开始讲起&#xff0c;主要讲&#xff1a;如果类中的属性或者方法&#xff0c;如果被DubboReference注解所修饰&#xff0c;Dubbo是怎么处理的&#xff0c;处理逻辑类似Spring框架提供的Autowired注解…