二叉树进阶(红黑树改造)

news2024/11/18 13:57:48

目录

引言

改造比较

1.颜色定义

2.节点构造

3.迭代器

3.1  迭代器的模板参数

3.2  迭代器的构造函数

3.3  迭代器的运算符重载函数

3.3.1 前置++

3.3.2 前置--

3.3.3 源码对比

3.3.4 其它运算符重载

4.红黑树改造

4.1 红黑树的begin(),end()函数

4.2 红黑树的模板参数

4.3 下标访问


引言

在上篇中我们已经实现了红黑树的构造,但是和stl库中的红黑树还是有区别,这节我们将根据stl库

中的源码进行比对,改造红黑树,之后将用改造后的红黑树,来封装map和set,完成模拟实现

改造比较

1.颜色定义

第一个在颜色定义上,我们采用的是枚举的方法,而源码采用的是直接全局定义,不过两种方式本

质没什么区别,可以不需要改动

//源码
typedef bool __rb_tree_color_type;
const __rb_tree_color_type __rb_tree_red = false;
const __rb_tree_color_type __rb_tree_black = true;
//我们自己的代码
enum Colour {
	RED = 0,
	BLACK,
};

2.节点构造

其次是节点的构造,我们在模拟实现红黑树的时候,采用的是kv型模板,即构造二叉树结点,都是

用pair来进行构造,但是我们需要用改造后的红黑树来封装map和set,也就是套用同一个红黑树模

板,前者的value才是pair,后者的value则为key

因此,我们要进行模板参数的改造,将kv模型改为T模板,由上层来实例化相应的红黑树

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)
	{}
};

当然,源码会比我们的节点类写得更为精妙,源码与节点相关的类有两个类,一个是节点基类,另

一个才是节点类(节点基类的派生类),并且在节点基类中,加入了minimum,maximum两个函数,

之后在实现像是leftmost函数(找最左节点,即红黑树的最小节点中直接赋用即可)

//源码
struct __rb_tree_node_base
{
  //重命名节点基类颜色为color_type
  typedef __rb_tree_color_type color_type;
  //重命名节点指针为base_ptr
  typedef __rb_tree_node_base* base_ptr;
  
  color_type color; 
  base_ptr parent;
  base_ptr left;
  base_ptr right;
  //找最左节点,返回节点指针
  static base_ptr minimum(base_ptr x)
  {
    while (x->left != 0) x = x->left;
    return x;
  }
  //找最右节点,返回节点指针
  static base_ptr maximum(base_ptr x)
  {
    while (x->right != 0) x = x->right;
    return x;
  }
};

template <class Value>
struct __rb_tree_node : public __rb_tree_node_base
{
  typedef __rb_tree_node<Value>* link_type;
  //节点的成员仅有value_field,与我们的data成员是等价的
  //传pair,实例化出的就是map;传key,实例化出的就是set
  Value value_field;
};

3.迭代器

在之前红黑树的实现中,我们并没有实现红黑树的迭代器,而实际上,map和set都是支持双向迭

代器,即可以通过迭代器,实现中序遍历访问,因此,我们还需要在红黑树改造中,完成对迭代器

的实现

在链表那节中,我们已经提到过,迭代器要么是原生指针,要么是自定义类型对原生指针的封装,

模拟指针的行为,红黑树的迭代器也不例外,它实际就是原生指针的封装,然后在自定义类型中加

入相应前置++,前置--等函数

3.1迭代器的模板参数

和list类的迭代器相同,模板参数有三个,分别是T(Value),Ref,Ptr

其中后两个模板参数发挥的作用和list迭代器发挥的作用是相同的,目的就是完成const迭代器和普

通迭代器的实例化用同一个模板

当我们不想修改节点的值,需要使用const迭代器时,Ref模板参数开始发挥作用,只要往里面传

入const Value&即可实例化出相应的const迭代器 

同理,假如树节点中存的是自定义类型,我们想要通过迭代器重载->来模拟实现用迭代器访问自定

