set和map

news2024/10/6 12:31:30

set和map

  • 关联式容器
  • 键值对
  • 树状结构关联式容器
    • set
      • 介绍
      • 使用
    • multiset
      • 介绍
      • 使用
    • map
      • 介绍
      • 使用
    • multimap
      • 介绍
      • 使用
  • 底层容器
    • AVL树
      • 概念
      • 操作
      • 节点定义
      • 插入
      • 旋转
    • 红黑树(RBTree)
      • 概念
      • 节点的设计
      • 迭代器的设计
      • 结构
      • 插入
    • 红黑树模拟实现`set`与`map`
      • 模拟实现map
      • 模拟实现set

关联式容器

在之前的学习中,所学习的容器类型都是序列式容器,其底层是线性数据结构存储的是元素本身

本章所学习的是另一种容器关联式容器,关联就意味着元素与元素之间存在着某种关联;存储的是数据是 <key,value>结构的键值对,接下来学习什么是键值对

键值对

键值对用来表示具有一一对应关系的一种结构,此结构中一般只包括两个成员变量 keyvaluekey代表键值, value代表与 key对应的信息;通过结构体pair进行定义

在这里插入图片描述
在这里插入图片描述

template<class T1,class T2>
struct pair
{
	typedef T1 first_type;
	typedef T2 second_type;

	T1 first;
	T2 second;

	pair()
		:first(T1())
		,second(T2())
	{}

	pair(const T1&a,const T2&b)
		:first(a)
		,second(b)
	{}
};

一般在进行对pair赋值并插入时,使用其另一种形式make_pair
在这里插入图片描述

树状结构关联式容器

根据应用场景的不同,STL实现了两种不同结构的管理式容器:树形结构和哈希结构;树形结构的关联式容器主要有四种: set, multiset, map, multimap。这四种容器都是以平衡搜索树作为底层结构来实现的。接下来对这些容器一一介绍

set

介绍

  1. set是按照一定次序存储元素的
  2. 在set中,只存放实值value,但在底层实际存放的是键值对<value,value>;插入元素时只需要插入实值value,不需要构造键值对
  3. 元素不可以重复,且不允许修改
  4. 元素默认按照小于进行比较
  5. 底层由二叉搜索树实现

使用

模板参数列表

在这里插入图片描述

  1. T:set中存放的元素的类型是T,既是key也是value
  2. Compare:set中元素默认按照小于进行比较
int main()
{
	set<int>myset;
	myset.insert(5);
	myset.insert(5);
	myset.insert(3);
	myset.insert(4);
	myset.insert(1);
	myset.insert(0);
	myset.insert(0);
	for (auto e : myset)
	{
		cout << e << " ";
	}
	cout << endl;
	auto pos = find(myset.begin(), myset.end(), 3);
	myset.erase(pos);
	for (auto e : myset)
	{
		cout << e << " ";
	}
	return 0;
}

在这里插入图片描述

通过运行结果可以发现, set会自动对元素进行排序+去重

multiset

介绍

在这里插入图片描述

  1. multiset是按照特定顺序存储元素的容器,元素可以重复
  2. 其余的与set一致

使用

int main()
{
	multiset<int>myset;
	myset.insert(5);
	myset.insert(5);
	myset.insert(3);
	myset.insert(4);
	myset.insert(1);
	myset.insert(0);
	myset.insert(0);
	for (auto e : myset)
	{
		cout << e << " ";
	}
	cout << endl;
	auto pos = find(myset.begin(), myset.end(), 3);
	myset.erase(pos);
	for (auto e : myset)
	{
		cout << e << " ";
	}
	return 0;
}

在这里插入图片描述

通过结果可以发现,相比于set,multiset只是进行简单的排序,并不会进行去重操作

map

介绍

  1. 关联式容器,按照 key的比较次序来存储由键值 key和值 value组成的键值对,通过成员类型 value_type,称为 pair
