C++STL——map与set的模拟实现

news2024/12/27 11:32:14

map与set的模拟实现

  • map与set的部分源码参考
  • 改造红黑树
  • 红黑树的迭代器
  • 补全set与map的实现
  • 完整代码

map与set的部分源码参考

map和set的底层都是由红黑树实现的。
所以这里将上次实现的红黑树插入拿来用。
首先想一想,搜索二叉树不能修改值,因为会破坏整棵树的平衡。
set与map的部分源码:

class set {
public:
  // typedefs:

  typedef Key key_type;
  typedef Key value_type;
  typedef Compare key_compare;
  typedef Compare value_compare;
private:
  typedef rb_tree<key_type, value_type, //set这里传了一个K,一个V
                  identity<value_type>, key_compare, Alloc> rep_type;
  rep_type t;  // red-black tree representing set
template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>

class map {
public:

// typedefs:

  typedef Key key_type;
  typedef T data_type;
  typedef T mapped_type;
  typedef pair<const Key, T> value_type;
private:
  typedef rb_tree<key_type, value_type, //map这里传了一个K,一个V
                  select1st<value_type>, key_compare, Alloc> rep_type;
  rep_type t;  // red-black tree representing map

这个是红黑树的部分源码:

template <class Key, class Value, class KeyOfValue, class Compare,class Alloc = alloc>
class rb_tree {
protected:
  typedef void* void_pointer;
  typedef __rb_tree_node_base* base_ptr;
  typedef __rb_tree_node<Value> rb_tree_node;
public:
  typedef rb_tree_node* link_type;
protected:
  size_type node_count; // keeps track of size of tree
  link_type header;  
  Compare key_compare;

set要传入到红黑树的Value的值是K,map要传入的值是pair<const K,V>
那么,这里完全可以区分传入的是set还是map,为什么要给红黑树传入第一个模板参数呢?
第一个模板参数是用来查找的,因为无论是set还是map都是用kay去查找的。

改造红黑树

这是红黑树的结点:

enum Color//利用枚举来给红黑树配色
{
	RED,
	BLACK
};
template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& data)//让红黑树的结点变成一个模板就行了,因为有可能是set有可能是map
		:_data(data)
		, _color(RED)//这里一定要给红色,如果给黑色其他路径就要涉及到也要加黑色结点,更加麻烦
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
	{}

	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;
	T _data;
	Color _color;//结点的配色
};
namespace baiye
{
	template<class K>
	class set
	{
	public:

	private:
		RBTree<K, K> _t;//K模型
	};
}
namespace baiye
{
	template<class K, class V>
	class map
	{
	public:

	private:
		RBTree<K, pair<const K, V>> _p;//KV模型
	};
}

然后我们发现,后面的插入这里调用出现了问题:
在这里插入图片描述
原来写的是VK模型,但是现在set是K模型,要兼容两个模型。
让我们来看看源码是怎么做的:
在这里插入图片描述
源码这里多了一个模板参数,意思是将key取出来比较大小。
这里可以写两个仿函数用:

namespace baiye
{
	template<class K>
	class set
	{
		struct setKeyOFV
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		bool insert(const K& key)
		{
			return _t.Insert(key);
		}
	private:
		RBTree<K, K, setKeyOFV> _t;
	};
}
namespace baiye
{
	template<class K, class V>
	class map
	{
		struct mapKeyOFV
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		bool insert(const pair<const K, V>& kv)
		{
			return _p.Insert(kv);
		}
	private:
		RBTree<K, pair<const K, V>, mapKeyOFV> _p;
	};
}

红黑树的迭代器

先来实现*与->:

template<class T>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	Node* _node;
	RBTreeIterator(Node* node)
		:_node(node)
	{}
	T& operator*()
	{
		return _node->_data;
	}

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

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

迭代器难的是++和- -操作:
原来stl中的红黑树其实有一个哨兵位的头结点:
在这里插入图片描述
哨兵位中还有两个指针分别指向红黑树中的最小值和最大值,但是这里我并没有去实现这个哨兵位,所以就用另一种方法。
首先先给红黑树加begin和end函数:

	iterator begin()
	{
		Node* cur = _root;
		while (cur && cur->left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}
	iterator end()
	{
		return iterator(nullptr);
	}

然后是++,++是要走中序遍历的,我们要采用迭代的方式,以前要借助一个队列来进行中序遍历的方法进行++。
在这里插入图片描述
假设我们传入的迭代器的结点是8结点,那么下一个结点就是10号结点,也就是8号结点的右子树最小结点。
那么如果右为空呢?
右子树为空就说明不需要去右子树找了,需要向上找节点,假设是这样子的:
在这里插入图片描述
it指向5结点,发现右为空,那就向上走,走到6结点发现右不为空然后走到7结点,7结点右为空就要继续往上走,这个时候不应该走到6结点的位置,应该是8结点的位置。
在这里插入图片描述
在这里插入图片描述
假设这里再多出一个结点:
在这里插入图片描述
那么他的右为空也不能再走到7结点的位置了。
在这里插入图片描述
所以我们能总结出来一个规律。

右不为空就去找右子树的最小节点。
右为空就去找祖先(孩子为父节点的左的那个祖先)

如果是it在15结点这个位置,往上走没有遇到符合祖先的位置,并且已经走到空了,那就代表已经结束了。

	Self& operator++()
	{
		if (_node->_right)//右子树不为空
		{
			Node* cur = _node->_right;
			while (cur->_left)
			{
				cur = cur->_left;
			}
			_node = cur;
		}
		else//左子树为空
		{
			Node* parent = _node->_parent;
			Node* cur = _node;
			while (parent && parent->_left != cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

然后来实现- -逻辑:

	Self& operator--()
	{
		if (_node->_left)//左子树不为空
		{
			Node* cur = _node->_left;
			while (cur && cur->_right)
			{
				cur = cur->_right;
			}
			_node = cur;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur != parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

补全set与map的实现

set当中的值是不允许被修改的。
这里我们就要实现const版本的迭代器:

template<class T,class Ref, class Ptr>
struct RBTreeIterator//红黑树的迭代器
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;
	RBTreeIterator(Node* node)
		:_node(node)
	{}
	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}

先将这两个函数变成能兼容const和非const版本。
看一下set源码是怎么定义迭代器的:
在这里插入图片描述

那么set当中就算是非const版本的迭代器也不能修改值:

	template<class K>
	class set
	{
		struct setKeyOFV
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, K, setKeyOFV>::const_iterator iterator;//这里无论是非const迭代器也不能修改set的值,所以要变成const版本
		typedef typename RBTree<K, K, setKeyOFV>::const_iterator const_iterator;
		iterator begin()
		{
			return _t.begin();
		}
		iterator end()
		{
			return _t.end();
		}
		bool insert(const K& key)
		{
			return _t.Insert(key);
		}
	private:
		RBTree<K, K, setKeyOFV> _t;
	};

但是我们的begin函数和end函数的返回值和重命名的迭代器类型已经不相同了。
所以看看set源码是如何做到的:
在这里插入图片描述
后面加了一个const就解决了问题,因为权限可以缩小,传入的就算是非const版本也可以。
在set的begin与end中加了个const就说明要去调用const版本的begin与end,所以在树那里也要实现const版本的begin与end。

template<class K, class T, class KeyOFV>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef RBTreeIterator<T, T&, T*> iterator;
	typedef RBTreeIterator<T, const T&, const T*> const_iterator;

	iterator begin()
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}
	iterator end()
	{
		return iterator(nullptr);
	}
	const_iterator begin()const
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return const_iterator(cur);
	}
	const_iterator end()const
	{
		return const_iterator(nullptr);
	}

然后来实现map的const版本的迭代器:
先看看源码:
在这里插入图片描述
在这里插入图片描述

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

		iterator begin()
		{
			return _p.begin();
		}
		iterator end()
		{
			return _p.end();
		}
		const_iterator begin()const
		{
			return _p.begin();
		}
		const_iterator end()const
		{
			return _p.end();
		}
		bool insert(const pair<const K, V>& kv)
		{
			return _p.Insert(kv);
		}
	private:
		RBTree<K, pair<const K, V>, mapKeyOFV> _p;
	};

然后就是[]重载了:
这里首先要改插入的返回值和返回类型。

	pair<iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = BLACK;
			return make_pair(iterator(_root), true);
		}
		Node* cur = _root;
		Node* parent = nullptr;
		KeyOFV 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;//因为cur会变位置,所以储存一下新节点
		if (kot(parent->_data) > kot(data))
		{
			cur->_parent = parent;
			parent->_left = cur;
		}
		else if (kot(parent->_data) < kot(data))
		{
			cur->_parent = parent;
			parent->_right = cur;
		}
		//如果父节点为空就代表cur是根节点,没必要调整了
		//还要判断cur结点是否与父节点的颜色均为红色
		while (parent && parent->_color == RED)
		{
			Node* grandfather = parent->_parent;//祖父结点
			if (parent == grandfather->_left)//新增结点在祖父左
			{
				Node* uncle = grandfather->_right;
				//情况一
				if (uncle && uncle->_color == RED)//这里不要忘记验证uncle的存在
				{
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandfather->_color = RED;
					cur = grandfather;//最后让cur等于祖父结点的位置
					parent = cur->_parent;
				}
				else
				{
					if (parent->_left == cur)//情况二
					{
						RotateR(grandfather);//右单旋
						grandfather->_color = RED;
						parent->_color = BLACK;
					}
					else if (parent->_right == cur)//情况三
					{
						RotateL(parent);//左单旋
						RotateR(grandfather);//右单旋
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;//第二种和第三种情况变完之后因为最上面的组节点变为黑,所以这里跳出循环
				}
			}
			else//新增结点在祖父右
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_color == RED)//情况一
				{
					uncle->_color = BLACK;
					parent->_color = BLACK;
					grandfather->_color = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				else {
					if (cur == parent->_right)//情况二
					{
						RotateL(grandfather);
						grandfather->_color = RED;
						parent->_color = BLACK;
					}
					else if (cur == parent->_left)//情况三
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;
				}
			}
		}
		_root->_color = BLACK;
		return make_pair(iterator(newnode), true);
	}

[]在map中重载即可:

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

因为树种插入的函数改变了返回值,所以set与map的插入调用函数也要改变返回值,但是set当中插入的返回值是非const类型:
在这里插入图片描述
这里看看源码是如何解决的:
在这里插入图片描述
这里使用了rep_type类型去接收,然后再用p去重新构造一个pair<iterator, bool>类型。
在这里插入图片描述
这是构造,为了解决非const的迭代器不能拷贝给const迭代器的问题。
在这里插入图片描述
在这里插入图片描述

完整代码

RBTree.h

#include<iostream>
#include<cassert>
using namespace std;
enum Color//利用枚举来给红黑树配色
{
	RED,
	BLACK
};
template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& data)
		:_data(data)
		, _color(RED)//这里一定要给红色,如果给黑色其他路径就要涉及到也要加黑色结点,更加麻烦
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
	{}

	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;
	T _data;
	Color _color;//结点的配色
};
template<class T,class Ref, class Ptr>
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)
	{}
	//普通迭代器传给普通迭代器用的是默认生成的拷贝构造
	//当迭代器是const的时候用的就是普通迭代器构造的const迭代器
	RBTreeIterator(const iterator& s)
		:_node(s._node)
	{}
	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	Self& operator++()
	{
		if (_node->_right)//右子树不为空
		{
			Node* cur = _node->_right;
			while (cur->_left)
			{
				cur = cur->_left;
			}
			_node = cur;
		}
		else//左子树为空
		{
			Node* parent = _node->_parent;
			Node* cur = _node;
			while (parent && parent->_left != cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
	Self& operator--()
	{
		if (_node->_left)//左子树不为空
		{
			Node* cur = _node->_left;
			while (cur && cur->_right)
			{
				cur = cur->_right;
			}
			_node = cur;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur != parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
	bool operator!=(const Self& it)
	{
		return _node != it._node;
	}

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

template<class K, class T, class KeyOFV>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef RBTreeIterator<T, T&, T*> iterator;
	typedef RBTreeIterator<T, const T&, const T*> const_iterator;

	iterator begin()
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}
	iterator end()
	{
		return iterator(nullptr);
	}
	const_iterator begin()const
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return const_iterator(cur);
	}
	const_iterator end()const
	{
		return const_iterator(nullptr);
	}
	pair<iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = BLACK;
			return make_pair(iterator(_root), true);
		}
		Node* cur = _root;
		Node* parent = nullptr;
		KeyOFV 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;//因为cur会变位置,所以储存一下新节点
		if (kot(parent->_data) > kot(data))
		{
			cur->_parent = parent;
			parent->_left = cur;
		}
		else if (kot(parent->_data) < kot(data))
		{
			cur->_parent = parent;
			parent->_right = cur;
		}
		//如果父节点为空就代表cur是根节点,没必要调整了
		//还要判断cur结点是否与父节点的颜色均为红色
		while (parent && parent->_color == RED)
		{
			Node* grandfather = parent->_parent;//祖父结点
			if (parent == grandfather->_left)//新增结点在祖父左
			{
				Node* uncle = grandfather->_right;
				//情况一
				if (uncle && uncle->_color == RED)//这里不要忘记验证uncle的存在
				{
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandfather->_color = RED;
					cur = grandfather;//最后让cur等于祖父结点的位置
					parent = cur->_parent;
				}
				else
				{
					if (parent->_left == cur)//情况二
					{
						RotateR(grandfather);//右单旋
						grandfather->_color = RED;
						parent->_color = BLACK;
					}
					else if (parent->_right == cur)//情况三
					{
						RotateL(parent);//左单旋
						RotateR(grandfather);//右单旋
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;//第二种和第三种情况变完之后因为最上面的组节点变为黑,所以这里跳出循环
				}
			}
			else//新增结点在祖父右
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_color == RED)//情况一
				{
					uncle->_color = BLACK;
					parent->_color = BLACK;
					grandfather->_color = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				else {
					if (cur == parent->_right)//情况二
					{
						RotateL(grandfather);
						grandfather->_color = RED;
						parent->_color = BLACK;
					}
					else if (cur == parent->_left)//情况三
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;
				}
			}
		}
		_root->_color = BLACK;
		return make_pair(iterator(newnode), true);
	}
	void RotateL(Node* parent)//左单旋
	{
		Node* pparent = parent->_parent;
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

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

		if (pparent)
		{
			if (pparent->_left == parent)
				pparent->_left = subR;
			else
				pparent->_right = subR;
		}
		else
		{
			_root = subR;

		}
		subR->_parent = pparent;
	}
	void RotateR(Node* parent)//右单旋
	{
		Node* pparent = parent->_parent;
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

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

		if (pparent)
		{
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
		}
		else
		{
			_root = subL;
		}
		subL->_parent = pparent;
	}
	void Inorder()
	{
		_Inorder(_root);
	}
	bool IsBalanceTree()
	{
		if (_root == nullptr)
			return true;
		if (_root->_color != BLACK)
		{
			cout << "根不为黑色" << endl;
			return false;
		}
		int count = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_color == BLACK)
				count++;
			cur = cur->_left;//找一条路径上的黑节点
		}
		_IsBalanceTree(_root, 0, count);
	}