义类型数据,Ptr模板参数开始发挥作用,只要往里面传入const T*或T*,即可分别实例化出不能通

过->修改节点中的数据的const迭代器和能通过->修改节点中的数据的普通迭代器

当然stl源码中,同样采取了迭代器基类,迭代器类(继承迭代器基类)的方式

基类包含了两个函数,一个是increment自增函数,另一个是decrement自减函数,两者都是服务

于后面迭代器前置++,--运算符重载 

3.2  迭代器的构造函数

这里先提前说明,set类是不能对value进行修改的,而map类键值对应,是可以对value进行修改的

因此我们仿照源码中,模拟实现set和map封装时,set中的普通迭代器和const迭代器,实际上都是

const迭代器,不过这会导致上层红黑树类在调用begin函数时,发生冲突

因为set类的成员变量是普通对象_t,即便我们在红黑树类中已经实现了const迭代器版本的begin和end函数,但是在set类中,编译器依旧会调用普通迭代器的begin与end函数

//源码中set类的成员变量
typedef rb_tree<key_type, value_type, 
                  identity<value_type>, key_compare, Alloc> rep_type;
rep_type t;  // red-black tree representing set
//源码中红黑树类有关begin,end函数的实现
link_type& leftmost() const { return (link_type&) header->left; }
link_type& rightmost() const { return (link_type&) header->right; }

iterator begin() { return leftmost(); }
const_iterator begin() const { return leftmost(); }
iterator end() { return header; }
const_iterator end() const { return header; }

因此,在迭代器构造函数中,除了默认构造外,还要增加普通迭代器转为const迭代器的构造函数

//自己模拟实现
__RBtree_iterator() {}
__RBtree_iterator(Node* node)
	:_node(node)
{}
// 1、typedef __RBTreeIterator<T, T&, T*> iterator;  拷贝构造
// 2、 typedef __RBTreeIterator<T, const T&, const T*> const_iterator;
//  支持普通迭代器构造const迭代器的构造函数

__RBtree_iterator(const __RBtree_iterator<T, T&, T*>& it)
	:_node(it._node)
{}


//源码实现
typedef __rb_tree_iterator<Value, Value&, Value*>             iterator;
typedef __rb_tree_iterator<Value, const Value&, const Value*> const_iterator;
//迭代器自身重命名
typedef __rb_tree_iterator<Value, Ref, Ptr>                   self;
//节点指针重命名
typedef __rb_tree_node<Value>* link_type;

__rb_tree_iterator() {}
__rb_tree_iterator(link_type x) { node = x; }
//参数是普通迭代器引用,当传入普通迭代器,它是拷贝构造;
//当传入const迭代器,它是支持普通迭代器构造const迭代器的构造函数
__rb_tree_iterator(const iterator& it) { node = it.node; }

3.3  迭代器的运算符重载函数

3.3.1 前置++

运算符重载函数中最关键就是实现前置++,--这两个运算符重载

  对于红黑树来说,迭代器的移动要求符合中序遍历的要求,即用迭代器遍历整棵树时,会得到有

序序列

思路如下:

    假如当前节点的右子树不为空,下一个打印的节点就是右子树的最左节点

   假如当前节点的右子树为空,因为中序遍历是左,根,右的顺序进行遍历的,所以意味着这棵树已经结束,需要向上找孩子为父亲左孩子的祖先节点,其实就是找对应的根

当遍历完最后一个节点时,迭代器会一直向上沿着路径找孩子为父亲左孩子的祖先节点,找不到,

最后为空,则可以直接跳出循环 

//实现前置++,保证指针往后移动,能够实现树的中序遍历
	self& operator++()
	{
		//如果当前节点的右子树不为空
		if (_node->_right)
		{
			// 1、右不为空,下一个就是右子树的最左节点
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}


			_node = subLeft;
		}