typedef pair<const key,T>value_type;
  1. 键值 key用于排序和标识元素;值 value存储与键值关联的内容;两者的类型可能不同
  2. 在内部,map中的元素总是按照键值key进行比较排序的,默认是按照小于进行排序的
  3. map中存放的元素是键值对pair<key,value>
  4. 支持下标访问,即可以通过键值key访问与之对应的value

使用

模板参数说明
在这里插入图片描述

  1. Key:key的类型
  2. T:value的类型
  3. Compare:比较器,通过key进行比较
int main()
{
	map<string, string>dict;
	dict.insert(make_pair("east", "东"));
	dict.insert(make_pair("west", "西"));
	dict.insert(make_pair("south", "南"));
	dict.insert(make_pair("north", "北"));
	for (const auto& e : dict)
	{
		cout << e.first << " " << e.second << endl;
	}
	return 0;
}

在这里插入图片描述

multimap

介绍

在这里插入图片描述

  1. multimap是按照特定顺序存储键值对pair<key,value>的序列式容器,元素可以重复
  2. 其余的与map一致

使用

统计球的次数

int main()
{
	string arr[] = { "篮球","羽毛球","乒乓球","羽毛球","乒乓球","羽毛球""羽毛球","乒乓球" };
	multimap<string, int>coutmap;
	for (auto& e : arr)
	{
		auto it = coutmap.find(e);
		if (it == coutmap.end())
		{
			coutmap.insert(make_pair(e, 1));
		}
		else
		{
			it->second++;
		}
	}
	for (const auto& e : coutmap)
	{
		cout << e.first << " " << e.second << endl;
	}
	return 0;
}

在这里插入图片描述

这里还有另一种方式进行统计,需要使用operator[],使用之前,先介绍其原理,做到知其然知其所以然

在这里插入图片描述

下标访问返回值:

(*((this->insert(make_pair(k,mapped_type()))).first)).second

逐步进行分析:

  1. make_pair(k,mapped_type()),根据键值k和实值类型相同的临时对象mapped_type()创建一个元素
  2. insert(make_pair(k,mapped_type())),将该元素插入到map中去,这里还需要了解到插入操作会返回一个pair,第一个元素是迭代器,指向插入的新元素或者键值重复的旧元素;第二个元素标识着插入的成功与否
pair<iterator,bool> insert (const value_type& val);
  1. (this->insert(make_pair(k,mapped_type()))).first,取得插入返回 pair中的第一个元素,即迭代器,指向插入的元素
  2. (*((this->insert(make_pair(k,mapped_type()))).first)).second,对迭代器进行解引用,获得一个 map元素,由键值 key和实值 value组成的 pair取得其第二个元素,也就是实值

对上面的代码进行修改

int main()
{
	string arr[] = { "篮球","羽毛球","乒乓球","羽毛球","乒乓球","羽毛球""羽毛球","乒乓球" };
	map<string, int>coutmap;
	for (auto& e : arr)
	{
		coutmap[e]++;
	}

	for (const auto& e : coutmap)
	{
		cout << e.first << " " << e.second << endl;
	}
	return 0;
}

在这里插入图片描述

底层容器

前面的set/multiset/map/multimap有个共同特点:底层都是通过二叉搜索树来实现的,二叉搜索树本身也存在着缺陷,如果插入值不够随机,或者经过某些插入或删除操作导致二叉搜索树失去平衡,造成搜寻效率低的情况,因此set/map的底层结构是对二叉搜索树进行了平衡处理的平衡树来实现的

在这里插入图片描述

AVL树

概念

加上了平衡条件的二叉搜索树,要求任何节点的左右子树高度差最多是1,可以降低树的高度,从而减少平均搜索长度

一颗AVL树或者空树,具有以下性质

  1. 它的左右子树都是AVL树
  2. 左右子树的高度差的绝对值不超多1(-1/0/1)

在这里插入图片描述

操作

节点定义

