详解c++---map和set的封装

news2024/9/21 3:03:59

目录标题

  • 前言
  • 红黑树的基本代码
  • map和set的封装
  • 红黑树迭代器
  • 红黑树迭代器- -
  • begin和end函数
  • 代码测试
  • const迭代器
  • 方括号的实现

前言

通过之前的学习我们知道set容器中存储的数据是k,map容器中存储的数据是k和v,但是这两个容器底层都是通过红黑树来进行实现的,那根据我们正常的思维就是,红黑树分别给这两个容器来进行一个适配,也就是说给map实现一个kv版本的迭代器,给set实现一个k版本的迭代器,但是如果是这样实现的话肯定就十分的麻烦,会干很多相同的事情,而c++是有模板这个东西的那我们能不能通过模板使得两个容器虽然装的数据个数是不一样的,但是却可以通过一个模板的红黑树类来封装成map或者set呢?答案是可以,我们使用的标准库就是用的一个红黑树封装了map和set,那接下来我们就要一步一步的实现map和set的封装。

红黑树的基本代码

enum colour
{
	RED,
	BLACK,
};
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& _kv)
		:parent(nullptr)
		, right(nullptr)
		, left(nullptr)
		, kv(_kv)
		, _col(RED)
	{}
	RBTreeNode<K, V>* parent;
	RBTreeNode<K, V>* right;
	RBTreeNode<K, V>* left;
	pair<K, V> kv;
	colour _col;
};
template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	RBTree()
		:root(nullptr)
	{}
	bool insert(const pair<K, V>& _kv)
	{
		if (root == nullptr)
		{
			root = new Node(_kv);
			root->_col = BLACK;
			return true;
		}
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
			if (_cur->kv.first > _kv.first)
			{
				_parent = _cur;
				_cur = _cur->left;
			}
			else if (_cur->kv.first < _kv.first)
			{
				_parent = _cur;
				_cur = _cur->right;
			}
			else
			{
				return false;
			}
		}
		_cur = new Node(_kv);
		if (_kv.first > _parent->kv.first)
			//如果插入的数据比parent的数据大
		{
			_parent->right = _cur;
			_cur->parent = _parent;
		}
		else
			//如果插入的数据比parent的数据笑
		{
			_parent->left = _cur;
			_cur->parent = _parent;
		}
		while (_parent != nullptr && _parent->_col == RED)
		{
			Node* _grandparent = _parent->parent;
			if (_grandparent->left == _parent)
				//祖先节点的左边要调整
			{
				Node* uncle = _grandparent->right;//parent在左边所以uncle在右边
				//情况一
				if (uncle != nullptr && uncle->_col == RED)
				{
					uncle->_col = _parent->_col = BLACK;
					if (_grandparent->parent != nullptr)
					{
						_grandparent->_col = RED;
					}
					_cur = _grandparent;
					_parent = _grandparent->parent;
				}
				else
				{
					//情况二
					if (_cur == _parent->left)
					{
						RototalR(_grandparent);
						_parent->_col = BLACK;
						_grandparent->_col = RED;
					}
					else//情况三
					{
						RototalL(_parent);
						RototalR(_grandparent);
						_cur->_col = BLACK;
						_grandparent->_col = RED;
					}
					break;
				}

			}
			else
				//祖先节点的右边要调整
			{
				Node* uncle = _grandparent->left;//parent在右边所以uncle在左边
				//情况一
				if (uncle != nullptr && uncle->_col == RED)
				{
					uncle->_col = _parent->_col = BLACK;
					if (_grandparent->parent != nullptr)
					{
						_grandparent->_col = RED;
					}
					_cur = _grandparent;
					_parent = _grandparent->parent;
				}
				else
				{
					//情况二
					if (_cur == _parent->right)
					{
						RototalL(_grandparent);
						_parent->_col = BLACK;
						_grandparent->_col = RED;
					}
					else//情况三
					{
						RototalR(_parent);
						RototalL(_grandparent);
						_cur->_col = BLACK;
						_grandparent->_col = RED;
					}
					break;
				}
			}
		}
		return true;

	}
	void inorder()
	{
		_inorder(root);
	}
	bool find(const K& val)
	{
		Node* cur = root;
		while (cur)
		{
			if (cur->kv.first == val)
			{
				return true;
			}
			else if (cur->kv.first > val)
			{
				cur = cur->left;
			}
			else
			{
				cur = cur->right;
			}
		}
		return false;
	}
	bool check(Node* root, int BlackNums, int ref)
	{
		if (root == nullptr)
		{
			if (ref != BlackNums)
			{
				cout << "违反规则" << endl;
				return false;
			}
			return true;
		}
		if (root->_col == RED && root->parent->_col == RED)
		{
			cout << "出现了连续红色节点" << endl;
		}
		if (root->_col == BLACK)
		{
			BlackNums++;
		}
		return check(root->left, BlackNums, ref) && check(root->right, BlackNums, ref);
	}
	bool IsBalance()
	{
		if (root == nullptr)
		{
			return true;
		}
		if (root->_col != BLACK)
		{
			return false;
		}
		int ref = 0;
		Node* _left = root;
		while (_left)
		{
			if (_left->_col == BLACK)
			{
				ref++;
			}
			_left = _left->left;
		}
		return check(root, 0, ref);

	}
private:
	Node* root;
	void _inorder(const Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_inorder(root->left);
		cout << root->kv.first << " " << root->kv.second << endl;
		_inorder(root->right);
	}
	void RototalL(Node* _parent)
	{
		Node* subR = _parent->right;//右孩子的节点
		Node* subRL = subR->left;//右孩子的左节点
		Node* ppNode = _parent->parent;//祖父节点
		//把subRL放到_parent的右
		_parent->right = subRL;
		if (subRL)
		{
			//如果subRL不为空则修改父节点的指向
			subRL->parent = _parent;
		}
		//把_parent放到subR的左
		subR->left = _parent;
		//修改_parent的parent的指向
		_parent->parent = subR;
		if (ppNode)//如果祖父不为空,则要改变祖父的指向
		{
			if (ppNode->right == _parent)
			{
				ppNode->right = subR;
				subR->parent = ppNode;
			}
			else//如果_parent是祖父的左
			{
				ppNode->left = subR;
				subR->parent = ppNode;
			}
		}
		else//祖父为空节点说明当前调整的是根节点
		{
			root = subR;
			subR->parent = nullptr;
		}
	}
	//右旋转
	void RototalR(Node* _parent)
	{
		Node* subL = _parent->left;
		Node* subLR = subL->right;
		Node* ppNode = _parent->parent;
		_parent->left = subLR;
		if (subLR)
		{
			subLR->parent = _parent;
		}
		subL->right = _parent;
		_parent->parent = subL;
		if (ppNode != nullptr)
		{
			if (ppNode->right == _parent)
			{
				ppNode->right = subL;
				subL->parent = ppNode;
			}
			else
			{
				ppNode->left = subL;
				subL->parent = ppNode;
			}
		}
		else
		{
			root = subL;
			subL->parent = nullptr;
		}
	}
};