//如果当前节点的右子树为空,由于按照左子树,根,右子树遍历,说明此时该子树已经全部遍历
//需要向上找孩子为父亲左孩子的祖先节点
		else
		{
			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

3.3.2 前置--

前置--则是相反操作,我们可以将它看作是按照右子树,根,左子树的顺序遍历整棵树,因此,进

行的操作和前置++其实完全是一样的,不过此时的left变成right,right变成left

	//实现前置--
	self& operator--()
	{

		//如果当前节点的左子树不为空
		if (_node->_left)
		{
			// 1、左不为空,下一个就是左子树的最右节点
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}


			_node = subRight;
		}
		// 2、左为空,孩子是父亲的右的那个祖先
		else
		{
			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

3.3.3 源码对比

源码中由于已经在基类已经实现过increment,decrement函数,因此在迭代器类已经继承,直接调

用即可,代码的整体思想是一样的,不过会有所差别

原因在于stl库中采用的红黑树结构会有自带一个头节点,为了进一步方便后面关联式容器set,

map的实现,为了与根节点进行区分,将头结点给成红色,并且让头结点的 pParent 域指向红黑树

的根节点,pLeft 域指向红黑树中最小的节点(leftmost),_pRight域指向红黑树中最大的节点

 (rightmost)

 void init() {
    header = get_node();
    color(header) = __rb_tree_red; // used to distinguish header from 
                                   // root, in iterator.operator++
    root() = 0;
    leftmost() = header;
    rightmost() = header;
  }

 可以看到,由于多了一个header的头节点,因此increment函数需要作出相应的改变

void increment()
  { 
    //右子树不为空,找右子树的最左节点
    if (node->right != 0) {
      node = node->right;
      while (node->left != 0)
        node = node->left;
    }
    //右子树为空,沿路径找节点为父节点的左孩子的祖先节点
    else {
      base_ptr y = node->parent;
      while (node == y->right) {
        node = y;
        y = y->parent;
      }
      if (node->right != y)
        node = y;
    }
  }

右子树不为空的情况都是一样的,但右子树为空的情况,因为header而变得复杂起来

右子树为空,最后迭代器指向的位置,肯定停留在父节点上

比如说5遍历完,迭代器++后,移动到的位置就是parent的位置,即源码中的y

但是为什么赋值前还需要判断节点的右孩子不与父节点相同呢?

因为会出现如图下面的情况,此时迭代器指向根节点,而恰好根节点没有右孩子,如果对迭代器进

行++操作,新的迭代器指向的就是header,而不需要再把y赋值给_node,这样++就能够继续推进

 同理,由于多了一个header的头节点,在进行我们原来的所有操作前,还需要判断现在迭代器指

向的是否是头节点,假如是头节点,则进行迭代器--操作,则迭代器指向根节点

如果不进行该特殊情况判断,则进行--操作,反而迭代器会指向begin()

void decrement()
  {
    if (node->color == __rb_tree_red &&
        node->parent->parent == node)
      node = node->right;
    else if (node->left != 0) {
      base_ptr y = node->left;
      while (y->right != 0)
        y = y->right;
      node = y;
    }
    else {
      base_ptr y = node->parent;
      while (node == y->left) {
        node = y;
        y = y->parent;
      }
      node = y;
    }
  }

 下面给出stl库中源码,顺便包括了后置++,后置--的实现

//iterator基类
struct __rb_tree_base_iterator
{
  typedef __rb_tree_node_base::base_ptr base_ptr;
  typedef bidirectional_iterator_tag iterator_category;
  typedef ptrdiff_t difference_type;
  base_ptr node;

  void increment()
  { 
    //右子树不为空,找右子树的最左节点
    if (node->right != 0) {
      node = node->right;
      while (node->left != 0)
        node = node->left;
    }
    //右子树为空,沿路径找节点为父节点的左孩子的祖先节点
    else {
      base_ptr y = node->parent;
      while (node == y->right) {
        node = y;
        y = y->parent;
      }
      if (node->right != y)
        node = y;
    }
  }

  void decrement()
  {
    if (node->color == __rb_tree_red &&
        node->parent->parent == node)
      node = node->right;
    else if (node->left != 0) {
      base_ptr y = node->left;
      while (y->right != 0)
        y = y->right;
      node = y;
    }
    else {
      base_ptr y = node->parent;
      while (node == y->left) {
        node = y;
        y = y->parent;
      }
      node = y;
    }
  }
};

//iterator类运算符重载 
self& operator++() { increment(); return *this; }
  self operator++(int) {
    self tmp = *this;
    increment();
    return tmp;
  }
    
  self& operator--() { decrement(); return *this; }
  self operator--(int) {
    self tmp = *this;
    decrement();
    return tmp;
  }

3.3.4 其它运算符重载

源码中将这类运算符重载函数,全部改为了内联函数,其它没有什么变化

//自己实现的代码
Ref operator*()
{
	return _node->_data;
}

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

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

//源码
inline bool operator==(const __rb_tree_base_iterator& x,
                       const __rb_tree_base_iterator& y) {
  return x.node == y.node;
}

inline bool operator!=(const __rb_tree_base_iterator& x,
                       const __rb_tree_base_iterator& y) {
  return x.node != y.node;
}

4.红黑树改造

4.1 红黑树的begin(),end()函数

由于在上面已经实现了迭代器的前置++,--运算符

接下来,只要在红黑树类实现begin,end函数,其实就可以用迭代器遍历这棵树了

对于我们模仿实现红黑树,由于不存在头节点,因此begin函数返回的就是红黑树的最左节点

end函数返回的是红黑树的最右节点的下一个节点,这里我们直接用空指针构造即可

同样实现const版本和普通迭代器两个版本

而由于源码中红黑树多了一个头节点,因此begin函数返回的是最左节点,而end函数返回的则是头

节点.

//我们自己模拟实现
iterator begin()
{
	Node* cur = _root;
	//找树的最左节点
	while (cur && cur->_left)
	{
		cur = cur->_left;
	}

	return iterator(cur);
}

const_iterator begin() const
{
	Node* cur = _root;
	//找树的最左节点
    while (cur && cur->_left)
	{
		cur = cur->_left;
	}

	return iterator(cur);
}

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

//源码

link_type& root() const { return (link_type&) header->parent; }
link_type& leftmost() const { return (link_type&) header->left; }
link_type& rightmost() const { return (link_type&) header->right; }

iterator begin() { return leftmost(); }
const_iterator begin() const { return leftmost(); }
iterator end() { return header; }
const_iterator end() const { return header; }

4.2 红黑树的模板参数

与我们之前模拟实现的红黑树不同,除了Key,Value两个参数外,源码中的红黑树有五个参数

除去最后一个和空间支配器相关的参数外,还增加了两个KeyOfValue,和Compare参数

第一个Key参数发挥的作用我们比较好理解,因为无论是map,或者是set,底层封装的这棵红黑

树,它首先是一棵搜索平衡二叉树,是按照key来建树的,find,erase这些函数接口参数都是key,

如果单纯只有一个Value模板,显然是不够的

第二个Value参数主要是服务于insert函数,它决定了树的节点里面存的是什么,map存的就是

pair,set存的就是key

第三个KeyOfValue参数,则是服务于比较

RBtree判断不了你是map还是set,我们需要自己在封装map和set的时候提供对应的Value,以此

完成红黑树类中值的判断

  • 比如set的keyofvalue就是它自己的key
  • 而map的keyofvalue是pair.first
//对于set类型而言,其value比较的是key,所以增加了一个模板参数
//改造前
Node* Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first > key)
		{
			cur = cur->_left;
		}
		else if (cur->_kv.first < key)
		{
			cur = cur->_right;
		}
		else
		{
			return cur;
		}
	}

	return nullptr;
}