template<class K,class V>
struct AVLTreenode
{
	pair<K, V> _kv;
	AVLTreenode<K, V>* _left;
	AVLTreenode<K, V>* _right;
	AVLTreenode<K, V>* _parent;
	int _bf;//平衡因子
	AVLTreenode(const pair<K, V>& kv)
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}
};

这里引进了一个新的概念平衡因子,简单来说就是:右子树的高度减去左子树的高度得到的结果就是该节点的平衡因子

插入

AVL树在插入元素方面与二叉搜索树大致相同,除了需要对平衡因子进行处理之外;所以插入的过程分为两步:1.按照二叉搜索树的方式插入新节点2.调节平衡因子

插入新节点

bool insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new node(kv);
		return true;
	}
	node* parent = nullptr;
	node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if(cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->left;	
		}
		else
		{
			return false;
		}
	}
	cur = new node(kv);
	if (parent->_kv.first > kv.first)
	{
		parent->_right = cur;
		cur->_parent = parent;
	}
	else
	{
		parent->_left = cur;
		cur->_parent = parent;
	}
	//更新平衡因子
}

更新平衡因子

  1. 插入节点在右,parent->_bf++
  2. 插入节点在左,parent->_bf--
if (cur == parent->_left)
{
	parent->_bf--;
}
else
{
	parent->_bf++;
}

父节点的平衡因子更新过后,是否需要继续向上更新的依据是:子树的高度是否发生变化

  1. parent->_bf==0,说明插入之前父节点就空缺了左右节点其中一个parent->_bf==-1/parent->_bf==1,而插入之后刚好补全了空缺;也就是说子树并没有发生变化,不需要继续向上更新
if (parent->_bf == 0)
{
	break;
}
  1. parent->_bf==1/parent->_bf==-1,说明插入之前左右子树的高度一样,插入之后其中一边变得更高;由于子树的高度发生了变化,所以需要向上更新
else if (parent->_bf == 1 || parent->_bf == -1)
{
	cur = parent;
	parent = parent->_parent;
}
  1. parent->_bf==2/parent->_bf==-2,说明插入之前左右子树就已经一边高一边低;插入之后平衡已经被破坏,需要就地处理->旋转

旋转

旋转的核心思想:
使这颗子树的高度差不超过1;旋转过程中继续保持它是二叉搜索树;更新子节点的平衡因子;使子树的高度与插入之前保持一致

根据父节点的平衡因子和插入节点的平衡因子旋转的情景大志分为4种:

  1. parent->_bf==2&&cur->_bf==1

在这里插入图片描述

旋转的具体操作:将节点6的左节点调整到节点5的右节点上;节点5变成节点6的左节点;节点6作为根指向ppnode