如果大家不知道红黑树的原理,或者红黑树不会实现的话可以看看我的上篇文章。

map和set的封装

我们知道set容器里面装的是一种元素k,map容器里面装的是两种元素k和v,根据前面的学习我们知道map是使用pair来装载的两种元素,那逻辑上讲map中存储的就是一种元素pair,那红黑树的模板可以是一种元素吗?那这里我们先一步一步的分析,如果红黑树的模板是一个元素的话map和set的封装应该是这个样子:

template<class K>
class set
{
public:

private:
	RBTree<K> tree;
};

template<class K,class V>
class map
{
public:

private:
	RBTree<pair<const K,V>> tree;

};

如果我传过来的是一个K的话,那么红黑树实例化的就是set,如果传过来的是一个pair的话,那么红黑树实例化的就是map,看上去好像很有道理但是标准库中好像不是这么干的,在标准库里面map用两个参数k和pair来进行实例化,set也是用两个参数来进行实例化但是这两个参数都是k,map和set的底层都是根据红黑树来进行封装,如果第二个参数是k那么实例化的就是set,如果第二个参数是pair则实例化的就是map,比如说下面的代码:

template<class K>
class set
{
public:

private:
	RBTree<K,K> tree;
};

#include"RBTree.h"
template<class K,class V>
class map
{
public:

private:
	RBTree<K,pair<const K,V>> tree;

};

那么这里就有个问题既然第二个模板参数就能够确定谁是map谁是set,那为什么还要传第一个模板参数呢?那为了搞清楚这个问题我们先来看看只有一个参数会出现什么问题,首先就是insert函数,这个函数的参数是这样的:

itertaor insert(const V& date)

如果当前的容器是set的话这里V的类型就为K,如果当前容器为map的话这里V的类型就为pair,而且平时我们使用insert函数传递参数时也是相对应的,往set中插入数据传递的就是K,往map中插入数据的话传递的就是pair,如果红黑树的模板只有一个参数的话这里也能应对的过来,那如果是find函数呢?不管是map容器还是find容器我们使用find函数时传递的都是K类型的数据,不存在说如果当前的容器是map的话我们就传递pair来进行查找对吧!所以如果红黑树只有一个参数的话那map容器中的find函数如何来声明呢?所以为了能够正常的封装出map和set这里的红黑树得有两个模板参数来表示数据的类型,当前红黑树既要封装map又要封装set所以我们还要对描述节点的结构体做出修改,之前结构体的形式是这样:

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& _kv)
		:parent(nullptr)
		, right(nullptr)
		, left(nullptr)
		, kv(_kv)
		, _col(RED)
	{}
	RBTreeNode<K, V>* parent;
	RBTreeNode<K, V>* right;
	RBTreeNode<K, V>* left;
	pair<K, V> kv;
	colour _col;
};

我们上面说红黑树的第二个参数就能确定当前的树封装的是map还是set,那么这里也是同样的道理该结构体也只需要一个参数就能够确定当前实例化的是map的节点还是set的节点,所以我们将模板的参数个数改成一个用来表述存储数据的类型,然后将pair变量修改成为模板类型创建出来的变量即可,比如说下面的代码:

template< class T>
struct RBTreeNode
{
	RBTreeNode(const T& _data)
		:parent(nullptr)
		, right(nullptr)
		, left(nullptr)
		, data(_data)
		, _col(RED)
	{}
	RBTreeNode<T>* parent;
	RBTreeNode<T>* right;
	RBTreeNode<T>* left;
	T data;
	colour _col;
};

但是这里就出现了一个新的问题:在insert函数和find函数的内部都会拿传来的参数进行比较,对于set来说这个比较是正常的可以直接拿k值进行比较那map呢?他可以直接拿内部的pair来进行比较吗?那么这里就有两个问题首先pair能不能使用操作符">" "<"来进行比较,其次这个比较的规则又是否符合我们的预期呢?对于第一个问题答案是可以使用操作符来进行比较,pair提供了对该操作符的重载比如说下面的图片:
在这里插入图片描述
既然可以进行比较那我们就来看看第二个问题这里重载的逻辑是否符合我们的预期呢?对于map我们希望他内部数据的比较规则是pair的第一个元素越大那么这个pair的值就越大跟第二个元素没有关系,那库中的重载是这么实现的吗?我们来看看下面的图片:
在这里插入图片描述
大家仔细看一下就能发现这里的实现跟我们想象的不一样,他把第二个元素也添加到pair的大小比较之中了,所以如果map中直接使用pair来进行比较的话他肯定是会出问题的,我们只希望使用pair的第一个元素来进行比较也就是使用K来进行比较,那这里如何来解决这个问题呢!答案是在红黑树的模板中添加一个仿函数,这个仿函数的功能就是专门获取容器内部数据的K,首先这个仿函数需要一个参数,对于set来说这个参数的类型就是K,对于map来说这个参数的类型就是pair,比如说下面的代码:

//set.h
	struct SetofKey
	{
		const K& operator()(const K& key)
		{
		}
	};
//map.h
	struct MapofKey
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			
		}
	};

对于set的仿函数传过来的数据就是key所以我们直接返回即可,对于map的仿函数传过来的数据是pair我们将pair中的第一个元素进行返回即可,那么这里的代码就如下:

//set.h
	struct SetofKey
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
//map.h
	struct MapofKey
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};

