C++ - set 和 map 的实现(下篇)- set 和 map 的迭代器实现

news2024/11/23 16:31:26

前言

在上篇当中我们为了 让红黑树适用于 set 和 map 对红黑树进行了修改,还是实现了红黑树的迭代器,因为 set 和 map 的底层都是使用 红黑树,那么,set 和 map 的迭代器也就实现了。具体可以看本博客的上篇:C++ - map 和 set 的模拟实现上篇 - 红黑树当中的仿函数 - 红黑树的迭代器实现-CSDN博客

set 和 map 实现(下)

set 的 const 迭代器

 要实现 set 和 map 的const  迭代器,就要先实现 红黑树当中的const 迭代器:
修改如下所示:
 

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

	__TreeIterator(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--();

	Self& operator++()
	{
		// 此时就是最简单的情况
		// 直接找出该结点的右子树的最小结点
		if (_node->_right)
		{
			// 右树的最左节点(最小节点)
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}

			_node = subLeft;
		}
		else  //_node->_left
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			// 找孩子是父亲左的那个祖先节点,就是下一个要访问的节点
			while (parent)
			{
				if (cur == parent->_left)
				{
					break;   // 说明已经找到,parent此时就是下一次需要迭代的结点
				}
				// 如果程序走到这里,该结点和 父亲结点的左右子树都遍历完了
				// 就要往上迭代
				// 直到找到 父亲 的右子树没有找完的情况
				else  //cur == parent->_right
				{
					cur = cur->_parent;
					parent = parent->_parent;
				}
			}

			_node = parent;
		}

		return *this;
	}
};

// 红黑树节点的定义
template<class K, class T, class KeyOfT>
struct RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef __TreeIterator<T , T* , T&> iterator;
	typedef __TreeIterator<T , const T*, const T&> const_iterator;
	// const_iterator

	iterator begin()
	{
		Node* leftMin = _root;
		// 加上 subleft 这个条件是为了防止 这棵树是空
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}

		return iterator(leftMin);
	}

	iterator end()
	{
		// end()不用像上述一样 找最大值
		// 通过迭代器当中的 operator++()函数我们知道,中序最后都是遍历到 nullptr 的
		// 这个 nullptr就是 根结点的父亲指针指向的 nullptr
		return iterator(nullptr);
	}

	// const 迭代器
	const_iterator begin() const
	{
		Node* leftMin = _root;
		// 加上 subleft 这个条件是为了防止 这棵树是空
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}

		return iteconst_iteratorrator(leftMin);
	}

	const_iterator end() const
	{
		// end()不用像上述一样 找最大值
		// 通过迭代器当中的 operator++()函数我们知道,中序最后都是遍历到 nullptr 的
		// 这个 nullptr就是 根结点的父亲指针指向的 nullptr
		return iterconst_iteratorator(nullptr);
	}
··············
············
············

private:
	Node* _root = nullptr;
};

上述就是 实现了 红黑树的 迭代器 和 const 迭代器。

set 的 const 迭代器:

// 如果 set 当中可以要修改的话应该像如下一样写
		//typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;

		// 下一个是 只对外开放 const 迭代器
		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();
		}

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

但是如果单独 像上述一样在 set 当中这样实现 const  迭代器的话,是会出现问题的:编译报错

error C2440: “return”: 无法从“__TreeIterator<T,T *,T &>”转换为“__TreeIterator<T,const T *,const T &>”

这是因为,上述 const_iterator  迭代器,的 begin()和 end()函数,用的是一个 普通对象 来使用红黑树当中的迭代器,就不行。

如果用普通对象来调用的话,在红黑树那一层,调用的就是 普通的迭代器,然后在 set 当中的 begin()和 end()返回的就是 以 普通的红黑树迭代器,但是 begin()和 end()返回类似是一个 const_iterator 类型,这里就发生了类型的转换。

在红黑树当中 实现的 const-iterator 和 普通的迭代器使用的是一个类模板, 是传入不同的参数,实例化出的不同类型。