//左旋
void RotateL(node* parent)
{
	node* subR = parent->_right;
	node* subRL = subR->_left;
	parent->_right = subRL;
	if (subRL)
	{
		subRL->_parent = parent;
	}
	node* ppnode = parent->_parent;
	subR->_left = parent;
	parent->_parent = subR;
	if (ppnode == nullptr)
	{
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{
		if (ppnode->_left = parent)
		{
			ppnode->_left = subR;
		}
		else
		{
			ppnode->_right = subR;
		}
		subR->_parent = ppnode;
	}
	parent->_bf = subR->_bf = 0;
}
  1. parent->_bf == -2 && cur->_bf == -1

在这里插入图片描述

旋转具体操作:将节点3的右节点调整到节点6的左节点上;节点6变成节点3的右节点;节点3作为根节点指向ppnode

//右旋
void RotateR(node* parent)
{
	node* subL = parent->_left;
	node* subLR = subL->_right;
	parent->_left = subLR;
	if (subLR)
	{
		subLR->parent = parent;
	}
	node* ppnode = parent->_parent;
	subL->_right = parent;
	parent->_parent = subL;
	if (ppnode == nullptr)
	{
		_root = subL;
		_root->_parent = nullptr;
	}
	else
	{
		if (ppnode->_left == parent)
		{
			ppnode->_left = subL;
		}
		else
		{
			ppnode->_right = subL;
		}
		subL->_parent = ppnode;
	}
	subL->_bf = parent->_bf = 0;
}
  1. parent->_bf == -2 && cur->_bf == 1

在这里插入图片描述

旋转操作:以节点3为轴点,进行左单旋,将节点6的左节点调整到节点3的右节点上;节点3变成节点6的左节点,节点6变成节点9的左节点;以节点9为轴点,进行右单旋,将节点6的右节点调整到节点9的左节点上;节点9变成节点6的右节点;节点6变成根节点指向ppnode

//左右双旋
void RotateLR(node* parent)
{
	node* subL = parent->_left;
	node* subLR = subL->_right;
	int bf = subLR->_bf;
	RotateL(parent->_left);
	RotateR(parent);
	//subLR左子树新增节点
	if (bf == -1)
	{
		subL->_bf = 0;
		parent->_bf = 1;
		subLR->_bf = 0;
	}//subLR右子树新增节点
	else if (bf == 1)
	{
		parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}//subLR自己是新增节点
	else if (bf == 0)
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else
	{
		assert(false);
	}
}
  1. parent->_bf == 2 && cur->_bf == -1

在这里插入图片描述

旋转操作:以节点9为轴点,进行右单旋,将节点6的右节点调整到节点9的左节点上;节点9变成节点6的右节点,节点6变成节点3的右节点;以节点3为轴点进行左单旋,将节点6的左节点调整为节点3的右节点;节点3变成节点6的左节点,节点6变成根节点指向ppnode

//右左双旋
void RotateRL(node* parent)
{
	node* subR = parent->_right;
	node* subRL = subR->_left;
	int bf = subRL->_bf;
	RotateR(parent->_right);
	RotateL(parent);
    //subRL右子树新增节点
	if (bf == 1)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = -1;
	}
	//subRL右子树新增节点
	else if (bf == -1)
	{
		subR->_bf = 1;
		subRL->_bf = 0;
		parent->_bf = -1;
	}
	//subRL本身就是新增节点
	else if (bf == 0)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

红黑树(RBTree)

概念

AVL树之外,另一个颇具历史被广泛运用的平衡二叉树就是红黑树RBTree,所谓红黑树,不仅是一个二叉搜索树,而且必须满足以下规则

  1. 每个节点不是红色就是黑色
  2. 根节点为黑色
  3. 如果节点为红,其子节点必须为黑(注意,这里并不没有说明不可以是连续的黑色)
  4. 任意节点至树尾端的路劲,所含有的黑节点数必须相同

最长路径不超过最短路径的二倍
最长路径:黑红相间的路径
最短路径:全为黑色

在这里插入图片描述

根据规则4,新增节点必须是红色

节点的设计

红黑树有红黑两种颜色,并且拥有左右子节点,节点的设计如下

enum Color
{
	RED,
	BLACK,
};
template<class K,class V>
struct RBTreenode
{
	pair<K, V> _kv;
	RBTreenode<K, V>* _left;
	RBTreenode<K, V>* _right;
	RBTreenode<K, V>* _parent;

	Color _col;

	RBTreenode(const pair<K, V>& kv)
		:_kv(kv)
		,_col(RED)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}
};

迭代器的设计

红黑树的迭代器属于双向迭代器,各种操作都类似于list,较为特殊的就是++--,这两个操作完全依据红黑树的节点的排列法则

先处理++,根据红黑树的中序遍历:左子树-根-右子树
如果右子树不为空,可直接到右子树中寻找键值最小的节点便是根节点++之后的节点

在这里插入图片描述

如果右子树不存在,并且此时节点还是父节点的右节点,向上找到的祖先便是++之后的节点;如果此时节点是父节点的左节点,那么父节点便是++之后的节点