private:
	bool _IsBalanceTree(Node* root, int k, int sum)//验证
	{
		if (root == nullptr)
		{
			if (k == sum)//这里代表当前路径点和最左边的路径点相同
				return true;
			else
			{
				cout << "每条路径上黑色结点数量不同" << endl;
			}
			return false;
		}
		if (root->_color == BLACK)
			k++;
		if (root->_parent && root->_parent->_color == RED && root->_color == RED)
		{
			cout << root->_parent->_data.first << endl;
			return false;
		}
		return _IsBalanceTree(root->_left, k, sum) && _IsBalanceTree(root->_right, k, sum);
	}
	void _Inorder(Node* _root)
	{
		if (_root == nullptr)
			return;
		_Inorder(_root->_left);
		cout << _root->_data.first << ":" << _root->_data.second << endl;
		_Inorder(_root->_right);

	}
	Node* _root = nullptr;
};

set.h

#include"RBTree.h"

namespace baiye
{
	template<class K>
	class set
	{
		struct setKeyOFV
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, K, setKeyOFV>::const_iterator iterator;//这里无论是非const迭代器也不能修改set的值,所以要变成const版本
		typedef typename RBTree<K, K, setKeyOFV>::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, setKeyOFV>::iterator, bool> it = _t.Insert(key);//这里接收的是普通迭代器
			return pair<iterator, bool>(it.first, it.second);//这里用普通迭代器构造const迭代器
		}
	private:
		RBTree<K, K, setKeyOFV> _t;
	};
	void settest()
	{
		int arr[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
		set<int>s;
		for (auto& e : arr)
		{
			s.insert(e);
		}
		set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
}

map.h

#include"RBTree.h"

namespace baiye
{
	template<class K, class V>
	class map
	{
		struct mapKeyOFV
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<const K, V>, mapKeyOFV>::iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, mapKeyOFV>::const_iterator const_iterator;

		iterator begin()
		{
			return _p.begin();
		}
		iterator end()
		{
			return _p.end();
		}
		const_iterator begin()const
		{
			return _p.begin();
		}
		const_iterator end()const
		{
			return _p.end();
		}
		pair<iterator, bool> insert(const pair<const K, V>& kv)
		{
			return _p.Insert(kv);
		}
		V& operator[](const K& key)
		{
			pair<iterator, bool> it = insert(make_pair(key, V()));
			return it.first->second;
		}
	private:
		RBTree<K, pair<const K, V>, mapKeyOFV> _p;
	};
	void maptest()
	{
		int arr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
		map<int, int> m;
		for (auto& e : arr)
		{
			m.insert(make_pair(e, e));
		}
		map<int, int>::iterator it = m.begin();
		while (it != m.end())
		{
			cout << it->first << " ";
			++it;
		}
		cout << endl;
	}
}

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

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

相关文章

第十章 装饰者模式

文章目录 前言一、装饰者模式定义装饰者模式 UML图 二、装饰者模式解决星巴克咖啡订单完整代码Drink 抽象 饮料类Coffee 咖啡类继承 Drink 做一个缓冲层Espresso 意大利咖啡 继承 CoffeeLongBlack 咖啡ShortBlack 咖啡装饰者&#xff0c;调料牛奶巧克力豆浆咖啡店测试程序添加 …

01-yolo算法

要点&#xff1a; 归纳 YOLOv5 github 1 YOLO v1 1) 将一幅图像分成SxS个网格(grid cell)&#xff0c;如果某个object的中心 落在这个网格中&#xff0c;则这个网格就负责预测这个object。 2)每个网格要预测B个bounding box&#xff0c;每个bounding box 除了要预测位置之…

TortoiseSVN使用-合并分支代码

文章目录 3.4.12 合并分支代码TortoiseSVN有2种合并方式演示场景1&#xff1a;&#xff08;合并一个版本范围 Merge a range of revisions&#xff09;演示场景2&#xff1a;&#xff08;合并两个不同的树 Merge two different trees&#xff09;&#xff0c;不设置主分支版本演…

初窥Edubuntu 23.04:装有教育软件的Ubuntu桌面

导读4月20日&#xff0c;Edubuntu将作为Ubuntu官方口味卷土重来&#xff0c;作为即将发布的Ubuntu 23.04&#xff08;Lunar Lobster&#xff09;的一部分&#xff0c;所以我认为让你们先看看这个重制版中包含的内容是个好主意。 Edubuntu以前被称为Ubuntu教育版&#xff0c;最…

Anaconda安装及tensorflow安装

1.下载Anaconda安装包&#xff0c;并安装好 官网下载网址&#xff1a;https://www.anaconda.com/download/ 清华映像站&#xff1a; https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 注意&#xff1a;注意安装anaconda时一定要把环境变量加入windows环境中。若没有…

C语言---函数介绍详解

生活的全部意义在于无穷地探索尚未知道的东西,在于不断地增加更多的知识——左拉 文章目录 前言函数模块化程序设计方法函数的定义函数的分类函数定义角度库函数自定义函数 函数形式角度无参函数有参函数 函数兼有其他语言中的函数和过程两种功能的角度有返回值函数无返回值函…

ENVI 国产高分2号(GF-2)卫星数据辐射定标 大气校正 影像融合

1.数据 高分2号卫星数据&#xff0c;包含&#xff1a; MSS-1\2多光谱数据&#xff0c;4m分辨率&#xff1b; Pan-1\2全色波段数据&#xff0c;0.8m分辨率。 2.处理软件 ENVI5.3 国产插件下载地址&#xff1a;ENVI App Store (geoscene.cn) 首先下载插件文件&#xff1b; …

Spring的事务传播行为

事务传播行为 多个声明的事务的方法在相互调用的时候&#xff0c;这个是事务应该如何去传递。 比如说methodA()调用methodB(), 那么这两个方法都显示了开启了事务&#xff0c;那么methodB()是开启一个新的事务还是继续在methodA()这个事务里面去执行就取决于所谓的事务传播的…

多兴趣推荐召回模型:ComiRec

前言 多兴趣向量召回系列&#xff1a; 通过Youtube DNN推荐模型来理解推荐流程 多兴趣召回模型&#xff1a;MIND 推荐系统可以表达为序列推荐问题的形式&#xff0c;序列推荐任务是通过用户的历史行为来预测用户下一个感兴趣的item&#xff0c;这也与真实场景的推荐场景是符…

Linux 通过Chrony实现NTP

Linux实现NTP服务器时间同步&#xff0c;可以通过ntp服务实现&#xff0c;也可以通过chrony服务实现 两者区别主要有 Chrony运行于UDP的323端口&#xff0c;NTP运行于UDP的123端口 Chrony相比于NTP可以更快同步&#xff0c;能够最大同步的减少时间和频率的误差 Chrony能够更好…

linux安装harbor ,搭建镜像私服

linux安装harbor &#xff0c;搭建镜像私服 前提 环境中要有 docker 和 docker-compose 2、下载 harbor 的 .tgz 安装包 官网地址&#xff1a; Releases goharbor/harbor GitHub 第一个是离线安装包&#xff0c;第二个是在线安装包&#xff1b;带asc后缀的文件就是校验…

ROS学习第三十二节——xacro构建激光雷达小车

https://download.csdn.net/download/qq_45685327/87718396 在前面小车底盘基础之上&#xff0c;添加摄像头和雷达传感器。 0.底盘实现 deamo02_base.xacro <!--使用 xacro 优化 URDF 版的小车底盘实现&#xff1a;实现思路:1.将一些常量、变量封装为 xacro:property比如…

【Spring】Spring AOP

目录 一、Spring AOP简介1.什么是AOP2.AOP术语 二、AspectJ开发1.基于XML的声明式AspectJ1.1 配置切面1.2 配置切入点1.3 配置通知 2.基于注解的声明式AspectJ 一、Spring AOP简介 1.什么是AOP AOP的全称是Aspect-Oriented Programming&#xff0c;即面向切面编程&#xff08;…

一种引入过渡阶段和高斯变异的改进算术优化算法(TGAOA)-附代码

一种引入过渡阶段和高斯变异的改进算术优化算法(TGAOA) 文章目录 一种引入过渡阶段和高斯变异的改进算术优化算法(TGAOA)1.算术优化算法2. 改进算术优化算法2.1 重构数学加速优化器 MOA2.2 新策略的引入2.3 具有一致性的高斯变异策略2.4具有一致性的高斯变异策略 3.实验结果4.参…

YOLOv8 应用轻量级通用上采样算子CARAFE

特征上采样是现代卷积神经网络架构中的关键操作,例如特征金字塔。其设计对于密集预测任务,如目标检测和语义/实例分割至关重要。在本研究中,我们提出了一种称为内容感知特征重组(CARAFE)的通用、轻量级且高效的操作符,以实现这一目标。CARAFE具有以下几个优点:(1)大的…

模型评估与选择

一、问题 在现实任务中&#xff0c;我们往往有多种学习算沾了供选择&#xff0c;甚至对同 -个学习算法&#xff0c;当使用不同的参数配置时也会产生不 同的模型 . 那么&#xff0c;我们该选用 哪一个学习算法、使用哪一种参数配置呢? 理想的解决方案当然是对候选模型的泛化误…

Java线程详解

线程是CPU调度和分配的基本单位&#xff0c;是操作系统可以识别的最小执行和调度单位&#xff0c;每个线程都有自己特定的独立的内存区域&#xff0c;当然也与其他线程共享堆内存&#xff0c;文件队列以及其他内核资源&#xff0c;Java虚拟机允许一个应用拥有多个线程并发工作。…

AI绘画——Lora模型 小人书·连环画 xiaorenshu

目录 小人书连环画 使用说明&#xff1a; Instructions for v1.5: Instructions for v1.0: 下载地址 模型演示&#xff08;多图预警&#xff09; Night Sky YOZORA Model 模型 Lora模型xiaorenshu pastel-mix模型pastel-waifu-diffusion.vae模型Lora模型xiaorenshu 小人书…

JavaSE05|数组的定义与使用

文章目录 JavaSE05|**数组的定义与使用**1.数据基本概念**2.** **数组是引用类型****3.** **数组的应用场景** JavaSE05|数组的定义与使用 1.数据基本概念 定义&#xff1a;可以看成是相同类型元素的一个集合 1.1数组初始化 动态初始化&#xff1a;在创建数组时&#xff0c…

使用 Kubectl Patch 命令更新资源

Kubectl patch 命令允许用户对运行在 Kubernetes 集群中的资源进行局部更新。相较于我们经常使用的 kubectl apply 命令&#xff0c;kubectl patch 命令在更新时无需提供完整的资源文件&#xff0c;只需要提供要更新的内容即可。 Kubectl patch 支持以下 3 种 patch 类型&…