库当中的实现就比较聪明:
 

 如上是 库当中的set 的 const 迭代器 的begin 和 end 函数,发现,两个函数的返回值是 iterator 而不是 const_iterator。

然后我们修改代码 ,把 返回值为 const_iterator 的begin()函数和 end()函数删除掉,只留上面的两个函数,发现编译就通过了:
 

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

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

而且,也可以实现外部不能修改的 const 迭代器的功能。

 加上const 修饰 this指针之后,这个 _t 对象就是 const 的对象了,那么调用的 begin()和 end()函数就是 const 版本的 函数。

虽然上述 begin 和 end()两个函数的 返回值 只是普通的 迭代器,但是在 set 当中是把 const 迭代器 typedef 出 iterator 和 const_iterator 两个迭代器名。然后不管在外面创建的是  iterator 还是 const_iterator 的迭代器,创建的都会是 coust_iterator:

 我们这里只提供 const 版本的迭代器就可以适用  set  的使用场景了,因为set 当中只存储了 key ,这个 key 是不允许修改的。而且,const 对象可以使用 const 迭代器,这是权限的等价;普通对象也可以调用 const 迭代器,这是 权限的缩小;权限只能相等或者是 缩小,不能放大。

 map 当中的迭代器

map 的 当中的修改规则是,取出的迭代器可以 修改 value 但是不能修改 key。那么我们该如何实现上述 效果呢?

如果我们使用 和在 set 当中的 一样的方式,只开放 const 迭代器的方式,这样的话,key 和 value 都不能修改了

所以,我们肯定不能使用 set 当中的方式来实现。

其实在 上篇当中我们已经说明了:

C++ - map 和 set 的模拟实现上篇 - 红黑树当中的仿函数 - 红黑树的迭代器实现-CSDN博客

 map 的传给 RBTree 的value 值不是一个简单的 value值,而且是一个 包含键值对的 pair 类。

而在 这个 pair 当中,我能发现 key 是const 修饰的,而 T(value)是普通类型。所以才实现  key 不能修改,value 可以修改。 

更具体的细节可以去看 上篇当中的 对于 set 和 map 的实现介绍。

 在 map 的成员当中会有一个 红黑树,这个红黑树的模版参数如下所示:

 库当中的实现是非常巧妙的,红黑树当中每一个结点当中的 pair 对象是可以进行修改的,但是pair 当中的  key 是const 修饰的,所以 key 是不能修改的,但是 可以利用可以修改的 pair 对 其中的 value 进行修改。

两者的 operator--()函数

 operator--()函数,和 operator++()函数是反过来来的,也就是说,如果一直--,那么输出结果是 跟中序结果相反的。遍历过程也就是 右子树 -> 根 -> 左子树 这样来遍历的。

operator--()和 operator++()两者在遍历和实现过程当中是完全对称的

 具体细节可以对照着 上篇当中对 operator++()函数介绍来理解:
C++ - map 和 set 的模拟实现上篇 - 红黑树当中的仿函数 - 红黑树的迭代器实现-CSDN博客

代码实现:
 

	Self& operator--()
	{
		// 如果此时 右子树还有结点,就去找右子树当中找到最左边的结点
		if (_node->_left)
		{
			Node* subleft = _node;
			while (subleft)
			{
				subleft = subleft->_left;
			}
			_node = subleft;
		}
		// 如果 右边不为空
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}

		return *this;
	}

库当中的迭代器增加了一个 头结点:

 让头结点 header 的 左指针指向 最左结点(最小结点),让右指针直系那个 最右结点(最右结点)。这样做可以快速的找到树当中的最大和最小的结点。

所以,咋库当中,红黑树的 begin()是这样写的:

 返回的是 leftmost 函数的返回值。这样做,begin()相对于我们实现的begin()要相对高效一点,但是也高效不了多少,因为 红黑树是控制 了高度 的。

而 end()就是 hearder,因为end()是最右结点的下一个,本来是空,这里就是 hearder了

 header 还让 其 _parent 指针指向 根,让 根的 _parent 指向 header。