//改造后
//假如是set,则传入的则是key比较;假如是map。传入的则是pair.first
Node* Find(const K& key)
{
	Node* cur = _root;
	KeyOfValue kot;
	while (cur)
	{   
		if (kot(cur-> _data) > key)
		{
			cur = cur->_left;
		}
		else if (kot(cur-> _data) < key)
		{
			cur = cur->_right;
		}
		else
		{
			return cur;
		}
	}

	return nullptr;
}

 第四个Compare参数则是给用户机会,能够提供比较的方法,键值大于往左边插入还是往右边插

入等等

4.3 下标访问

map非常特殊的一点就是,可以直接用map[key]=value来修改value的内容,如果不存在,则直接

插入相应的新节点

原理也很简单,就是稍微改造一下insert函数,不单单返回bool,来判断是否插入成功,还需要返

回插入元素的迭代器,通过迭代器,我们就可以在map中重载方括号运算符,来修改对应的Value

//修改后的insert函数代码
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;
		KeyOfValue kot;
		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;
		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;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					//父亲和叔叔节点都调节为黑色
					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);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						//    g
					   //   p   u
					  //      c
						//LR双旋
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						//parent->_col = RED;
						grandfather->_col = RED;
					}

					break;
				}
			}
			//父亲为爷爷的右节点
			else
			{
				//则叔叔是爷爷的左节点
				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 // 情况2+3:u不存在/u存在且为黑,旋转+变色
				{
					//     g
					//   u   p
					//        c
					//左单旋
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						//     g
						//   u   p
						//     c
						//RL双旋
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						//parent->_col = RED;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

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

源码的insert_unique的实现

  

 // insert/erase
 //插入声明
  pair<iterator,bool> insert_unique(const value_type& x);

//源码中插入的具体实现
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::insert_unique(const Value& v)
{
  link_type y = header;
  //从根节点开始往下找合适的插入点
  link_type x = root();
  bool comp = true;
  while (x != 0) {
    y = x;
    //小于则true,大于等于则false,循环中只会出现一次false
    comp = key_compare(KeyOfValue()(v), key(x));
    //x遇上大,则往左走;遇到小于等于,则往右走
    x = comp ? left(x) : right(x);
  }
//x为新插入的节点位置,y为其父节点,v为插入节点的value值
//循环结束时,此时x必为空,y必为叶节点
  //令迭代器j指向父节点y
  iterator j = iterator(y);   
  //如果comp为真,说明x位于y的左边
  if (comp)
    //如果j指向树的最左节点,则直接插入
    if (j == begin())     
      return pair<iterator,bool>(__insert(x, y, v), true);
    else
    //插入点之父节点不为最左节点,回调j
      --j;
  //小于新值,则往右进行插入
  if (key_compare(key(j.node), KeyOfValue()(v)))
    return pair<iterator,bool>(__insert(x, y, v), true);
  //到这一步说明键值重复,那么就不该插入该值,返回false
  return pair<iterator,bool>(j, false);
}

PS:上述代码展示的是插入中不允许重复元素

源码解决重复问题的方法也比较巧妙

   父节点为最左节点,也就是最小的,插入的新节点比父节点还小,则插入的新节点一定不是重复元素,可以直接插入

   如果不是最左节点,就会出现两种情况

   第一种情况,comp为真,此时要回调父节点,--j,再判断是否会有重复元素

   比如说【8 9 11】,往里面插入10,就需要判断9,如果9小于10,则插入

   第二种,comp为假

   比如说[【9 8 13 10 12】,往里面插入11,11大于10,往右直接插入

关键其实在于理解,假如出现相同元素后,会发生什么?

  x会往右走,然后一直往左移动,因为右子树所有的元素一定比根要大

五.完整代码展示

#include <iostream>
using namespace std;

enum Colour {
	RED = 0,
	BLACK,
};

//为了实现map与set封装使用同一个模板红黑树,前者的value是pair,后者的value为key
//因此我们需要对红黑树的模板进行改造
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 __RBtree_iterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBtree_iterator<T, Ref, Ptr> self;
	Node* _node;

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

	// 1、typedef __RBTreeIterator<T, T&, T*> iterator;  拷贝构造
	// 2、 typedef __RBTreeIterator<T, const T&, const T*> const_iterator;
	//  支持普通迭代器构造const迭代器的构造函数

	__RBtree_iterator(const __RBtree_iterator<T, T&, T*>& it)
		:_node(it._node)
	{}

	//实现前置++,保证指针往后移动,能够实现树的中序遍历
	self& operator++()
	{
		//如果当前节点的右子树不为空
		if (_node->_right)
		{
			// 1、右不为空,下一个就是右子树的最左节点
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}


			_node = subLeft;
		}
//如果当前节点的右子树为空,由于按照左子树,根,右子树遍历,说明此时该子树已经全部遍历
		//需要向上找孩子为父亲左孩子的祖先节点
		else
		{
			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	//实现前置--
	self& operator--()
	{

		//如果当前节点的左子树不为空
		if (_node->_left)
		{
			// 1、左不为空,下一个就是左子树的最右节点
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}


			_node = subRight;
		}
		// 2、左为空,孩子是父亲的右的那个祖先
		else
		{
			Node* parent = _node->_parent;
			Node* cur = _node;

			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

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

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

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

//仿函数
template<class K, class T, class KeyOfValue>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

public:
	typedef __RBtree_iterator<T, T&, T*> iterator;
	typedef __RBtree_iterator<T, const T&, const T*> const_iterator;

	iterator begin()
	{
		Node* cur = _root;
		//找树的最左节点
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}

		return iterator(cur);
	}

	const_iterator begin() const
	{
		Node* cur = _root;
		//找树的最左节点
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}

		return iterator(cur);
	}

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

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

	//对于set类型而言,其value比较的是key,所以增加了一个模板参数
	Node* Find(const K& key)
	{
		Node* cur = _root;
		KeyOfValue kot;
		while (cur)
		{   
			if (kot(cur-> _data) > key)
			{
				cur = cur->_left;
			}
			else if (kot(cur-> _data) < key)
			{
				cur = cur->_right;
			}
			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;
		KeyOfValue kot;
		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;
		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;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					//父亲和叔叔节点都调节为黑色
					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);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						//    g
					   //   p   u
					  //      c
						//LR双旋
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						//parent->_col = RED;
						grandfather->_col = RED;
					}

					break;
				}
			}
			//父亲为爷爷的右节点
			else
			{
				//则叔叔是爷爷的左节点
				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 // 情况2+3:u不存在/u存在且为黑,旋转+变色
				{
					//     g
					//   u   p
					//        c
					//左单旋
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						//     g
						//   u   p
						//     c
						//RL双旋
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						//parent->_col = RED;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

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

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

	bool IsRBTree()
	{
		//假如根节点存在,但颜色不是黑色,则不是红黑树
		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);
	}

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 (blackNum != benchmark)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}

		//递归遇到黑色节点时,则blackNum可以加1
		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 RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		//b变成30的右
		parent->_right = subRL;
		//父节点也需要调整,但subRL可能为空
		if (subRL)
			subRL->_parent = parent;

//调整时未必是整棵树的调整,所以还需要考虑parent的链接问题,因此需要先记录ppNode
		Node* ppNode = parent->_parent;

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

		if (ppNode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			//在调整爷爷节点指向的时候,还需要考虑原来parent是爷爷的左还是右
			//subR重新链接回爷爷的左或者右
			if (ppNode->_right == parent)
			{
				ppNode->_right = subR;
			}
			else
			{
				ppNode->_left = subR;
			}

			subR->_parent = ppNode;
		}
	}

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

		//b变成60的左
		parent->_left = subLR;
		//父节点也需要调整,但subRL可能为空
		if (subLR)
			subLR->_parent = parent;