既然添加了仿函数那么红黑树在显示实例化的时候是不是也得做出修改啊,那么这里的代码就如下:

//set.h
template<class K>
class set
{
public:

private:
	struct SetofKey
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};

	RBTree<K,K, SetofKey> tree;

};
//map.h
template<class K,class V>
class map
{
public:

private:
	struct MapofKey
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};

	RBTree<K,pair<const K,V>,MapofKey> tree;
};

添加了仿函数那么红黑树的比较逻辑也得做出修改,之前的比较逻辑是这样的:

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K,V> Node;
	bool insert(const pair<K, V>& _kv)
	{
		//.....
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
			if (_cur->kv.first > _kv.first)
			{
				_parent = _cur;
				_cur = _cur->left;
			}
			else if (_cur->kv.first < _kv.first)
			{
				_parent = _cur;
				_cur = _cur->right;
			}
			else
			{
				return false;
			}
		}
		//.....
	}
}

有了仿函数之后就不能靠我们自己来获取数据中的K,而是使用仿函数创建出来的仿函数对象来获取节点中data的Key值,这里大家不要忘记修改insert函数的的参数类型和节点实例化的参数个数以及红黑树模板的参数个数,那么这里修改之后的代码就是这样:

template<class K, class T,class KeyofT>
class RBTree
{
public:
typedef RBTreeNode<T> Node;
	bool insert(const T& _data)
	{
		//....
		KeyofT kot;
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
			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 false;
			}
		}
		//....
	}
}

当然这里要修改的地方远不止上面这些地方,只要是用到使用pair的first进行比较的地方都得做出修改,因为篇幅问题这里不能给大家一一展示。那么修改之后map和set的insert函数和find函数就可以调用红黑树的find和insert函数来实现比如说下面的代码:

//map.h
template<class K, class V>
class map
{
public:
	bool insert(const pair<K,V>& data)
	{
		return tree.insert(data);
	}
	bool find(const K& key)
	{
		return tree.find(key);
	}
private:
	struct MapofKey
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};
	RBTree<K, pair<const K, V>, MapofKey> tree;
};
//set.h
template<class K>
class set
{
public:
	bool insert(const K& key)
	{
		return tree.insert(key);
	}
	bool find(const K& key)
	{
		return tree.find(key);
	}
private:
	struct SetofKey
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
	RBTree<K, K, SetofKey> tree;

红黑树迭代器

我们首先来看看库中的迭代器是如何实现的:
在这里插入图片描述

库中给了一个头结点用于指向树的根节点,header的left指针指向最左节点,所以迭代器的begin函数就是指向header的left,header的right指针指向的是最右节点,所以迭代器的end函数指向的就是header,那么这就是库中迭代器实现的方式,那么我们为了方便就不添加头节点,begin函数依然是返回最左节点,而end函数则是直接返回一个空指针,那么这里我们就可以实现一下这两个函数,根据前面list的经验我们知道这里的迭代器不可能是在红黑树类的内部来实现的,而是得单独创建一个类出来,因为我们当前要实现的是一个普通迭代器,所以这个类模板就只有一个参数用于表示当前迭代器只想的数据类型,因为我们要在这个类里面执行一些操作比如说找到最左节点或者下一步的节点等等,所以我们得在该类里面创建一个该节点类型的变量用于做一些操作,迭代器里面肯定存在++和–的操作符重载,这些函数的返回类型就是迭代器本身,所以为了方便后面的书写我们就对常用的类型进行重命名,因为该类中存在着成员变量所以我们还得实现一下该类的构造函数,那么这里的代码就如下:

template<class V>
struct _RBTreeIterator
{
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V> self;
	Node* _node;
	_RBTreeIterator( Node* node)
		:_node(node)
	{}
};

然后我们就可以实现几个简单的操作符重载函数,第一个就是解引用重载该函数直接返回当前节点里面的data数据的引用即可,第二个就是->操作符重载该函数的作用就是返回当前节点内部数据的地址,第三个就是!=操作符重载,这三个函数都十分的简单我们就不讲解大家直接看代码即可:

template<class V>
struct _RBTreeIterator
{
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V> self;
	Node* _node;
	_RBTreeIterator( Node* node)
		:_node(node)
	{}
	V& operator*()
	{
		return _node->data;
	}
	V* operator->()
	{
		return &_node->data;
	}
	bool operator !=(const self& s)
	{
		return _node != s._node;
	}
};

我们说搜索二叉树采用中序遍历得到的结果就是有序的,而中序遍历的顺序就是先访问左子树再访问根节点最后访问右子树,使用++就是让当前的节点往后走一步,当前停留的节点一定是某个子树的根节点,这个时候再往后走的话一定是来到右子树的最左节点,但是这里有个情况就是如果右节点不存在怎么办呢?对吧如果右节点的不存在就说明我们当前的子树已经访问完了得访问上层的子树,可是这里的子树是不是也得分情况讨论啊,如果当前的子树是左子树的话我们是不是还得访问上层的根啊,如果当前的子树是右子树的话是不是还得继续往上调整一直遇到一个当前节点是根节点的左边为止或者父节点为空为止,首先判断一下我的右子树是否为空,如果不为空的话就循环到右子树的最左子树,那么这里的代码就如下:

self& operator++()
{
	if (_node->right)
	{
		Node* mine = _node->right;
		while (mine->left)
		{
			mine = mine->left;
		}
		_node = mine;
	}
	else
	{
	}
}

如果右子树为空就说明当前子树已经遍历完了,这时需要往上走了,如果当前节点是父节点的左边的话我们就停止,如果当前节点是父节点的右边的话我们还得继续往上调整,所以这里我们得创建两个Node*指针一个名为parent用于指向父节点,一个名为cur指向当前的节点,每次循环我们就判断一下cur和parent的位置关系以及parent是否为空,循环体里面就是将parent的值赋值给cur,然后让parent指向parent的parent,最后将parent的值赋值给_node即可,那么这里的代码就如下:

	self& operator++()
	{
		if (_node->right)
		{
			Node* mine = _node->right;
			while (mine->left)
			{
				mine = mine->left;
			}
			_node = mine;
		}
		else
		{
			Node* parent = _node->parent;
			Node* cur = _node;
			while (parent != nullptr && parent->right == cur)
			{
				cur = parent;
				parent = parent->parent;
			}
			_node = parent;
		}
		return *this;
	}

红黑树迭代器- -

中序遍历的顺序是先访问左子树再访问根节点最后访问右子树,- - 是倒着走所以这里访问的节点顺序就是先访问右子树再访问根节点最后访问左子树,迭代器所在的位置一定是某个子树的根节点,当迭代器位于这个位置时说明该节点的右子树已经访问完了,接下来就要访问该节点的左子树又因为先要访问 右子树,所以对该迭代器- -就会来到左子树的最右节点,如果左子树为空的话这里就得分情况讨论,如果当前的节点是父节点的左的话就得继续往上调整,如果当前的节点是父节点的右的话这里就往上调整一次即可:

self& operator--()
{
	if (_node->left)//左边不为空
	{
		Node* mine = _node->left;
		while (mine->right)
		{
			mine = mine->right;
		}
		_node = mine;
	}	
	else//左边为空
	{
		Node* _parent = _node->parent;
		Node* cur = _node;
		while (_parent->left == cur && _parent != nullptr)
		{
			cur = _parent;
			_parent = _parent->parent;
		}
		_node = _parent;
	}
	return *this;
}

begin和end函数

红黑树的begin函数就是返回当前树的最左节点,begin函数在红黑树的类中实现,所以我们还得在红黑树的类中添加一个迭代器的类型,然后begin函数的返回类型是迭代器类型,那么这里的代码就如下:

iterator begin()
{
	Node* cur = root;
	while (cur->left)
	{
		cur = cur->left;
	}
	return iterator(cur);	
}

end函数就是直接用空指针构造一个迭代器返回即可:

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

红黑树的迭代器完成之后就可以用来封装map和set的迭代器,那么这里的代码就如下:

//set.h
template<class K>
class set
{
	struct SetofKey
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	typedef typename RBTree<K, K, SetofKey>::iterator iterator;
	iterator begin()
	{
		return tree.begin();
	}
	iterator end()
	{
		return tree.end();
	}
//....
private:
	RBTree<K, K, SetofKey> tree;
};
//map.h
template<class K, class V>
class map
{
	struct MapofKey
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::iterator iterator;
	iterator begin()
	{
		return tree.begin();
	}
	iterator end()
	{
		return tree.end();
	}
//....
private:
	RBTree<K, pair<const K, V>, MapofKey> tree;
};

我们在map和set中使用了RBTree中的类型iterator,但是使用之前我们并没有实例化出来一个红黑树对象,所以这就会导致使用RBTree<K, pair<const K, V>, MapofKey>::iterator这个迭代器类型时编译器不知道这个iterator到底是类型还是一个静态变量,所以这里我们得添加一个typename来告诉编译器虽然这里没有实例化出来对象但是这个iterator是一个类型名。

代码测试

将上面的代码完成之后我们就可以写一段代码来测试一下上面的代码的正确性,首先测试一下set的插入函数和查找函数的实现是否是正确的,那么这里的测试代码就如下:

#include"set.h"
int main()
{
	srand(time(0));
	set<int> s1;
	const size_t N = 100000;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand();
		s1.insert(x);
	}
	s1.insert(100);
	cout << s1.check() << endl;
	cout << s1.find(100) << endl;
	return 0;
}

代码的运行结果就如下:
在这里插入图片描述
符合我们的预期,那么我们再测试一下set的迭代器实现的是否是真确的,那么这里的测试代码如下:

int main()
{
	srand(time(0));
	set<int> s1;
	for (int i = 0; i < 100; i++)
	{
		int x = rand();
		s1.insert(x);
	}
	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	return 0;
}

代码的运行结果如下:
在这里插入图片描述
符合我们的预期那么这就说明我们的代码实现的结果是正确的,接下来我们就看看map的insert函数和find函数实现的是否是正确的:

#include"set.h"
#include"map.h"
int main()
{
	map<int, int> m;
	srand(time(0));
	for (int i = 0; i <= 10000; i++)
	{
		int x = rand();
		m.insert(make_pair(x, x));
	}
	m.insert(make_pair(100, 100));
	cout << m.find(100) << endl;
	return 0;
}

代码的运行结果如下:
在这里插入图片描述
我们再来看看迭代器的测试代码:

	map<int, int> m;
	srand(time(0));
	for (int i = 0; i <= 100; i++)
	{
		int x = rand();
		m.insert(make_pair(x, x));
	}
	map<int, int>::iterator it = m.begin();
	while (it != m.end())
	{
		cout << (*it).first << " ";
		++it;
	}

代码的运行结果如下:

在这里插入图片描述
符合我们的预期那么这就说明当前map迭代器实现的内容是正确的,但是这里的实现存在一个问题,当迭代器来到end位置时内部的_node指针就指向了空,那这个时候我们要是想通过- - 让他往回走的话是会出错的,所以这是上面迭代器中得一个bug,如果想要修改这个bug的话就得像库一样往树中插入一个头节点,那这里我们就不修改了,接着来看const迭代器的实现。

const迭代器

有了前面list迭代器的经验我们知道要想用一个迭代器模板封装出来const迭代器和普通迭代器的话迭代器模板需要三个参数,比如说下面的代码:

template<class V,class Ref,class Ptr>

第一个参数表示当前迭代器所指向的数据类型,第二个参数表示的就是引用的数据类型,第三个参数表示的是指针的数据类型,迭代器的模板参数发生修改之后,我们就得对红黑树里面的迭代器类型重命名进行修改,那么这里修改之后的代码就如下:

class RBTree
{
public:
//....
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V, V&, V*> iterator;
	typedef _RBTreeIterator<V,const V&,const V*> const_iterator;
//....
}

那么在迭代器类里面还得对函数的返回值进行修改,* 操作符重载返回的是节点的引用所以就将这个函数的返回值修改成为Ref,->操作符重载函数返回的是内部数据的地址所以将其返回值修改成为Ptr,那么修改后的代码就如下:

template<class V,class Ref,class Ptr>
struct _RBTreeIterator
{
	typedef RBTreeNode<V> Node;
	Node* _node;
	_RBTreeIterator( Node* node)
		:_node(node)
	{}
	Ref operator++(){//...}
	Ref operator--(){//...}
	Ref operator*(){//...}
	Ptr operator->(){//...}
	bool operator !=(const self& s){//...}
};

既然内部的迭代器发生了改变,那么外部的set和map是不是也得做出一点修改,首先set类得添加const版本的begin函数和end函数,比如说下面的代码:

template<class K>
class set
{
//...
public:
	typedef typename RBTree<K, K, SetofKey>::iterator iterator;
	typedef typename RBTree<K, K, SetofKey>::const_iterator const_iterator;
	iterator begin(){return tree.begin();}
	iterator end(){return tree.end();}
	const_iterator begin() const{return tree.begin();}
	const_iterator end() const{return tree.end();}
private:
	RBTree<K, K, SetofKey> tree;
};

但是这么实现存在一个问题,我们知道set内部的数据存在很紧密的关系,如果随意修改内部数据的值的话就会导致关系出现错误使其不再成为一个搜索二叉树,比如说下面的代码:

int main()
{
	set<int> s;
	s.insert(1);
	s.insert(2);
	s.insert(3);
	s.insert(4);
	set<int>::iterator it = s.begin();
	cout << *it << endl;
	*it = 10;
	cout << *it << endl;
	return 0;
}

这段代码的运行结果如下:
在这里插入图片描述
对吧不管是const迭代器还是普通迭代器对于set来说内部的数据都不能修改,所以本质上来说就不应该存在普通版本的迭代器,但是库中有啊我们平时的使用习惯也存在普通迭代器,那这里该如何进行修改呢?但是用const迭代器封装set的普通迭代器,表面上看这是一个普通迭代器但是实际上这是一个const迭代器,但是这样修改又会出现另外一个问题,之前普通版本的begin和end函数都返回的都是红黑树的普通迭代器,但是经过上面的修改现在的返回值的类型都变成了const迭代器,所以这里就会出现问题普通迭代器没有办法转换成为const迭代器,那如何解决呢?但是将普通版本的begin和end函数去掉即可,让类中只存在const版本的begin和end函数,这样在调用的时候返回的都是const版本的红黑树迭代器,那么这里的代码就如下:

template<class K>
class set
{
//...
public:
	typedef typename RBTree<K, K, SetofKey>::const_iterator iterator;
	typedef typename RBTree<K, K, SetofKey>::const_iterator const_iterator;
	iterator begin() const{return tree.begin();}
	iterator end() const{return tree.end();}
	bool insert(const K& key){return tree.insert(key);}
	bool find(const K& key){return tree.find(key);}
	bool check(){return tree.IsBalance();}
private:
	RBTree<K, K, SetofKey> tree;
};

经过这样的修改上面的测试代码就无法正常运行:
在这里插入图片描述
map容器就不能像上面一样将两个类型的迭代器都封装成为const迭代器,因为map允许用户修改每个数据中的V值,所以map中的const迭代器就是红黑树中的const迭代器,普通迭代器就是红黑树中的普通迭代器,并且得提供两个不同版本的begin和end函数,那么这里的代码就如下:

template<class K, class V>
class map
{
//....
public:
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::iterator iterator;
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::const_iterator const_iterator;
	iterator begin(){return tree.begin();}
	iterator end(){return tree.end();}
	const_iterator begin() const{return tree.begin();}
	const_iterator end() const{return tree.end();}
	bool insert(const pair<K,V>& data) {return tree.insert(data);}
	bool find(const K& key)	{return tree.find(key);}
private:
	RBTree<K, pair<const K, V>, MapofKey> tree;
};

我们就可以用下面的代码来进行测试:

int main()
{
	map<string, int> m;
	m.insert(make_pair("a", 1));
	m.insert(make_pair("b", 2));
	m.insert(make_pair("c", 3));
	m.insert(make_pair("d", 4));
	map<string, int>::iterator it = m.begin();
	cout << it->second << endl;
	it->second = 10;
	cout << it->second << endl;
	return 0;
}

这段代码的运行结果如下:
在这里插入图片描述

方括号的实现

要实现方括号的话我们就得修改insert函数的返回值让其返回pair,那么外部map和set容器中的insert函数如下:

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

然后再对红黑树里面的insert函数进行修改,因为pair里面的迭代器指向的是插入数据的地址或者是已经存在的元素的地址,又因为insert函数里面的cur变量会随着红黑树的调整而发生更改,所以我们找到插入的位置之后就可以创建一个变量来记录当前的位置,这样在返回值的时候就好返回,如果当前的元素不存在第二个元素的值就为true,如果当前插入的值存在的话第二个元素的值就为false,那么这里修改之后的代码就如下:

	pair<iterator,bool> insert(const V& _data)
	{
		if (root == nullptr)
		{
			root = new Node(_data);
			root->_col = BLACK;
			return make_pair(iterator(root), true);
		}
		KeyofT kot;
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
			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;
		//节点的调整
		//.....
		return make_pair(iterator(newnode)true);
	}

经过这个修改之后我们就可以实现方括号重载函数,首先创建一个pair来接收insert函数的返回值,然后再将pair中的第一个元素的第二个元素进行返回即可,那么这里的代码如下:

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

但是这么改会出问题因为set的insert函数返回的pair里面是const迭代器,而红黑树的insert函数返回的pair里面是普通迭代器,普通迭代器无法转换成为const迭代器所以这里就会出现问题,那么库中解决的方法就是通过先接收红黑树的插入函数的返回值比如说下面的代码:

pair<iterator,bool> insert(const K& key)
{
	pair<typename RBTree<K, K, SetofKey>::iterator, bool> ret = tree.insert(key);
}

然后我们就要实现一个函数这个函数的功能就是能用一个普通迭代器构造出const迭代器即可,那么这里的创建方式就是下面这样:

template<class V,class Ref,class Ptr>
struct _RBTreeIterator
{
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V, Ref, Ptr> self;
	typedef _RBTreeIterator<V, V&, V*> iterator;
	Node* _node;
	_RBTreeIterator( Node* node)
		:_node(node)
	{}
	_RBTreeIterator(const iterator& s)
		:_node(s._node)
	{}
//....
};

我们创建了一个新的类型iterator,这个类型是该迭代器的重命名,但是这个迭代器的参数是V也就是说iterator永远都是一个普通迭代器,我们还添加了一个构造函数但是这个构造函数的参数是普通迭代器,如果当前的迭代器是普通迭代器的话这个函数就是拷贝构造函数,如果当前的迭代器是const迭代器的话,这个函数的意思就是用普通迭代器来构造const迭代器,那么我们可以用下面的代码来进行验证:

int main()
{
	map<string,  int> m;
	m.insert(make_pair("a", 1));
	m.insert(make_pair("b", 2));
	m.insert(make_pair("c", 3));
	m.insert(make_pair("d", 4));
	map<string, int>::const_iterator it = m.begin();//编译器优化
	cout << it->second << endl;
	it->second = 10;
	cout << it->second << endl;
	return 0;
}

这里将普通迭代器赋值给了一个const迭代器,然后const迭代器想要对值进行修改,这段代码的运行结果如下:
在这里插入图片描述
很明显这段代码是错的,但是报错的原因是const迭代器不能进行修改而不是普通迭代器构造const迭代器出现错误,那么这就说明我们的代码实现的是正确的,最后用下面的代码来进行测试:

int main()
{
	string arr[] = { "苹果","苹果", "苹果", "苹果", "苹果",
				  "香蕉","香蕉", "香蕉", "香蕉", "香蕉",
					"西瓜","西瓜", "西瓜", "梨子", "梨子" };
	map<string, int> m;
	for (auto ch : arr)
	{
		m[ch]++;
	}
	for (auto ch : m)
	{
		cout << ch.first << ":" << ch.second << endl;
	}
	return 0;
}

代码运行的结果如下:
在这里插入图片描述
符合我们的预期,那么这就说明我们的代码实现的没有问题,完整的代码如下:
RBTree.h文件的代码如下:

#pragma once
#include<iostream>
using namespace std;
enum colour
{
	RED,
	BLACK,
};
template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& _data)
		:parent(nullptr)
		, right(nullptr)
		, left(nullptr)
		, data(_data)
		, _col(RED)
	{}
	RBTreeNode<T>* parent;
	RBTreeNode<T>* right;
	RBTreeNode<T>* left;
	T data;
	colour _col;
};
template<class V,class Ref,class Ptr>
struct _RBTreeIterator
{
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V, Ref, Ptr> self;
	typedef _RBTreeIterator<V, V&, V*> iterator;
	Node* _node;

	_RBTreeIterator( Node* node)
		:_node(node)
	{}
	_RBTreeIterator(const iterator& s)
		:_node(s._node)
	{}
	self& operator++()
	{
		if (_node->right)
		{
			Node* mine = _node->right;
			while (mine->left)
			{
				mine = mine->left;
			}
			_node = mine;
		}
		else
		{
			Node* parent = _node->parent;
			Node* cur = _node;
			while (parent != nullptr && parent->right == cur)
			{
				cur = parent;
				parent = parent->parent;
			}
			_node = parent;
		}
		return *this;
	}
	Ref operator--()
	{
		if (_node->left)//左边不为空
		{
			Node* mine = _node->left;
			while (mine->right)
			{
				mine = mine->right;
			}
			_node = mine;
		}
		else//左边为空
		{
			Node* _parent = _node->parent;
			Node* cur = _node;
			while (_parent != nullptr&&_parent->left == cur )
			{
				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 V, class KeyofT>
class RBTree
{

public:
	typedef RBTreeNode<V> Node;

	typedef _RBTreeIterator<V, V&, V*> iterator;
	typedef _RBTreeIterator<V,const V&,const V*> const_iterator;
	RBTree()
		:root(nullptr)
	{}
	iterator begin()
	{
		Node* cur = root;
		while (cur->left)
		{
			cur = cur->left;
		}
		return iterator(cur);
	}
	iterator end()
	{
		return iterator(nullptr);
	}
	pair<iterator,bool> insert(const V& _data)
	{
		if (root == nullptr)
		{
			root = new Node(_data);
			root->_col = BLACK;
			return make_pair(iterator(root), true);
		}
		KeyofT kot;
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
			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(_data) > kot(_parent->data))
			//如果插入的数据比parent的数据大
		{
			_parent->right = _cur;
			_cur->parent = _parent;
		}
		else
			//如果插入的数据比parent的数据笑
		{
			_parent->left = _cur;
			_cur->parent = _parent;
		}
		while (_parent != nullptr && _parent->_col == RED)
		{
			Node* _grandparent = _parent->parent;
			if (_grandparent->left == _parent)
				//祖先节点的左边要调整
			{
				Node* uncle = _grandparent->right;//parent在左边所以uncle在右边
				//情况一
				if (uncle != nullptr && uncle->_col == RED)
				{
					uncle->_col = _parent->_col = BLACK;
					if (_grandparent->parent != nullptr)
					{
						_grandparent->_col = RED;
					}
					_cur = _grandparent;
					_parent = _grandparent->parent;
				}
				else
				{
					//情况二
					if (_cur == _parent->left)
					{
						RototalR(_grandparent);
						_parent->_col = BLACK;
						_grandparent->_col = RED;
					}
					else//情况三
					{
						RototalL(_parent);
						RototalR(_grandparent);
						_cur->_col = BLACK;
						_grandparent->_col = RED;
					}
					break;
				}
			}
			else
				//祖先节点的右边要调整
			{
				Node* uncle = _grandparent->left;//parent在右边所以uncle在左边
				//情况一
				if (uncle != nullptr && uncle->_col == RED)
				{
					uncle->_col = _parent->_col = BLACK;
					if (_grandparent->parent != nullptr)
					{
						_grandparent->_col = RED;
					}
					_cur = _grandparent;
					_parent = _grandparent->parent;
				}
				else
				{
					//情况二
					if (_cur == _parent->right)
					{
						RototalL(_grandparent);
						_parent->_col = BLACK;
						_grandparent->_col = RED;
					}
					else//情况三
					{
						RototalR(_parent);
						RototalL(_grandparent);
						_cur->_col = BLACK;
						_grandparent->_col = RED;
					}
					break;
				}
			}
		}
		return make_pair(iterator(newnode),true);
	}
	void inorder()
	{
		_inorder(root);
	}
	bool find(const K& val)
	{
		Node* cur = root;
		KeyofT kot;
		while (cur)
		{
			if ( kot(cur->data) == val)
			{
				return true;
			}
			else if (kot(cur->data) > val)
			{
				cur = cur->left;
			}
			else
			{
				cur = cur->right;
			}
		}
		return false;
	}
	bool check(Node* root, int BlackNums, int ref)
	{
		if (root == nullptr)
		{
			if (ref != BlackNums)
			{
				cout << "违反规则" << endl;
				return false;
			}
			return true;
		}
		if (root->_col == RED && root->parent->_col == RED)
		{
			cout << "出现了连续红色节点" << endl;
		}
		if (root->_col == BLACK)
		{
			BlackNums++;
		}
		return check(root->left, BlackNums, ref) && check(root->right, BlackNums, ref);
	}
	bool IsBalance()
	{
		if (root == nullptr)
		{
			return true;
		}
		if (root->_col != BLACK)
		{
			return false;
		}
		int ref = 0;
		Node* _left = root;
		while (_left)
		{
			if (_left->_col == BLACK)
			{
				ref++;
			}
			_left = _left->left;
		}
		return check(root, 0, ref);

	}
private:
	void _inorder(const Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_inorder(root->left);
		cout << root->key << " ";
		_inorder(root->right);
	}
	void RototalL(Node* _parent)
	{
		Node* subR = _parent->right;//右孩子的节点
		Node* subRL = subR->left;//右孩子的左节点
		Node* ppNode = _parent->parent;//祖父节点
		//把subRL放到_parent的右
			_parent->right = subRL;
		if (subRL)
		{
			//如果subRL不为空则修改父节点的指向
				subRL->parent = _parent;
		}
		//把_parent放到subR的左
			subR->left = _parent;
		//修改_parent的parent的指向
			_parent->parent = subR;
		if (ppNode)//如果祖父不为空,则要改变祖父的指向
		{
			if (ppNode->right == _parent)
			{
				ppNode->right = subR;
				subR->parent = ppNode;
			}
			else//如果_parent是祖父的左
			{
				ppNode->left = subR;
				subR->parent = ppNode;
			}
		}
		else//祖父为空节点说明当前调整的是根节点
		{
			root = subR;
			subR->parent = nullptr;
		}
	}
	//右旋转
	void RototalR(Node* _parent)
	{
		Node* subL = _parent->left;
		Node* subLR = subL->right;
		Node* ppNode = _parent->parent;
		_parent->left = subLR;
		if (subLR)
		{
			subLR->parent = _parent;
		}
		subL->right = _parent;
		_parent->parent = subL;
		if (ppNode != nullptr)
		{
			if (ppNode->right == _parent)
			{
				ppNode->right = subL;
				subL->parent = ppNode;
			}
			else
			{
				ppNode->left = subL;
				subL->parent = ppNode;
			}
		}
		else
		{
			root = subL;
			subL->parent = nullptr;
		}
	}

	Node* root;
};

set.h文件的代码如下:

#pragma once
#include"RBTree.h"
template<class K>
class set
{
	struct SetofKey
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:

	typedef typename RBTree<K, K, SetofKey>::const_iterator iterator;
	typedef typename RBTree<K, K, SetofKey>::const_iterator const_iterator;
	iterator begin() const
	{
		return tree.begin();
	}
	iterator end() const
	{
		return tree.end();
	}
	pair<iterator,bool> insert(const K& key)
	{
		pair<typename RBTree<K, K, SetofKey>::iterator, bool> ret = tree.insert(key);
	}
	bool find(const K& key)
	{
		return tree.find(key);
	}
	bool check()
	{
		return tree.IsBalance();
	}
private:
	RBTree<K, K, SetofKey> tree;
};

map.h文件的大代码如下:

#pragma once
#include"RBTree.h"
template<class K, class V>
class map
{
	struct MapofKey
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::iterator iterator;
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::const_iterator const_iterator;
	iterator begin()
	{
		return tree.begin();
	}
	iterator end()
	{
		return tree.end();
	}
	const_iterator begin() const
	{
		return tree.begin();
	}
	const_iterator end() const
	{
		return tree.end();
	}
	pair<iterator,bool> insert(const pair<K,V>& data)
	{
		return tree.insert(data);
	}
	bool find(const K& key)
	{
		return tree.find(key);
	}
	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = insert(make_pair(key, V()));
		return ret.first->second;
	}
private:
	RBTree<K, pair<const K, V>, MapofKey> tree;
};

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

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

相关文章

blfs:为lfs虚拟机增加桌面01

vmware中克隆一份lfs&#xff0c;扩大硬盘分区再操作。 1、硬盘拓展容量&#xff0c;分区挂载到/home&#xff08;已有的大小在后面编译桌面系统会捉襟见肘&#xff09; 使用fdisk进行分区 fdisk /dev/sda 执行p w分区并保存 mkfs -v -t ext4 /dev/sda4 转ext4格式 让/…

uniapp中使用mixins(混入)

mixins 选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项&#xff0c;这些选项将会被合并到最终的选项中&#xff0c;使用的是和 Vue.extend() 一样的选项合并逻辑。也就是说&#xff0c;如果你的混入包含一个 created 钩子&#xff0c;而创建组…

如何使用ffmpeg将BDMV(m2ts)转换成MKV、MP4等其他格式的文件

BDMV 是蓝光碟使用的格式。这种格式没有办法使用播放软件播放&#xff0c;必须要用硬盘播放器&#xff0c;也就是专门的设备。但是最经典的 ffmpeg 可以将其转换成其他格式&#xff0c;并且保持相同的码率和清晰度&#xff0c;这样就可以很方便的查看了。 本文使用 macOS 进行…

加速度计的原理与应用

什么是加速度计 加速度计是一种传感器&#xff0c;可以测量物体所受加速的大小和方向。 加速度计的工作原理 传统加速度计利用质量和弹簧的相互作用来感应加速度&#xff0c;当物体收到加速度时&#xff0c;弹簧会发生变形&#xff0c;通过衡量这种变形来测量加速度的大小。 …

postgresql | 数据库| 生成2000W条的简单测试表

前言&#xff1a; 数据库学习的过程中&#xff0c;很可能需要数据量比较大的表来进行模拟测试&#xff0c;那么&#xff0c;测试表的创建需要遵循的是贴近实际的生产环境&#xff0c;尽量的模仿实际的生产环境。 因此&#xff0c;学习数据库的时候&#xff0c;快速的创建一个…

chatgpt赋能python:Python求单词长度:基于字符串操作的简单实现

Python求单词长度&#xff1a;基于字符串操作的简单实现 Python作为一种广泛应用于各个领域的编程语言&#xff0c;其强大的字符串操作功能在文本处理中经常被使用。本篇文章将介绍基于Python的字符串操作实现单词长度的方法。 什么是单词长度 在文本处理中&#xff0c;单词…

【Redis】1、学习 Redis 的五大基本数据类型【String、Hash、List、Set、SortedSet】

目录 一、NoSQL 和 SQL 区别二、认识 Redis三、Redis 的数据结构介绍四、Redis 通用命令五、String 类型六、key 的格式七、Hash&#xff08;散列&#xff09;类型八、List 类型九、Set 类型十、SortedSet 类型&#xff08;可排序&#xff09; 一、NoSQL 和 SQL 区别 二、认识 …

macOS Sonoma编译OpenCV源码输出IOS平台库

1.macOS下载并编译OpenCV源码: 克隆源码: 主仓: git clone https://github.com/opencv/opencv.git 扩展仓: git clone https://github.com/opencv/opencv_contrib.git 编译xcode源码需要CMake与XCode命令行工具 确认已安装CMake 确认已安装XCode 安装xcode command l…

UNZIP

目录 搭建环境 做题开始 通过ln直接创建 通过mkdir 后进行ln 搭建环境 这次是在自己的靶机环境里面搭建 cd /var/www/html index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</ti…

罗技k380键盘教程

在智能手机和平板电脑上享受台式电脑般舒适便捷的输入体验。罗技蓝牙™ 多设备键盘 K380 是一款小巧独特的键盘&#xff0c;让您在家中任何地方都能使用个人设备进行沟通和创作。 借助便捷的易于切换™ 按钮&#xff0c;可以通过蓝牙™ 无线技术同时连接最多三台设备&#xff…

【LeetCode】动态规划 刷题训练(一)

文章目录 面试题 08.01. 三步问题题目解析状态转移方程完整代码 746. 使用最小花费爬楼梯题目解析状态转移方程完整代码 91. 解码方法题目解析状态转移方程情况1&#xff1a;让i位置的数&#xff0c;单独去解码情况2&#xff1a;让i位置的数 和i-1位置的数 结合 一起去解码 完整…

安全合规进入场景细分时代

2022年6月1日&#xff0c;《中华人民共和国网络安全法》&#xff08;以下简称《网络安全法》&#xff09;正式实施迎来5周年。 作为国家实施网络空间管辖的第一部法律&#xff0c;《网络安全法》标志着我国网络安全工作有了基础性的法律框架&#xff0c;意味着建设网络强国的制…

chatgpt赋能python:Python清空:如何在Python中使用清空来删除变量和数据结构中的所有元素

Python清空&#xff1a;如何在Python中使用清空来删除变量和数据结构中的所有元素 在Python编程中&#xff0c;我们常常需要删除变量和数据结构中的元素。Python提供了多种方法来实现这一点&#xff0c;其中最常用的方法之一就是使用清空功能。在本文中&#xff0c;我们将介绍…

【期末总复习】机器学习(公式推导与代码实现)鲁伟

【第一章】机器学习预备知识 1、了解机器学习的发展简史 2、复述出训练集、验证集和测试集的作用 训练集&#xff08;Training set&#xff09; 作用是用来拟合模型&#xff0c;通过设置分类器的参数&#xff0c;训练分类模型。后续结合验证集作用时&#xff0c;会选出同一参…

chatgpt赋能python:Python教程:求反向位置的字母

Python教程&#xff1a;求反向位置的字母 在这篇文章中&#xff0c;我们将介绍如何使用Python编程语言来找到给定字符串中的反向位置的字母。这是一个有趣的编程问题&#xff0c;特别是对于那些喜欢解决有趣问题的人来说&#xff0c;同时本文也将从SEO的角度来讲解如何优化文本…

LNMP搭建

一、编译安装nginx1.1 关闭防火墙&#xff0c;将安装nginx所需软件包传到/opt目录下并解压1.2 安装依赖包1.3 创建运行用户与组1.4 编译安装Nginx1.5 检查配置文件是否配置正确&#xff0c;并启动nginx服务 二、安装 MySQL 服务2.1 安装Mysql环境依赖包2.2 创建运行用户2.3 编译…

前端Vue自定义简单好用商品分类列表组件 侧边栏商品分类组件

前端Vue自定义简单好用商品分类列表组件 侧边栏商品分类组件 &#xff0c; 下载完整代码请访问uni-app插件市场地址&#xff1a;https://ext.dcloud.net.cn/plugin?id13148 效果图如下&#xff1a; # cc-defineCateList #### 使用方法 使用方法 <!-- data:商品列表数组…

C专家编程 —— 链接的思考

文章目录 编译器的作用动态链接和静态链接动态链接动态链接的优点 函数库链接的几个小秘密 编译器的作用 通常编译器被氛围六七个小的程序&#xff1a; C预处理器&#xff0c;得到main.i文件前端做语法语义分析&#xff0c;然后后端生成汇编的指令代码main.s文件优化器可以放…

Java-API简析_java.lang.SecurityManager类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/131346082 出自【进步*于辰的博客】 其实我的【Java-API】专栏内的博文对大家来说意义是不大的。…

【Python 基础篇】Python学生管理系统

文章目录 引言一、系统设计与功能分析二、系统设计与实现三、系统应用示例四、总结 引言 学生管理系统是一个常见的应用程序&#xff0c;它可以帮助学校、教育机构或教师管理学生的信息。本文将介绍如何使用面向对象编程思想&#xff0c;利用Python开发一个学生管理系统。系统…