那么也就是说,it 指向 15 结点,it++ 之后,就应该指向 header 了。 因为15 的右子树为空,就要从父亲开始往上寻找某一个祖父,如果某一个祖父的右子树不为空,就访问这个祖父,如果没有就一直访问,直到访问到 根结点。那么此时就有头结点,就会访问到 header了。

 最后就会是这种情况:

 当然加了哨兵位之后,就有一些特殊情况,如下述举例:
 

 上述如果按照我们之前找到的 cur == parent->_right 的话,就会死循环。

map 的 operator[]()函数 

 要实现这个函数,就要复用 insert()函数,但是这个函数,我们在红黑树当中实现的时候,返回值是 bool 类型,所以我们需要把 insert()函数,返回一个 pair<iterator,bool> 对象,这个对象当中的 迭代器就是 insert()当前插入的结点的迭代器。修改如下:

// RBTree.h
pair<iterator, bool> Insert(const T& data)
	{
		// 搜索二叉树的插入逻辑
		// 
		// 如果当前树为空,直接用头指针只想新结点
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root),true);
		}

		// 不为空接着走
		Node* parent = nullptr;  // 用于首次插入时候指针的迭代
		Node* cur = _root;

		KeyOfT kot;
		while (cur)
		{
			// 如果当前新插入的 key 值比 当前遍历的结点 key 值大
			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;
		cur->_col = RED;

		// 再次判断大小,判断 cur应该插入到 parent 的那一边
		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		// 链接 新插入结点 cur 的_parent 指针
		cur->_parent = parent;

		// 红黑树调整高度(平衡高度)的逻辑
		while (parent && parent->_col == RED)
		{
			// parent 为 红,parent->_parent 一定不为空
			Node* grandfather = parent->_parent;
			// 如果父亲是在 祖父的左
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				// u存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else // u不存在 或 存在且为黑
				{
					if (cur == parent->_left)
					{
						//     g
						//   p
						// c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//     g
						//   p
						//		c
						RotateL(parent);
						RotateR(grandfather);

						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;// 不需要再往上更新
				}
			}
			else // parent == grandfather->_right
			{
				Node* uncle = grandfather->_left;
				// u存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else// 不存在 或者 存在且为黑色
				{
					if (cur == parent->_right)
					{
						// g
						//	  p
						//       c
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						// g
						//	  p
						// c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		// 不管上述如何修改,红黑树的根结点永远是黑的
		// 所以我们这里既直接硬性处理
		_root->_col = BLACK;

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

那么,在set 和map 当中都是复用了 红黑树当中的 insert()函数,所以,此时我们要把 set 和 map 当中的 insert()函数进行修改:
 

// MySet.h
    		// 这里的 iterator 是 const_iterator
		pair<iterator, bool> insert(const K& key)
		{
			// 因为底层是哟个红黑树实现的,直接套用红黑树的 插入
			// pair<iterator, bool> 其中的 iterator 是 普通的 iterator
			return _t.Insert(key);
		}



// mymap.h
		pair<iterator, bool> insert(const pair<const K, V>& key)
		{
			// 因为底层是哟个红黑树实现的,直接套用红黑树的 插入
 			return _t.Insert(key);
		}

但是此时有报错:
 

 error C2440: “return”: 无法从“std::pair<__TreeIterator<T,T *,T &>,bool>”转换为“std::pair<__TreeIterator<T,const T *,const T &>,bool>”

这是因为 ,_t 是普通对象,所以调用的是一个普通的 迭代器,但是在 insert()函数当中的返回值 pair<iterator,bool> 当中的 iterator 是 set 当中对 const_iterator 的 typedef:

 所以,此处就会发生 从 普通迭代器到 const 迭代器的转换。就会报错。

解决方法:
库当中是这样实现的:

 使用 typename 来在 编译之后再去寻找这个迭代器。

 而且,发现库当中对于 返回值 pair<iterator,bool> 当中的迭代器构造,是直接使用 first 成员进行构造的。

那么,我们就按照库当中的实现来实现:

 因为 make_pair ()是自己推导类型,那么推导出来的 iterator 还是 set 当中的 typydef 的 const 迭代器,所以,在库当中就 用一个变量来接收 insert()函数返回的 pair 对象,而我们看到 这个 ret 的类型pair 的模版参数非常的复杂,请看下述分析:
 

 具体请看上图当中的分析,但是在上述的书写之后,还是编译报错:

 我们先来看 此时的 pair 的构造函数:

 对于,pair 的构造函数,传入的第一个参数是 iterator x ,这个x 的类型是 iterator ,就是 RBTree当中的 iterator;但是 pair 当中 first成员的类型是 pair 类模板的第一个参数,是 iterator类型,但是这个 iterator 不是 RBTree 当中的 iterator,是 set 当中 typedef 出来的,其实这个 iterator是 const_iterator,所以在 初始化列表当中 first(x),就会发生类型的转换 从 RBTree 当中的 iterator 转换为  const_iterator,但是,我们没有写拷贝构造函数,所以这里是浅拷贝,只是一个 iterator 的浅拷贝,所以我们需要实现 拷贝构造函数来实现 深拷贝。

但是按理来说,一般实现的迭代器是不用实现拷贝构造函数,因为其中就只有一个 指针,我们对这个指针和一些功能函数进行了封装,我们不写,编译器自己生成的拷贝构造进行的浅拷贝也够迭代器的拷贝了。

如下图当中,说库当中实现的拷贝构造函数:

 看上去他也是实现了 浅拷贝,直接把 iterator 当中的 node 指针赋值了。

但是仔细看这个拷贝构造函数的参数,是 iterator 类型的,如果是拷贝构造函数的话,此处传入的对象应该是 迭代器的类型,也就是说 上图当中的 self 这个 typedef 出来的类型,但是发现,却给出的是 iterator这个 typedef 出来的类型,我们仔细观察 iterator 这个模版类型参数,感觉还有点奇怪。

其实,普通迭代器可以构造 const 迭代器是因为,const 迭代器实现了拷贝构造函数,这个构造函数可以传入参数为 普通迭代器类型,然后构造一个 const 的迭代器。

这里的self 就是这个迭代器,iterator不是迭代器。我们观察iterator 这个模版类型参数,发现和我们在写 iterator 的模版参数是一样的:
 

 所以说,这个 iterator 就是一个 普通的迭代器的模版参数,这个iterator 就是一个 普通迭代器,而self 是一个 可以 融合 普通迭代器和 const 迭代器的模版参数。

也就是说,这里不管在 拷贝构造函数当中传入的是一个 const 的迭代器还是一个 普通的迭代器,都会被 iterator 当中的模版参数修改为普通迭代器。

那么,此时,如果 这个类实例化出来是一个 普通迭代器,那么这就只是以 普通迭代器 的浅拷贝的拷贝构造函数;如果,这个类实例化出来是一个 const 迭代器,那么这个函数就是 const 迭代器的 构造函数。这个构造函数就支持 一个 普通迭代器 构造出一个 const 迭代器。

那么,此时,如果这个类模板示例化出来是一个 const 的迭代器,那么,在这个 const 迭代器当中就有两个构造函数,那么就相当于 const 迭代器当中没有拷贝构造吗?我们之前也说过,拷贝构造函数,是C++ 6 大默认函数之一,我们不写编译器会自动生成一个, 这里这个 又是 拷贝构造函数 又是 构造函数的函数,只是一个模版,是契合 普通迭代器 和 const 迭代器的模版,这个函数在 这 两种迭代器当中实现了不同的功能;但是,在普通迭代器当中实现的浅拷贝功能其实是多余的,因为拷贝构造函数我们不写,编译器会帮我们自己生成一个浅拷贝的拷贝构造函数。

 整体,红黑树, map ,set 的三个文件完整代码:
 

// MyMap.h

#pragma once
#include"MyRBTree.h"

namespace Mynamespace
{
	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, K>& kv)
			{
				return kv.first;
			}
		};

	public:

		// 如果 set 当中可以要修改的话应该像如下一样写
		//typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;

		// 下一个是 只对外开放 const 迭代器
		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();
		}

		V& operator[](const K& key)
		{
			// 这里的 V()是 V 的匿名对象,如果是set 的就是 key 的构造函数,如果是 map 的就是 其中pair 的构造函数
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}

		pair<iterator, bool> insert(const pair<const K, V>& key)
		{
			// 因为底层是哟个红黑树实现的,直接套用红黑树的 插入
 			return _t.Insert(key);
		}

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

// MySet.h

#pragma once
#include"MyRBTree.h"

namespace Mynamespace
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			// 和 map 对照的标准写法
			//const k& operator()(const pair<K, K>& kv)
			//{
			//	return p.first;
			//}
			// 其实可以直接这样写
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	public:
		// 如果 set 当中可以要修改的话应该像如下一样写
		//typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;

		// 下一个是 只对外开放 const 迭代器
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

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

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

		// 这里的 iterator 是 const_iterator
		pair<iterator, bool> insert(const K& key)
		{
			// 因为底层是哟个红黑树实现的,直接套用红黑树的 插入
			// pair<iterator, bool> 其中的 iterator 是 普通的 iterator
			pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
			return pair<iterator, bool>(ret);
		}

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

// MyBRTree.h

#pragma once
#include<iostream>
using namespace std;

// 节点的颜色
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 Ptr, class Ref>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T , Ptr, Ref> Self;
	typedef __TreeIterator<T, T*, T&> iterator;
	Node* _node;

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

	__TreeIterator(const iterator& it)
	{
		_node = it._node;
	}

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

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

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

	Self& operator--()
	{
		// 如果此时 右子树还有结点,就去找右子树当中找到最左边的结点
		if (_node->_left)
		{
			Node* subleft = _node;
			while (subleft)
			{
				subleft = subleft->_left;
			}
			_node = subleft;
		}
		// 如果 右边不为空
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}

		return *this;
	}

	Self& operator++()
	{
		// 此时就是最简单的情况
		// 直接找出该结点的右子树的最小结点
		if (_node->_right)
		{
			// 右树的最左节点(最小节点)
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}

			_node = subLeft;
		}
		else  //_node->_left
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			// 找孩子是父亲左的那个祖先节点,就是下一个要访问的节点
			// cur == parent->_left说明已经找到,parent此时就是下一次需要迭代的结点
			while (parent && cur == parent->_right)
			{

					cur = cur->_parent;
					parent = parent->_parent;

			}

			_node = parent;
		}

		return *this;
	}
};