在这里插入图片描述

--操作于此相同

这里还需要注意一点的是红黑树的迭代器中存在着构造函数
当普通迭代器调用时,权限缩小,此时是拷贝构造;当const迭代器调用时,此时是构造函数

_RBTreeIterator(const iterator& s)
	:_node(s._node)
{}

迭代器的完整代码如下

template<class T>
struct _RBTreeIterator
{
	typedef RBTreenode<T> node;
	typedef _RBTreeIterator<T, Ref, Ptr> Self;
	typedef _RBTreeIterator<T, T&, T*> iterator;
	node* _node;

	_RBTreeIterator(node* node)
		:_node(node)
	{}

	_RBTreeIterator(const iterator& s)
		:_node(s._node)
	{}

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

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

	Self& operator++()
	{
		if (_node->_right)
		{
			node* min = _node->_right;
			while (min->_left)
			{
				min = min->_left;
			}

			_node = min;
		}
		else
		{
			node* cur = _node;
			node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	Self& operator--()
	{
		if (_node->_left)
		{
			node* max = _node->_left;
			while (max->_right)
			{
				max = max->_right;
			}

			_node = max;
		}
		else
		{
			node* cur = _node;
			node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

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

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

结构

红黑树本质是二叉搜索树,每个节点存储的数据是键值对pair<key,value>,键值key唯一标识节点不可修改,实质value保存信息可以修改;

在这里插入图片描述
STL规定,begin()end()代表一段前闭后开的区间,对红黑树进行中序遍历之后,可以得到有序序列,因此:begin()可以放在红黑树中最小节点的位置,end()可以放在最大节点的下一个位置(这里直接设置为空指针)

begin()

iterator begin()
{
	node* left = _root;
	while (left && left->_left)
	{
		left = left->_left;
	}
	return iterator(left);
}

end()

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

插入

插入操作的返回值是键值对pair

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

红黑树是在二叉树搜索树的基础上加上其平衡条件,因此红黑树的插入可分两步:

  1. 按照二叉搜索树的规则插入新节点

  2. 检测新节点插入之后,红黑树的性质是否被破坏
    新增节点必须是红色的,如果双亲结点的颜色是黑色,则没有违反红黑树规则,不需要调整;如果双亲结点是红色,此时就违反了双亲结点的规则3不能有连续的红色节点,需要进行调整;根据叔叔节点的情况,调整的情景也分为3种:
    约定:新增节点为cur,父节点为p,祖父节点为g,叔叔节点为u

cur为红,p为红,g为黑,u存在且为红

在这里插入图片描述

调整操作:将父节点 p和叔叔节点 u改为黑色,祖父节点 g改为红色;将祖父节点 u作为新增节点,继续向上调整

cur为红,p为红,g为黑,u不存在/存在且为黑

在这里插入图片描述

调整操作:如果父节点 p是祖父节点 g的左节点,新增节点cur是父节点p的左节点,进行左单旋;相反,父节点 p是祖父节点 g的右节点,新增节点cur是父节点p的右节点,进行左单旋

以父节点 p为轴点进行左单旋;将父节点 p改为黑色,祖父节点 g改为红色;父节点 p作为新增节点继续向上调整

cur为红,p为红,g为黑,u不存在/存在且为黑

在这里插入图片描述

调整操作:如果父节点p是祖父节点g的左节点,新增节点cur是父节点p的右节点,则以父节点p为轴心进行左单旋;如果父节点p是祖父节点g的右节点,新增节点cur是父节点p的左节点,则以父节点p为轴心进行右单旋
以父节点p为轴心进行左单旋,转换成情景二进行调整

pair<iterator, bool>insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new node(data);
		_root->_col = BLACK;
		return make_pair(iterator(_root), true);
	}
	
	node* parent = nullptr;
	node*newnode=node(cur);
	node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return make_pair(iterator(cur),false);
		}
	}

	cur = new node(kv);
	node* newnode = cur;
	cur->_col = RED;
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
		cur->_parent = parent;
	}
	else
	{
		parent->_left = cur;
		cur->_parent = parent;
	}
	while (parent && parent->_col == RED)
	{
		node* grandfather = parent->_parent;
		if (parent == grandfather->_left)
		{
			node* uncle = grandfather->_right;
			//情景1,叔叔存在且为红
			if (uncle && uncle->_col == RED)
			{
				parent->_col == uncle->_col = BLACK;
				grandfather->_col = RED;
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				if (cur == parent->_left)
				{
					//情景2
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//情景3
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
		else
		{
			node* uncle = grandfather->_left;
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
		
	}
	_root->_col = BLACK;
	return make_pair(iterator(newnode), true);
}

红黑树模拟实现setmap

通过红黑树来模拟实现这两种容器,首先要解决的一个问题就是:set的节点中存储的数据是key(也是value),而map中存储的是键值对pair<key,value>;所以在红黑树中先要修改节点中存储数据的类型,使得当模板根据不同的类型,实现不同的容器

在这里插入图片描述

节点改造如下

enum Color
{
	RED,
	BLACK,
};

template<class T>
struct RBTreenode
{
	T _data;//数据类型
	RBTreenode<T>* _left;
	RBTreenode<T>* _right;
	RBTreenode<T>* _parent;
	Color _col;

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

节点修改之后,还会出现一个问题,在进行插入操作时,会将待插入节点的键值与根节点的键值进行比较,但是由于此时节点中的数据类型是data,如果是模拟实现set那么不会出现任何问题;如果是模拟实现map,编译器也不清楚data是表示键值还是实值,而且键值对pair中的比较大小的函数,并不是只比较键值大小

在这里插入图片描述

所以这时便需要想办法将键值对中的键值取出来;解决办法:在红黑树模板中加上一个模板参数KeyofT,创建仿函数;当实现setdata的是键值也是实值,当实现map时取出键值对pair中的键值即可

改造红黑树如下

//map->RBTree<K,pair<const K,V>,MapKeyofT> _t
//set->RBTree<K,K,SetKeyofT> _t
template<class K,class T,class KeyofT>
class RBTree
{
	typedef _RBTreenode<T> node;
public:
	typedef _RBTreeIterator<T, T&, T*>iterator;
	typedef _RBTreeIterator<T, const T&, const T*>const_iterator;
	~RBTree()
	{
	......
	}

	iterator begin()
	{
		node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}
		return iterator(left);
	}

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

	const_iterator begin() const
	{
		node* left = _root;
		while (left && left->_left)
		{
			left = left->left;
		}
		return const_iterator(left);
	}

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

	pair<iterator, bool>insert(const T& data)
	{
		......
		return make_pair(iterator(newnode), true);
	}
	
private:
	node* _node = nullptr;
};

模拟实现map

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

	iterator begin()
	{
		return _t.begin();
	}
	iterator end()
	{
		return _t.end();
	}
	const_iterator begin()
	{
		return _t.begin();
	}
	const_iterator end()
	{
		return _t.end();
	}
	pair<iterator, bool>insert(const pair<const K, V>& kv)
	{
		return _t.insert(kv);
	}
	V& operator[](const K& key)
	{
		pair<iterator, bool>ret = insert(make_pair(key, V()));
		return ret.first->second;
	}
private:
	RBTree<K, pair<const T, V>, MapkeyofT> _t;
};

模拟实现set

template<class K>
class set
{
	struct SetkeyofT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};

public:
	typedef typename RBTree<K, K, SetkeyofT>::const_iterator iterator;
	typedef typename RBTree<K, K, SetkeyofT>::const_iterator const_iterator;

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

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

	pair<iterator, bool>insert(const K& key)
	{
		pair<typename RBTree<K, K, SetkeyofT>::iterator, bool>ret = _t.insert();
		return pair<iterator, bool>(ret.first, ret.second);
	}

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

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

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

相关文章

【Java 数据结构】单向链表和双向链表的实现 (LinkedList)

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

android studio 页面布局(2)

<?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.android.com/apk/res-auto"xmlns:tools"http://schemas.android.com/too…

【数据挖掘与商务智能决策】第九章 随机森林模型

9.1.3 随机森林模型的代码实现 和决策树模型一样&#xff0c;随机森林模型既可以做分类分析&#xff0c;也可以做回归分析。 分别对应的模型为随机森林分类模型&#xff08;RandomForestClassifier&#xff09;及随机森林回归模型&#xff08;RandomForestRegressor&#xff…

Vue.js 2.0 组件

什么是组件&#xff1f; 组件&#xff08;Component&#xff09;是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素&#xff0c;封装可重用的代码。在较高层面上&#xff0c;组件是自定义元素&#xff0c; Vue.js 的编译器为它添加特殊功能。在有些情况下&#xff0c;组件也…

《花雕学AI》19:比较ChatGPT与新Bing在文章润色方面的应用优势与测试案例

引言&#xff1a; 文章润色是指对已经写好的文章进行修改、优化或完善的过程&#xff0c;以提高文章的质量和效果。文章润色涉及到多方面的内容&#xff0c;如语言表达、逻辑结构、文献引用、格式规范等。文章润色对于提升写作水平、提高论文发表率、增加学术影响力等都有重要意…

JavaScript【趣味】做一个网页版2048

文章目录&#x1f31f;前言&#x1f31f;先看效果&#xff08;粉丝特权哈哈&#xff09;&#x1f31f;代码实现&#x1f31f;页面布局 【index.html】&#x1f31f;样式文件【2048.css】&#x1f31f;index.html 里用到的JS文件&#x1f31f;jquery.min.js&#x1f31f;util.js…

300元左右的蓝牙耳机哪个好?300左右音质最好的蓝牙耳机

无线耳机是人们日常生活中必不可少的设备&#xff0c;无论是听音乐化石看电影都能获得身临其境的感觉&#xff0c;由于科技真在发展中&#xff0c;不断地的发生变化&#xff0c;百元价位就可以感受到不错的音色&#xff0c;下面小编整理了几款300左右音质表现不错的蓝牙耳机。 …

Linux 、Android将在汽车舞台上开战

导读在 CES 2017 上&#xff0c;AGL 宣布&#xff0c;Mercedes-Benz 的母公司 Daimler 正式加入。这是第十家汽车制造商加入 AGL&#xff0c;也是第一家德国公司加入 AGL。AGL&#xff08;Automotive Grade Linux&#xff09;&#xff0c;是 Linux 基金会的一个相互协作的开源组…

mallox勒索病毒数据恢复|金蝶、用友、管家婆、OA、速达、ERP等软件数据库恢复

目录 前言&#xff1a; 一、mallox勒索病毒及xollam勒索病毒的特点 二、mallox勒索病毒及xollam勒索病毒的影响 三、mallox勒索病毒及xollam勒索病毒数据恢复服务 四、mallox勒索病毒及xollam勒索病毒加密数据库恢复案例 五、以下是预防mallox勒索病毒及xollam勒索病毒安全…

解读CANDT测试项-采样点测试

原标题&#xff1a;解读CANDT测试项-采样点测试 一、为什么要进行采样点测试&#xff1f; 本文引用地址&#xff1a;http://www.eepw.com.cn/article/202004/411611.htm 为了保证有效的通信&#xff0c;对于一个只有两个节点的CAN网络&#xff0c;其两边距离不超过最大的传输…

day12 共享内存(内存映射的使用、注意事项、进程间通信、systemV共享内存)

内存映射的基本使用 概念&#xff1a; 功能共享内存可以通过mmap&#xff08;&#xff09;映射普通文件。 是一个磁盘文件与内存中的一个缓冲区相映射&#xff0c;进程可以像访问普通内存一样对文件进行访问&#xff0c;不必在调用read 、write。 mmap&#xff08;&#xf…

ChatGPT 与 MindShow 一分钟搞定一个PPT

前言 PPT制作是商务、教育和各种场合演讲的重要组成部分。然而&#xff0c;很多人会花费大量时间和精力在内容生成和视觉设计方面。为了解决这个问题&#xff0c;我们可以利用两个强大的工具——ChatGPT和MindShow&#xff0c;来提高制作PPT的效率。 一、ChatGPT 与 MindShow…

JUC-01 线程的创建和状态转换

本次我们主要讲三个问题 线程是什么&#xff1f;线程有哪些状态&#xff1f;各状态间的转换了解吗&#xff1f;创建线程的3种方法你都了解吗&#xff1f; 1. 线程是什么&#xff1f;&#xff08;了解即可&#xff09; 进程&#xff1a; 进程是一个具有一定独立功能的程序在一…

四次挥手刨根问底19问详解,全网最全

1.请描述一下TCP连接的四次挥手过程&#xff1f; 回答&#xff1a;TCP连接的四次挥手过程包括以下步骤&#xff1a; 步骤1&#xff1a;客户端向服务器端发送一个FIN报文段&#xff0c;请求关闭连接。 步骤2&#xff1a;服务器端收到FIN报文段后&#xff0c;向客户端发送一个…

python列表,元组和字典

1、python列表 1.1.列表的定义 list是一种有序的集合、基于 链表实现,name[ ] ,全局定义:list2list([ ])。 1.2下标索引 python不仅有负索引也有正索引。正索引从0开始,负索引从-1开始。这两个可以混用,但指向还是那个位置 a[0]a[-9]//length为10的数组a1.3列表的切片 列表可…

navicat如何使用orcale(详细步骤)

目录前言操作1.连接数据库2.建库问题总结前言 看过我昨天文章的兄弟姐妹都知道最近接手另一个国企项目&#xff0c;数据库用的是orcale。实话实说&#xff0c;也有快三年没用过orcale数据库了。 这期间问题不断&#xff0c;因为orcale日渐消沉&#xff0c;网上资料也是真真假…

UE4 回放系统升级到UE5之后的代码报错问题解决

关键词&#xff1a; UE4 回放系统 升级 UE5 报错 DemoNetDriver GetDemoCurrentTime GetDemoTotalTime 背景 照着网上教的UE4的回放系统&#xff0c;也叫重播系统&#xff0c;英文Replay。做完了&#xff0c;测试运行正常&#xff0c;可升级到UE5却报了一堆 WorldSetting 和 …

(20230417)最大数合并区间重新排列单词间的空格 按奇偶排序数组 II 数组形式的整数加法

最大数&#xff08;回顾等级&#xff1a;值得&#xff0c;已达最优解&#xff09; 来源&#xff1a;自己LeetCode刷题 usa long cmp(const void* e1, const void* e2) {int* p1(int*)e1;int* p2(int*)e2;long n110;long n210;while(*p1>n1){n1*10;}while(*p2>n2){n2*1…

利用AOP实现统一功能处理

目录 一、实现用户登录校验 实现自定义拦截器 将自定义的拦截器添加到框架的配置中&#xff0c;并且设置拦截的规则 二、实现统一异常处理 三、实现统一数据格式封装 一、实现用户登录校验 在之前的项目中&#xff0c;在需要验证用户登录的部分&#xff0c;每次都需要利…

RK3568平台开发系列讲解(环境篇)使用USB线缆升级固件

🚀返回专栏总目录 文章目录 一、进入升级模式1.1、硬件方式进入Loader模式1.2、软件方式进入Loader模式二、安装烧写工具2.1、Windows操作系统2.2、Linux操作系统沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍了如何将主机上的固件,通过USB数据线烧录到…