//调整时未必是整棵树的调整,所以还需要考虑parent的链接问题,因此需要先记录ppNode
		Node* ppNode = parent->_parent;

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

		if (ppNode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			//在调整爷爷节点指向的时候,还需要考虑原来parent是爷爷的左还是右
			//subL重新链接回爷爷的左或者右
			if (ppNode->_right == parent)
			{
				ppNode->_right = subL;
			}
			else
			{
				ppNode->_left = subL;
			}

			subL->_parent = ppNode;
		}
	}

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

		_Inorder(root->_left);
		cout << root->_kv.first << " ";
		_Inorder(root->_right);
	}
private:
	Node* _root = nullptr;
};

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

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

相关文章

又有一个手艺人震惊了B站用户

飞瓜数据&#xff08;B站版&#xff09;【热门视频榜】周榜显示&#xff0c;霸占全站视频流量第一的是来自UP主爱捣鼓的邢志磊发布的作品《我花了半年时间给猫做了个房子》。 视频在一周时间内新增播放1232.2万&#xff0c;新增点赞139.4万。 根据视频详细数据显示&#xff0c…

超详细推导逻辑回归公式与代码实现(二分类与多分类)

目录 概述逻辑回归理论数学推导二类分类多分类 代码实现备注 概述 本文使用梯度下降法对逻辑回归进行训练&#xff0c;使用类似于神经网络的方法进行前向传播与反向更新&#xff0c;使用数学公式详细推导前向传播与反向求导过程&#xff0c;包括二分类和多分类问题&#xff0c…