// set->RBTree<K, K, SetKeyOfT> _t;
// map->RBTree<K, pair<K, V>, MapKeyOfT> _t;


// 红黑树节点的定义
template<class K, class T, class KeyOfT>
struct RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef __TreeIterator<T , T* , T&> iterator;
	typedef __TreeIterator<T , const T*, const T&> const_iterator; 
	// const_iterator

	iterator begin()
	{
		Node* leftMin = _root;
		// 加上 subleft 这个条件是为了防止 这棵树是空
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}

		return iterator(leftMin);
	}

	iterator end()
	{
		// end()不用像上述一样 找最大值
		// 通过迭代器当中的 operator++()函数我们知道,中序最后都是遍历到 nullptr 的
		// 这个 nullptr就是 根结点的父亲指针指向的 nullptr
		return iterator(nullptr);
	}

	// const 迭代器
	const_iterator begin() const
	{
		Node* leftMin = _root;
		// 加上 subleft 这个条件是为了防止 这棵树是空
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}

		return const_iterator(leftMin);
	}

	const_iterator end() const
	{
		// end()不用像上述一样 找最大值
		// 通过迭代器当中的 operator++()函数我们知道,中序最后都是遍历到 nullptr 的
		// 这个 nullptr就是 根结点的父亲指针指向的 nullptr
		return const_iterator(nullptr);
	}

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

		return nullptr;
	}

	pair<iterator, bool> Insert(const T& data)
	{
		// 搜索二叉树的插入逻辑
		// 
		// 如果当前树为空,直接用头指针只想新结点
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root),true);
		}

		// 不为空接着走
		Node* parent = nullptr;  // 用于首次插入时候指针的迭代
		Node* cur = _root;

		KeyOfT kot;
		while (cur)
		{
			// 如果当前新插入的 key 值比 当前遍历的结点 key 值大
			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;
		cur->_col = RED;

		// 再次判断大小,判断 cur应该插入到 parent 的那一边
		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		// 链接 新插入结点 cur 的_parent 指针
		cur->_parent = parent;

		// 红黑树调整高度(平衡高度)的逻辑
		while (parent && parent->_col == RED)
		{
			// parent 为 红,parent->_parent 一定不为空
			Node* grandfather = parent->_parent;
			// 如果父亲是在 祖父的左
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				// u存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else // u不存在 或 存在且为黑
				{
					if (cur == parent->_left)
					{
						//     g
						//   p
						// c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//     g
						//   p
						//		c
						RotateL(parent);
						RotateR(grandfather);

						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;// 不需要再往上更新
				}
			}
			else // parent == grandfather->_right
			{
				Node* uncle = grandfather->_left;
				// u存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else// 不存在 或者 存在且为黑色
				{
					if (cur == parent->_right)
					{
						// g
						//	  p
						//       c
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						// g
						//	  p
						// c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		// 不管上述如何修改,红黑树的根结点永远是黑的
		// 所以我们这里既直接硬性处理
		_root->_col = BLACK;

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

	void RotateL(Node* parent)
	{
		Node* cur = parent->_right; // 存储 parent 的右孩子
		Node* curleft = cur->_left; // 存储 cur 的左孩子

		parent->_right = curleft;
		if (curleft)                // 判断 cur 的左孩子是否为空
		{
			curleft->_parent = parent;  // 不为空就 修改 cur 的左孩子的_parent 指针
		}

		cur->_left = parent;
		// 留存一份 根结点指针
		Node* ppnode = parent->_parent;

		parent->_parent = cur;

		// 如果parent 是根结点
		if (parent == _root)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;

			}

			cur->_parent = ppnode;
		}
	}

	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curRight = cur->_right;

		parent->_left = curRight;
		if (curRight)
		{
			curRight->_parent = parent;
		}

		cur->_right = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = cur;

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

			cur->_parent = ppnode;
		}
	}

	bool CheckColor(Node* root, int blacknum, int benchamark)
	{
		// 当走到叶子结点的 null 指针处,也就是 NIL结点处
		if (root == nullptr)
		{
			// 如果计算出的路径黑色结点长度 和 外部计算的不一样
			// 说明不是红黑树
			if (blacknum != benchamark)
			{
				cout << "路径黑色结点个数不一样" << endl;
				return false;
			}
			return true;
		}

		// 用于递归计算 路径的黑色结点个数
		if (root->_color == BLACK)
			blacknum++;

		// 如果当前结点为 红色,且当前结点的父亲也是红色,就不是红黑树
		if (root->_parent && root->_parent->_color == RED && root->_color == RED)
		{
			cout << "有连续红色" << endl;
			return false;
		}

		// 左右子树递归
		return CheckColor(root->_left, blacknum, benchamark)
			&& CheckColor(root->_right, blacknum, benchamark);
	}

	// 外部调用接口
	bool isBalance()
	{
		return isBalance(_root);
	}

	// 内部封装函数
	bool isBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		// 如果整棵树的 根结点不是 黑色的就不是红黑树
		if (root->_color != BLACK)
		{
			cout << "根结点不是黑色" << endl;
			return false;
		}

		// 基准值
		// 在递归外部计算出左路第一条路径的 黑色结点值
		int benchmark = 0;
		Node* cur = root;
		while (cur)
		{
			if (cur->_color == BLACK)
				benchmark++;
			cur = cur->_left;
		}

		return CheckColor(root, 0, benchmark);
	}
