AVL树与红黑树

AVL树

概念
  • 背景:二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下,因此两位俄罗斯的数学家G.M.Adelson-Velskii 和 E.M.Landis 在 1962 年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过 1 (需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度;
  • 性质:
    • 它的左右子树都是 AVL 树;
    • 左右子树高度之差 (简称平衡因子) 的绝对值不超过 1 (具体有:-1 / 0 / 1);
      在这里插入图片描述
  • 调整:在插入节点之后,需要向上调整平衡因子,这个很简单,具体看下面的代码,平衡因子调整完毕之后,有可能会导致平衡树不再平衡,所以需要对结构进行调整,这个来具体分析:
    假如以 parent 为根的子树不平衡,即 parent 的平衡因子为 2 或者 -2,分以下情况考虑:
    1. parent 的平衡因子为 2,说明 parent 的右子树高,设 parent 的右子树的根为 subR,设 subR 的左子树的根为 subRL;
      • 当 subR 的平衡因子为 1 时,对 parent 执行左单旋,旋转之后 parent 平衡因子为 0,subR 平衡因子为 0(平衡因子的调整在旋转的过程中已经修改了,外部不需要修改);
      • 当 subR 的平衡因子为 -1 时,先对 subR 进行右旋,再对 parent 进行左旋,旋转之后,要对平衡因子进行外部调整,平衡因子的调整分为三种情况,设旋转前 subRL 的平衡因子为 bf:
        • bf == 1:parent 的平衡因子修改为 -1,subR 的平衡因子修改为 0;
        • bf == -1:parent 的平衡因子修改为 0,subR 的平衡因子修改为 1;
        • bf == 0:无需任何改动;
    2. parent 的平衡因子为 -2,说明 parent 的左子树高,设 parent 的左子树的根为 subL,设 subL 的右子树的根为 subLR;
      • 当 subL 的平衡因子为 -1 是,对 parent 执行右单旋,旋转之后 parent 平衡因子为 0,subL 平衡因子为 0(平衡因子的调整在旋转的函数中已经修改了,外部不需要修改);
      • 当 subL 的平衡因子为 1 时,先对 subL 进行左旋,再对 parent 进行右旋,旋转之后,要对平衡因子进行外部调整,平衡因子的调整分为两种情况,设旋转前 subLR 的平衡因子为 bf:
        • bf == 1:parent 的平衡因子修改为 0,subL 的平衡因子修改为 -1;
        • bf == -1:parent 的平衡因子修改为 1,subL 的平衡因子修改为 0;
        • bf == 0:无需任何改动;
    • 旋转完成后,原 parent 为根的子树个高度降低,已经平衡,不需要再向上更新;
  • 总结:AVL 树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过 1,这样可以保证查询时高效的时间复杂度,即 log2N 。但是如果要对 AVL 树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置;
    因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑 AVL 树,但一个结构经常修改,就不太适合;
实现
#include<iostream>
using namespace std;

template<class T>
struct AVLNode {
	//数据
	T _data;
	//平衡因子
	int _bf;
	//左孩子指针
	AVLNode* _left;
	//右孩子指针
	AVLNode* _right;
	//父结点指针
	AVLNode* _parent;
	//带参构造函数
	AVLNode(const T& val = T())
		:_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_data(val)
		,_bf(0)
	{}
};

template<class T>
class AVLTree {
public:
	typedef AVLNode<T> Node;
	//构造函数
	AVLTree()
		:_root(nullptr)
	{}
	//左旋操作
	void RotateL(Node* parent) {
		//先拿到要旋转节点的右孩子
		Node* node = parent->_right;
		//再拿到要旋转节点的右孩子的左节点
		Node* nodeleft = node->_left;
		//进行重新链接
		parent->_right = nodeleft;
		if(nodeleft)
			nodeleft->_parent = parent;
		//如果要旋转的节点是根结点,那么需要额外考虑
		if (parent == _root) {
			_root = node;
			node->_parent = nullptr;
		}
		//如果不是根结点那么则正常调整
		else {
			Node* tmp = parent->_parent;
			node->_parent = tmp;
			if (tmp->_left == parent)
				tmp->_left = node;
			else
				tmp->_right = node;
		}
		parent->_parent = node;
		node->_left = parent;
		//调整平衡因子
		parent->_bf = node->_bf = 0;
	}
	//右旋操作
	void RotateR(Node* parent) {
		//先拿到要旋转节点的左孩子
		Node* node = parent->_left;
		//再拿到要旋转节点的左孩子的右节点
		Node* noderight = node->_right;
		//接着调整链接
		parent->_left = noderight;
		if (noderight)
			noderight->_parent = parent;
		//如果要旋转的节点是根结点,则需要额外考虑
		if (parent == _root) {
			_root = node;
			node->_parent = nullptr;
		}
		//如果不是则正常调整
		else {
			Node* tmp = parent->_parent;
			node->_parent = tmp;
			if (tmp->_left = parent)
				tmp->_left = node;
			else
				tmp->_right = node;
		}
		parent->_parent = node;
		node->_right = parent;
		//调整平衡因子
		parent->_bf = node->_bf = 0;
	}
	//数据插入
	bool insert(const T& val) {
		//如果当前树的为空树,那么直接将结点插入
		if (_root == nullptr) {
			_root = new Node(val);
			return true;
		}
		//否则就先先找到一个合适的位置(二叉搜索树的查找一样),然后插入
		Node* parent = nullptr;
		Node* node = _root;
		while (node) {
			parent = node;
			if (node->_data == val)
				return false;
			else if (node->_data > val)
				node = node->_left;
			else
				node = node->_right;
		}
		//找到合适的位置后,将结点插入,并且将指针链接起来
		node = new Node(val);
		if (parent->_data > val)
			parent->_left = node;
		else
			parent->_right = node;
		node->_parent = parent;
		//插入结点后开始调整平衡因子
		while (parent) {
			//更新父节点的平衡因子
			if (parent->_left == node)
				parent->_bf--;
			else
				parent->_bf++;
			//检查平衡因子的变化情况
			if (parent->_bf == 0)
				//如果变为0,则说明无影响
				break;
			else if (parent->_bf == -1 || parent->_bf == 1) {
				//继续向上检查平衡因子
				node = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == -2 || parent->_bf == 2) {
				if (parent->_bf == -2 && node->_bf == -1)
					//此时需要进行右旋调整
					RotateR(parent);
				else if(parent->_bf == 2 && node->_bf == 1)
					//此时需要进行左旋调整
					RotateL(parent);
				else if (parent->_bf == -2 && node->_bf == 1) {
					//先保存当前节点的右子树的平衡因子
					int bf = node->_right->_bf;
					//以当前节点先进行左旋,再以父结点进行右旋
					RotateL(node);
					RotateR(node);
					//修正平衡因子
					if (bf == -1) {
						parent->_bf = 1;
						node->_bf = 0;
					}
					else if (bf == 1) {
						parent->_bf = 0;
						node->_bf = -1;
					}
				}
				else if (parent->_bf == 2 && node->_bf == -1) {
					//先保存当前节点的左子树的平衡因子
					int bf = node->_left->_bf;
					//以当前节点先进行右旋,再以父结点进行左旋
					RotateR(node);
					RotateL(node);
					//修正平衡因子
					if (bf == -1) {
						parent->_bf = 0;
						node->_bf = 1;
					}
					else if(bf == 1) {
						parent->_bf = -1;
						node->_bf = 0;
					}
				}
				//调整完毕,结束循环
				break;
			}
		}
		return true;
	}
	//中序遍历
	void inorder() {
		_inorder(_root);
		cout << endl;
	}
	//检查左右子树高度差与平衡因子是否相同
	bool isBalance(Node* root) {
		//如果是空树则返回真
		if (root == nullptr)
			return true;
		//拿到左右子树高度
		int left = High(root->_left);
		int right = High(root->right);
		//判断左右子树高度差是否等于平衡因子
		if (right - left == root->_bf)
			return false;
		//返回当前节点的平衡因子是否小于2,并且左右子树是否满足
		return abs(root->_bf) < 2 && isBalance(root->left) && isBalance(root->right);
	}
	//获取树的高度
	int High(Node* root) {
		if (root == nullptr)
			return 0;
		//树的高度是左右子树中较高的那一个
		int left = High(root->_left);
		int right = High(root->_right);
		return left > right ? left + 1 : right + 1;
	}

	//删除节点:先按照二叉搜索树删除节点的方法进行删除,删除掉之后调整平衡因子
		//删除之后父节点的平衡因子变为 -1 或者 1,则说明不需要进行调整
		//删除时候父结点的平衡因子变为 0,那么需要向上继续调整,直到遇到第一个 -2 或者 2,然后开始旋转调整
private:
	//二叉树的中序遍历
	void _inorder(Node* root) {
		if (root == nullptr)
			return;
		_inorder(root->_left);
		cout << root->_data << " ";
		_inorder(root->_right);
	}
	Node* _root;
};

int main() {

	return 0;
}

红黑树

概念
  • 概念:红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是 Red 或 Black,通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的;
  • 性质:
    1. 每个结点在插入时,默认是红色的,最后要经过调整,调整之后不是红色就是黑色;
    2. 根节点是黑色的;
    3. 如果一个节点是红色的,则它的两个孩子结点是黑色的,也就是不允许出现连续的红色结点;
    4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点;
    5. 每个叶子结点都是黑色的 (此处的叶子结点指的是空结点);
  • 结构:红黑树中包含了一个头结点,它不包含任何数据,它的父指针指向根结点,左指针指向该树的最左节点,有指针指向该树的最右节点;
插入操作
  • 插入操作会有可能改变红黑树的结构,所以需要对红黑树的插入操作进行细细分析,下面是一些会出现的情况,以及做出的应对之法;
  • 插入节点的父结点为插入节点的父结点,父节点的父结点为插入节点的祖父节点,祖父节点除过父节点之外的另一个结点为插入节点的叔叔结点;
  • 父节点为黑色:直接插入,因为不会破坏红黑树的性质;
  • 父节点为红色、叔叔节点存在且为红色:插入后将父结点和叔叔节点都修改为黑色,祖父节点修改为红色,并将祖父节点的位置当做当前位置,向上循环遍历查看情况,直到无连续红色结点即可结束;
  • 父节点为红色、叔叔节点不存在或者为黑色:
    • 插入节点为父节点的左子树,父节点为祖父节点的左子树:对祖父节点进行右旋,并将祖父节点改为红色,父结点改为黑色;
    • 插入节点为父节点的右子树,父节点为祖父节点的左子树:先对父结点进行左旋,旋转后就变成了上一种情况,因此对祖父节点进行右旋,并将祖父节点改为红色,父结点改为黑色;
  • 父节点为红色、叔叔节点不存在或者为黑色:
    • 插入节点为父节点的右子树,父节点为祖父节点的右子树:对祖父节点进行左旋,并将祖父节点改为红色,父结点改为黑色;
    • 插入节点为父节点的左子树,父节点为祖父节点的右子树:先对父结点进行右旋,旋转后就变成了上一种情况,因此对祖父节点进行左旋,并将祖父节点改为红色,父结点改为黑色;
迭代器
  • 红黑树的迭代器和链表的迭代器原理是相通的,就是封装了红黑树的结点,然后重载了一些迭代器所能用到的运算符;
  • 因为红黑树的性质,所以遍历红黑树可以得到一个有序的序列,所以红黑树的正向迭代器的++操作走的是(左中右)的中序遍历移动,而反向迭代器的++操作走的是(右中左)的中序遍历移动;
  • 此时我们再回过头来看看,头结点中的左 / 右指针分别指向红黑树的最左 / 右结点,这是非常有意义的,这样begin()中封装的就是最左节点,这个刚好是正向遍历的起始位置,rbegin()中封装的就是最右节点,这正好是反向遍历的起始位置,而end() / rend()中封装的就是头结点;
实现
  • 说明:由于mapset等容器都是由红黑树实现的,所以在红黑树的实现过程中,顺便就将结构设计为方便实现mapset的底层结构,这中间的许多细节、要点都写在代码的注释中的,可以仔细查看;
#include<iostream>
using namespace std;

enum COLOR {
	BLACK,
	RED
};

//红黑树结点
template<class V>
struct RBNode {
	//指向父节点的指针
	RBNode* _parent;
	//指向左孩子节点的指针
	RBNode* _left;
	//指向右孩子节点的指针
	RBNode* _right;
	//节点的颜色
	COLOR _color;
	//数据
	V _val;
	//节点的构造函数
	RBNode(const V& val = V())
		:_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_color(RED)
		,_val(val)
	{}
};

//封装红黑树的迭代器
template<class V>
struct RBTreeIterator {
	typedef RBNode<V> Node;
	typedef RBTreeIterator<V> Self;
	//成员变量:红黑树结点
	Node* _node;
	//构造函数
	RBTreeIterator(Node* node)
		:_node(node)
	{}
	//重载 * 运算符
	V& operator*() {
		return _node->_val;
	}
	//重载 -> 运算符
	V* operator->() {
		return &(_node->_val);
	}
	//重载 != 运算符
	bool operator!=(const Self& it) {
		return _node != it._node;
	}
	//重载前置 ++ 运算符
	Self& operator++() {
		//如果当前迭代器的节点有右子树,那么迭代器就更新到到右子树的最左节点
		if (_node->_right) {
			_node = _node->_right;
			while (_node->_left)
				_node = _node->_left;
		}
		//如果没有右子树,则分两种情况
		//如果当前节点是父节点的左孩子,那么直接将迭代器更新到父节点位置
		//如果当前节点是父节点的右孩子,那么就将当前节点更新到父结点位置,父结点更新到祖父节点的位置,继续循环判断
		else {
			//拿到当前节点的父结点
			Node* parent = _node->_parent;
			//循环判断是否在右边
			while (parent->_right = _node) {
				_node = parent;
				parent = _node->_parent;
			}
			//避免该树没有右子树的情况,因为迭代器的结束条件就是走到头结点位置
			//如果该树没有右子树,那么就会在根结点与头结点之间死循环
			if (_node->_right != parent)
				_node = parent;
		}
		return *this;
	}
	Self& operator--() {
		//如果当前迭代器的节点有左子树,那么迭代器就更新到到左子树的最右节点
		if (_node->_left) {
			_node = _node->_left;
			while (_node->_right)
				_node = _node->_right;
		}
		//如果没有左子树,则分两种情况
		//如果当前节点是父节点的右孩子,那么直接将迭代器更新到父节点位置
		//如果当前节点是父节点的左孩子,那么就将当前节点更新到父结点位置,父结点更新到祖父节点的位置,继续循环判断
		else {
			//拿到父结点
			Node* parent = _node->_parent;
			//循环判断
			while (parent->_left == _node) {
				_node = parent;
				parent = _node->_parent;
			}
			//避免该树没有左子树的情况,因为迭代器的结束条件就是走到头结点位置
			//如果该树没有左子树,那么就会在根结点与头结点之间死循环
			if (_node->_left != parent)
				_node = parent;
		}
		return *this;
	}
};

//红黑树
template<class K, class V, class keyofval>
class RBTree {
public:
	typedef RBNode<V> Node;
	typedef RBTreeIterator<V> iterator;
	//构造函数
	RBTree()
		//创建一个空的头结点
		:_header(new Node)
	{
		_header->_left = _header->_right = _header;
	}
	//迭代器
	iterator begin() {
		return iterator(_header->_left);
	}
	iterator end() {
		return iterator(_header);
	}
	iterator rbegin() {
		return iterator(_header->_right);
	}
	iterator rend() {
		return iterator(_header);
	}
	//插入操作
	pair<iterator, bool> insert(const V& val) {
		//如果是一个空红黑树,那么就直接插入数据
		if (_header->_parent == nullptr) {
			//直接插入数据
			Node* root = new Node(val);
			//建立连接
			root->_parent = _header;
			_header->_parent = _header->_left = _header->_right = root;
			root->_color = BLACK;
			return make_pair(iterator(_header->_parent), true);
		}
		//仿函数:用来获取存储数据所需要的比较数据——key,这个由上层(map/set等)提供
		keyofval kov;
		//开始搜索待插入数据
		Node* parent = nullptr;
		Node* cur = _header->_parent;
		while (cur) {
			parent = cur;
			if (kov(cur->_val) == kov(val))
				return make_pair(iterator(cur), false);
			else if (kov(cur->_val) > kov(val))
				cur = cur->_left;
			else
				cur = cur->_right;
		}
		//找到位置后开始进行插入,并建立链接
		cur = new Node(val);
		if (kov(parent->_val) > kov(cur))
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;
		Node* tmp = cur;
		//向上判断当前节点与父结点的颜色是否符合
		while (cur->_color == RED && cur->_parent->_color == RED) {
			parent = cur->_parent;
			//先拿到祖父节点
			Node* gfather = parent->_parent;
			//分情况讨论,如果父节点是祖父节点的左子树
			if (gfather->_left == parent) {
				//此时判断叔叔结点是否存在
				Node* uncle = gfather->_right;
				//如果存在则进行下面的调整
				if (uncle && uncle->_color == RED) {
					//将父结点和叔叔结点的颜色改为黑色
					parent->_color = uncle->_color = BLACK;
					//将祖父节点的颜色改为红色
					gfather->_color = RED;
					//由于祖父节点被变为红色,所以有可能引发潜在问题,所以继续向上
					cur = gfather;
				}
				//如果叔叔节点不存在或者叔叔结点是黑色的
				else {
					//判断是否需要双旋,双旋条件:父亲是祖父的左节点,当前节点是父亲的右结点
					if (parent->_right == cur) {
						//父结点先进行左旋
						RotateL(parent);
						//此时cur、parent的指向是反的,所以需要交换
						swap(cur, parent);
					}
					//此时再对祖父节点进行右旋
					RotaleR(gfather);
					//再修改节点的颜色
					parent->_color = BLACK;
					gfather->_color = RED;
					break;
				}
			}
			//分情况讨论,如果父节点是祖父节点的右子树
			else{
				//此时判断叔叔结点是否存在
				Node* uncle = gfather->_right;
				//如果存在则进行下面的调整
				if (uncle && uncle->_color == RED) {
					//将父结点和叔叔结点的颜色改为黑色
					parent->_color = uncle->_color = BLACK;
					//将祖父节点的颜色改为红色
					gfather->_color = RED;
					//由于祖父节点被变为红色,所以有可能引发潜在问题,所以继续向上
					cur = gfather;
				}
				//如果叔叔节点不存在或者叔叔结点是黑色的
				else {
					//判断是否需要双旋,双旋条件:父亲是祖父的右节点,当前节点是父亲的左结点
					if (parent->_left == cur) {
						//父结点先进行右旋
						RotateR(parent);
						//此时cur、parent的指向是反的,所以需要交换
						swap(cur, parent);
					}
					//此时再对祖父节点进行左旋
					RotaleL(gfather);
					//再修改节点的颜色
					parent->_color = BLACK;
					gfather->_color = RED;
					break;
				}
			}
		}
		//调整完成后,在调整的过程中可能会改变根节点的颜色,所以改回黑色就好
		_header->_parent->_color = BLACK;
		//并且修改头结点的左右指针的指向
		_header->_left = LeftMost();
		_header->_right = RightMost();
		return make_pair(iterator(tmp), true);
	}
	//检测该红黑树是否满足红黑树的左右性质
	bool isBalance() {
		//如果是空树,这就是红黑树
		if (_header->_parent == nullptr)
			return true;
		//如果跟是红色,则不是红黑树
		if (_header->_parent->_color == RED)
			return false;
		//接下来判断是否有连续红色以及每条路径的黑色结点个数
		int count = 0, num = 0;
		Node* cur = _header->_parent;
		while (cur) {
			if (cur->_color == BLACK)
				num++;
			cur = cur->_left;
		}
		return _isBalance(_header->_parent, count, num);
	}
	//中序遍历
	void inorder() {
		_inorder(_header->_parent);
		cout << endl;
	}
private:
	//左旋操作
	void RotateL(Node* cur) {
		//获取要旋转节点的右孩子curR
		Node* curR = cur->_right;
		//获取右孩子curR的左孩子curRL
		Node* curRL = curR->_left;
		//开始建立指针连接
		cur->_right = curRL;
		curR->_left = cur;
		if (curRL)
			curRL->_parent = cur;
		//如果要旋转结点是根结点,特殊处理
		if (cur == _header->_parent) {
			_header->_parent = curR;
			curR->_parent = _header;
		}
		//不是根节点则正常处理
		else {
			Node* node = cur->_parent;
			curR->_parent = node;
			if (node->_left == cur)
				node->_left = curR;
			else
				node->_right = curR;
		}
		cur->_parent = curR;
	}
	//右旋操作
	void RotateR(Node* cur) {
		//获取要旋转节点的左孩子curL
		Node* curL = cur->_left;
		//获取左孩子curL的右孩子curLR
		Node* curLR = curL->_right;
		//开始建立指针连接
		cur->_left = curLR;
		curL->_right = cur;
		if (curLR)
			curLR->_parent = cur;
		//如果要旋转结点是根结点,特殊处理
		if (cur == _header->_parent) {
			_header->_parent = curL;
			curL->_parent = _header;
		}
		//不是根节点则正常处理
		else {
			Node* node = cur->_parent;
			curL->_parent = node;
			if (node->_left == cur)
				node->_left = curL;
			else
				node->_right = curL;
		}
		cur->_parent = curL;
	}
	//获取最左节点
	Node* LeftMost() {
		Node* cur = _header->_parent;
		while (cur && cur->_left)
			cur = cur->_left;
		return cur;
	}
	//获取最右节点
	Node* RightMost() {
		Node* cur = _header->_parent;
		while (cur && cur->_right)
			cur = cur->_right;
		return cur;
	}
	//递归判断是否为红黑树
	bool _isBalance(Node* node, int count, int num) {
		//当走到空节点时,判断黑色结点个数是否与确定的一样
		if (node == nullptr)
			return count == num;
		//判断是否出现连续的红色结点
		if (node->_parent && node->_color == RED && node->_parent->_color == RED)
			return false;
		//计数一条路径上黑色节点的个数
		if (node->_color == BLACK)
			count++;
		//同时判断左右子树是否也满足
		return _isBalance(node->_left, count, num)
			&& _isBalance(node->_right, count, num);
	}
	//中序遍历
	void _inorder(Node* root) {
		if (root) {
			_inorder(root->_left);
			cout << root->_val << ' ';
			_inorder(root->_right);
		}
	}
	Node* _header;
};
int main() {
	
	return 0;
}

mapset的简单实现

代码
//利用红黑树来简单实现map
template<class K, class V>
class Map {
	struct keyofval {
		const K& operator()(const pair<K, V>& val) {
			return val.first;
		}
	};
	RBTree<K, pair<K, V>, keyofval> rbt;
public:
	//因为这是泛型参数未确定的类的迭代器,所以编译器识别不了,因此需要加上一个typename,告诉编译器这个类的泛型参数可以延迟确定,所以这个迭代器可以使用
	typedef typename RBTree<K, pair<K, V>, keyofval>::iterator iterator;
	pair<iterator, bool> insert(const pair<K, V>& kv) {
		return rbt.insert(kv);
	}
	//Map的迭代器
	iterator begin() {
		return rbt.begin();
	}
	iterator end() {
		return rbt.end();
	}
	iterator rbegin() {
		return rbt.rbegin();
	}
	iterator rend() {
		rbt.rend();
	}
	V& operator[](const K& key) {
		//方括号的作用是:先插入一个默认值的键值对,成功与失败都返回一个迭代器,该迭代器指向了键值为key的pair键值对结点,然后返回该键值对的second的引用值,这样就可以对其进行修改了
		pair<iterator, bool> ret = rbt.insert(make_pair(key, V()));
		return ret.first->second;
	}
};

//利用红黑树来简单实现set
template<class V>
class Set {
	struct keyofval {
		const V& operator()(const V& key) {
			return key;
		}
	};
	RBTree<V, V, keyofval> rbt;
public:
	//因为这是泛型参数未确定的类的迭代器,所以编译器识别不了,因此需要加上一个typename,告诉编译器这个类的泛型参数可以延迟确定,所以这个迭代器可以使用
	typedef typename RBTree<V, V, keyofval>::iterator iterator;
	pair<iterator, bool> insert(const V& val) {
		return rbt.insert(val);
	}
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值