农业管理3d可视化管理大屏展示为乡村新基建加速

随着科技的不断发展&#xff0c;智慧农业已经成为当今社会农业发展的一个重要趋势。而数字孪生技术作为一种新兴的技术手段&#xff0c;正在逐渐应用到智慧农业领域中。 数字孪生公司深圳华锐视点基于数字孪生为核心技术打造的智慧大脑为乡村新基建加速&#xff0c;让乡村更“聪…

Python入门【推导式创建序列、字典推导式、集合推导式】(九)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小王&#xff0c;CSDN博客博主,Python小白 &#x1f4d5;系列专栏&#xff1a;python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 &#x1f4e7;如果文章知识点有错误…

239.滑动窗口最大值

leetcode原题链接 题目描述&#xff1a; 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例1: 输入&#xff1a;nums [1,…

【UniApp开发小程序】悬浮按钮+出售闲置商品+商品分类选择【基于若依管理系统开发】

文章目录 界面效果界面实现悬浮按钮实现商品分类选择界面使元素均匀分布 闲置商品描述信息填写界面价格校验 界面效果 【悬浮按钮】 【闲置商品描述信息填写界面】 【商品分类选择界面】 【分类选择完成】 界面实现 悬浮按钮实现 悬浮按钮漂浮于页面之上&#xff0c;等页面…