private:
	Node* _root = nullptr;
};

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

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

相关文章

Windows提权辅助工具WES-NG使用

Windows提权辅助工具WES-NG使用 1.下载安装2.开始使用1.下载安装 WES-NG 执行此命令更新CVE数据库: python wes.py --update2.开始使用 首先,生成系统systeminfo信息,并重定向一个txt文件中: systeminfo > systeminfo.txt查找提权可以利用的CVE: python wes.py s…

还不知道数据类岗位的相关技能和职责吗?涤生大数据告诉你(二)

续接上文&#xff1a;还不知道数据类岗位的相关技能和职责吗&#xff1f;涤生大数据告诉你&#xff08;一&#xff09; 1.数据治理工程师 工作职责 数据治理工程师的工作职责主要包括以下几个方面&#xff1a; 1. 数据管理策略制定&#xff1a;制定和实施数据管理策略&#…

【HCIE】VXLAN

VXLAN简介 介绍VXLAN的定义、目的和收益。 定义 RFC7348定义了VLAN扩展方案VXLAN&#xff08;Virtual eXtensible Local Area Network&#xff09;。VXLAN采用MAC in UDP&#xff08;User Datagram Protocol&#xff09;封装方式&#xff0c;是NVO3&#xff08;Network Virt…

YashanDB向量化执行引擎如何给海量数据分析提速

作者介绍&#xff1a;李伟超&#xff0c;数据库系统架构师&#xff0c;YashanDB架设技术开发负责人&#xff0c;10年以上数据库内核技术开发经验。 *全文4510个字&#xff0c;阅读时长约11分钟。 背景 海量数据OLAP场景&#xff0c;通常具有数据规模大、查询复杂度高、处理速…

【ESP-IDF篇】搭建ESP-IDF软件开发环境,包括手动命令行和VSCode两种方式

忘记过去&#xff0c;超越自己 ❤️ 博客主页 单片机菜鸟哥&#xff0c;一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建记录 2023-09-27 ❤️❤️ 本篇更新记录 2023-09-27 ❤️&#x1f389; 欢迎关注 &#x1f50e;点赞 &#x1f44d;收藏 ⭐️留言&#x1f4dd;&#x1f64…

三相无感正弦波 BLDC 驱动MS39539/MS39535

产品简述 MS39539 和 MS39535 是无感三相直流电机驱动芯片&#xff0c;采用 正弦波驱动方式&#xff0c;具有低噪声及低震动的特点。 MS39539 和 MS39535 特别针对电机启动进行了优化&#xff0c;相比 MS39549 和 MS39545 适用于反向电动势更小的电机。 有一个速…

《数据结构、算法与应用C++语言描述》-栈的应用-列车车厢重排问题