使用langchain与你自己的数据对话(三):检索(Retrieval)

之前我已经完成了使用langchain与你自己的数据对话的前两篇博客&#xff0c;还没有阅读这两篇博客的朋友可以先阅读一下&#xff1a; 使用langchain与你自己的数据对话(一)&#xff1a;文档加载与切割使用langchain与你自己的数据对话(二)&#xff1a;向量存储与嵌入 今天我们…

【C语言】文件操作(二)

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3; 目录 &#x1f4cc;补充1.sprintf2.…

hive 全量表、增量表、快照表、切片表和拉链表

全量表&#xff1a;记录每天的所有的最新状态的数据&#xff0c;增量表&#xff1a;记录每天的新增数据&#xff0c;增量数据是上次导出之后的新数据。快照表&#xff1a;按日分区&#xff0c;记录截止数据日期的全量数据切片表&#xff1a;切片表根据基础表&#xff0c;往往只…

如何利用tf.keras 实现深度学习?

tf.keras是TensorFlow 2.0的高阶API接口&#xff0c;为TensorFlow的代码提供了新的风格和设计模式&#xff0c;大大提升了TF代码的简洁性和复用性&#xff0c;官方也推荐使用tf.keras来进行模型设计和开发。 常用模块 tf.keras中常用模块如下表所示&#xff1a; 常用方法 深度…

智慧环保:创造绿色未来