列车车厢重排问题 一列货运列车有 n 节车厢&#xff0c;每节车厢要停靠在不同的车站。假设 n个车站从 1 到n 编号&#xff0c;而且货运列车按照从n到1的顺序经过车站。车厢的编号与它们要停靠的车站编号相同。为了便于从列车上卸掉相应的车厢&#xff0c;必须按照从前至后、从…

c语言练习68:文件的随机读写

文件的随机读写 fseek fseek 根据⽂件指针的位置和偏移量来定位⽂件指针。 1 int fseek ( FILE * stream, long int offset, int origin ); 例子&#xff1a; /* fseek example */ #include <stdio.h> int main () {FILE * pFile;pFile fopen ( "example.txt&…

接口测试的总结文档

接口测试的总结文档 第一部分&#xff1a;主要从问题出发&#xff0c;引入接口测试的相关内容并与前端测试进行简单对比&#xff0c;总结两者之前的区别与联系。但该部分只交代了怎么做和如何做&#xff1f;并没有解释为什么要做&#xff1f; 第二部分&#xff1a;主要介绍为什…

Java流的体系结构(一)

文章目录 一、文件读写操作FileReader和FileWriter1.main()2.FileReader1.说明&#xff1a;2.代码案例 3.对read()操作升级&#xff1a;使用read的重载方法4.FileWriter的使用1.说明2.代码 4.FileReader和FileWriter综合使用 二、使用步骤1.引入库 二、测试FileInputStream和Fi…

新型信息基础设施IP追溯:保护隐私与网络安全的平衡

随着信息技术的飞速发展&#xff0c;新型信息基础设施在全球范围内日益普及&#xff0c;互联网已经成为我们社会和经济生活中不可或缺的一部分。然而&#xff0c;随着网络使用的增加&#xff0c;隐私和网络安全问题也引发了广泛关注。在这个背景下&#xff0c;IP&#xff08;In…

XML文件反序列化读取

原始XML文件 <?xml version"1.0" encoding"utf-8" ?> <School headmaster"王校长"><Grade grade"12" teacher"张老师"><Student name"小米" age"18"/><Student name&quo…

26048-2010 易切削铜合金线材 学习记录

声明 本文是学习GB-T 26048-2010 易切削铜合金线材. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了易切削铜合金线材的产品分类、技术要求、试验方法、检验规则及标志、包装、运输、贮 存和合同(或订货单)内容等。 本标准适用…

国庆中秋特辑(四)MySQL如何性能调优?上篇

MySQL 性能优化是一项关键的任务&#xff0c;可以提高数据库的运行速度和效率。以下是一些优化方法&#xff0c;包括具体代码和详细优化方案。 接下来详细介绍&#xff0c;共有10点&#xff0c;先介绍5点&#xff0c;下次再介绍其他5点 1. 优化 SQL 语句 1.1 创建索引 创建…

【Axure高保真原型】3D圆柱图_中继器版

今天和大家分享3D圆柱图_中继器版的原型模板&#xff0c;图表在中继器表格里填写具体的数据&#xff0c;调整坐标系后&#xff0c;就可以根据表格数据自动生成对应高度的圆柱图&#xff0c;鼠标移入时&#xff0c;可以查看对应圆柱体的数据……具体效果可以打开下方原型地址体验…

了解汽车ecu组成

常用ecu框架组成&#xff1a; BCM(body control module)-车身控制模块: 如英飞凌tc265芯片&#xff1a; 车身控制单元&#xff08;BCM&#xff09;适合应用于12V和24V两种电压工作环境&#xff0c;可用于轿车、大客车和商用车的车身控制。输入模块通过采集电路采集各路开关量和…

SAP和APS系统订单BOM核对(SAP配置BOM攻略九)

配置订单BOM因为要考虑历史ECN、特征语法、BOM语法&#xff0c;是最复杂的一个算法结果&#xff0c;为了摆脱这种被动的场景&#xff0c;博主开发了一个被动核对数据的程序来保障数据质量。 今天是APS系统上线的第三天&#xff0c;我们的核对程序在昨天上线&#xff0c;面对大量…

nvm及node安装与配置

一、nvm安装 nvm&#xff08;Node Version Manager&#xff09;是一个用来管理node版本的工具。我们之所以需要使用node&#xff0c;是因为我们需要使用node中的npm(Node Package Manager)&#xff0c;使用npm的目的是为了能够方便的管理一些前端开发的包&#xff01;nvm的安装…

【Vue】数据表格增删改查与表单验证

目录 一、CRUD实现 1.1 后台CRUD编写 1.2 配置访问路径 1.3 前端编写&#xff08;及窗口&#xff09; 1.4 增删改查实现 1.4.1 新增示例 1.4.2 修改示例 1.4.3 删除示例 二、表单验证 2.1 设置表单验证属性 2.2 自定义验证规则 2.3 使用规则 2.4 效果演示 一、CRU…

通过docker-compose部署项目时遇到的问题

问题起因 最近两天在使用docker把项目的jar包打成镜像&#xff0c;Dockerfile文件的内容如下 FROM java:8 ADD mhxysy-0.0.1-SNAPSHOT.jar mhxysy.jar EXPOSE 8080 CMD java ‐jar mhxysy.jar 但是通过docker运行镜像的时候报错了 Error: Could not find or load main clas…