随着全球环境问题的日益严重&#xff0c;智慧环保成为推动绿色发展的关键。智慧环保利用先进的技术手段和智能化设备&#xff0c;致力于解决环境问题&#xff0c;保护生态环境&#xff0c;实现可持续发展。它融合了物联网、人工智能、大数据等技术&#xff0c;将科技的力量与环…

C#实现计算题验证码

开发环境&#xff1a;C#&#xff0c;VS2019&#xff0c;.NET Core 3.1&#xff0c;ASP.NET Core API 1、建立一个验证码控制器 新建两个方法Create和Check&#xff0c;Create用于创建验证码&#xff0c;Check用于验证它是否有效。 声明一个静态类变量存放列表&#xff0c;列…

公众号运营:公众号互选广告操作流程指南

什么是公众号互选广告平台&#xff1f; 公众号互选平台&#xff0c;是广告主和流量主双向互选、自由达成内容合作的交易 平台&#xff0c;广告创意呈现在公众号文章内容中。 收入模式&#xff1a;按合作文章收费&#xff0c;合作价格由流量主自主决定。 操作配合&#xff1a;提…

1334179-85-9,BTTAA,是各种化学生物学实验中生物偶联所需

资料编辑|陕西新研博美生物科技有限公司小编MISSwu​ BTTAA试剂 | 基础知识概述&#xff08;部分&#xff09;: 中文名称&#xff1a;2-[4-({双[(1-叔丁基-1H-1,2,3-三唑-4-基)甲基]氨基}甲基)-1H-1,2,3-三唑-1-基]乙酸 英文名称&#xff1a;BTTAA CAS号&#xff1a;1334179-8…

Vue2 第一节 通用概念和前置知识

本篇将记录自己学习Vue的知识点总结 学习资源&#xff1a; B站 &#xff1a;尚硅谷Vue2.0Vue3.0全套教程 有个博主将这个视频总结成了笔记&#xff0c;之后的博客也会参考这个笔记 (126条消息) 【2022.3】尚硅谷Vue.js从入门到精通基础笔记&#xff08;理论实操知识点速查&…

Android 之 Paint API —— PathEffect (路径效果)

本节引言&#xff1a; 本节继续来学习Paint的API——PathEffect(路径效果)&#xff0c;我们把画笔的sytle设置为Stroke&#xff0c;可以 绘制一个个由线构成的图形&#xff0c;而这些线偶尔会显得单调是吧&#xff0c;比如你想把这些先改成虚线&#xff0c;又 或者想让路径的转…

我们聊聊性能测试的理解误区

有同学私信我&#xff0c;和他聊了聊关于性能测试的一些话题&#xff0c;发现他对性能测试的理解走入了一些误区。 在一些技术交流群&#xff0c;同样遇到过很多同学由于对性能测试理解上的误区导致的各种问题&#xff0c;比如&#xff1a; 注册用户数并发数&#xff0c;然后服…

Top命令

Top top - 12:46:01 up 2 days, 11:10, 3 users, load average: 0.56, 0.59, 0.45系统基本信息&#xff1a;显示了系统运行时间、登录用户数和平均负载&#xff08;load average&#xff09;情况。平均负载是系统在特定时间范围内的平均活跃进程数&#xff0c;可以用来衡量系…

自动驾驶之轨迹规划8——Apollo参考线和轨迹

1. abstract 本文主要讲解routing和planning模块中的reference line&#xff0c;我之前一直搞不明白这个reference line是如何生成的&#xff0c;有什么作用&#xff0c;和routing以及planning的关系。现在有了一些心得打算梳理一下&#xff1a; 决策规划模块负责生成车辆的行…

Go基础—反射,性能和灵活性的双刃剑

Go基础—反射&#xff0c;性能和灵活性的双刃剑 1 简介2 结构体成员赋值对比3 结构体成员搜索并赋值对比4 调用函数对比5 基准测试结果对比 1 简介 现在的一些流行设计思想需要建立在反射基础上&#xff0c;如控制反转&#xff08;Inversion Of Control&#xff0c;IOC&#x…