[C++]20.实现红黑树。

实现红黑树

  • 一.基本概念:
    • 1.红黑树的概念:
    • 2.红黑树的性质:
  • 二.实现红黑树:
    • 1.基本结构:
    • 2.插入节点的多种情况:
      • 1.叔叔存在且为红:
      • 2.叔叔不存在/存在且为黑(单旋+变色)
      • 3.叔叔不存在/存在且为黑(多旋(不是一个单一方向比较长)+变色)
      • 4.总体插入代码:
    • 3.红黑树的验证:
      • 1.验证搜索树:
      • 2.验证性质:
        • 1.根节点是黑色
        • 2. 没有连续的红色节点出现
        • 3.每一条路径的黑色节点个数相同
    • 4.红黑树和AVL树的比较:
      • 1. 随机值判断:
        • 1.100
        • 2.10000
        • 3.1000000
        • 4.10000000
      • 2.有序值判断:
        • 1.100
        • 2.10000
        • 3.1000000
        • 4.10000000
  • 三.map和set的封装:
    • 1.概念:
      • 1.set
      • 2.map
    • 2.基本结构:
      • 1.set
      • 2.map
    • 3.插入
      • 1.set
      • 2.map
      • 3.总结:
    • 4.迭代器实现:
      • 1.set
      • 2.map
      • 3.红黑树部分中实现迭代器的++ -- != *
        • 1.实现operator++
        • 2.实现operator--
    • 5.查找:
      • 1.map
    • 6.operator[]的重载
      • 1.map特有:
      • 2.operator[]重载
      • 3.insert的优化

一.基本概念:

1.红黑树的概念:

红黑树是一种二叉搜索树,但是在每一个存储节点上面增加了一个成员变量用来记录当前节点的颜色,可以是RED 或者 BLACK 。 通过从根节点到任意一个叶子节点的着色限制,红黑树可以确保没有任何一条路径会比最短路径的两倍还要长,因此红黑树是接近平衡的。

2.红黑树的性质:

1.每一个节点不是红色就是黑色。
2.根节点必须是黑色。
3.如果一个节点是红色它的两个孩子节点是黑色---->不存在连续的红节点。
4.一个根节点出去的所有路径,路径的黑色节点个树必须相同。
5.每一个叶子节点都是黑色的,这个叶子是nullptr不是实际已经开辟好的节点。

二.实现红黑树:

1.基本结构:

在这里插入图片描述

2.插入节点的多种情况:

1.叔叔存在且为红:

在这里插入图片描述

2.叔叔不存在/存在且为黑(单旋+变色)

在这里插入图片描述

1.新增节点是父亲的左边,并且父亲是爷爷的左边。
进行右旋+变色处理
2.新增节点是父亲的右边,并且父亲是爷爷的右边。
进行右旋+变色处理

3.叔叔不存在/存在且为黑(多旋(不是一个单一方向比较长)+变色)

在这里插入图片描述

1.新增节点是父亲的右边,并且父亲是爷爷的左边。
进行左旋+右旋+变色处理
2.新增节点是父亲的左边,并且父亲是爷爷的右边。
进行右旋+左旋+变色处理

4.总体插入代码:

template<class T>
class RBTree {
	typedef RBTreeNode<T> Node;
public:
	//1.插入:
	bool insert(pair<T, T> x)
	{
		Node* newnode = new Node(x);
		//1.开始_root == nullptr:
		if (_root == nullptr)
		{
			_root = newnode;
			_root->c = BLACK;
			return true;
		}
		//2.有节点的情况下进行插入:
		else
		{
			//2_1:向下找插入位置:
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				if (x.first > cur->_date.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (x.first < cur->_date.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;
				}
			}

			//2_2:新增节点:
			if (x.first > parent->_date.first)
			{
				parent->_right = newnode;
				newnode->_parent = parent;
			}
			else if (x.first < parent->_date.first)
			{
				parent->_left = newnode;
				newnode->_parent = parent;
			}

			//确定一下新增节点:
			cur = newnode;

			//2_3:向上进行调整:
			while (parent && parent->c == RED)
			{
				Node* grandfather = parent->_parent;
				if (parent == grandfather->_left)
				{
					Node* uncle = grandfather->_right;
					//1.叔叔存在且为红:
					if (uncle && uncle->c == RED)
					{
						//1-1:变色:
						parent->c = BLACK;
						uncle->c = BLACK;
						grandfather->c = RED;
						//1-2:数据更新
						cur = grandfather;
						parent = cur->_parent;
					}
					//2.叔叔不存在/存在且为黑:
					else
					{
						//右旋+变色
						if (cur == parent->_left)
						{
							right_turn(grandfather);
							grandfather->c = RED;
							parent->c = BLACK;
						}
						//左旋+右旋+变色
						else
						{
							left_turn(parent);
							right_turn(grandfather);
							grandfather->c = RED;
							cur->c = BLACK;
						}
						break;
					}

					
				}

				else if (parent == grandfather->_right)
				{
					Node* uncle = grandfather->_left;
					//1.叔叔存在且为红:
					if (uncle && uncle->c == RED)
					{
						//1-1:变色:
						parent->c = BLACK;
						uncle->c = BLACK;
						grandfather->c = RED;
						//1-2:数据更新
						cur = grandfather;
						parent = cur->_parent;
					}

					//2.叔叔不存在/存在且为黑:
					else
					{
						else
					{
						//左旋+变色
						if (cur == parent->_right)
						{
							left_turn(grandfather);
							grandfather->c = RED;
							parent->c = BLACK;
						}
						//右旋+左旋+变色
						else
						{
							right_turn(parent);
							left_turn(grandfather);
							grandfather->c = RED;
							cur->c = BLACK;
						}

						break;
					}
					}

					
				}
			}

			//处理根节点的特殊情况:
			_root->c = BLACK;

			return true;
		}
	}

	//左旋:
	void left_turn(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		//特殊情况的判断:
		if (subRL != nullptr)
			subRL->_parent = parent;

		Node* ppNode = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;

		//当前的parent是子树还是根

		//1.作为根:
		if (ppNode == nullptr)
			_root = subR;
		//2.作为子树考虑左右
		else
		{
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else if (ppNode->_right == parent)
				ppNode->_right = subR;

			subR->_parent = ppNode;
		}
		
	}

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

		parent->_left = subLR;
		//特殊情况的判断:
		if (subLR != nullptr)
			subLR->_parent = parent;

		Node* ppNode = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;

		//当前的parent是子树还是根

		//1.作为根:
		if (ppNode == nullptr)
			_root = subL;
		//2.作为子树考虑左右
		else
		{
			if (ppNode->_left == parent)
				ppNode->_left = subL;
			else if (ppNode->_right == parent)
				ppNode->_right = subL;

			subL->_parent = ppNode;
		}
		
	}


	//中序遍历:
	void _Inorder(Node* cur)
	{
		if (cur == nullptr)
			return;

		_Inorder(cur->_left);
		//cout << "date:" << cur->_date << "平衡因子:" << cur->_bf << endl;
		cout << (cur->_date).first << endl;
		_Inorder(cur->_right);
	}

	void Inorder()
	{
		if (_root == nullptr)
			return;
		_Inorder(_root);
	}

private:
	Node* _root=nullptr;
};

3.红黑树的验证:

1.验证搜索树:

1.遍历一个vector进行数据插入
2.打印中序遍历二叉树的结果。
3.观察结果是否有序就可以确定是否是一个搜索树。

void text_1()
{
	RBTree<int> T;
	vector<int> num = { 8,6,7,11,5,10,13,12,15 };

	for (auto e : num)
	{
		T.insert(make_pair(e,e));
	}
	
	T.Inorder();
}

在这里插入图片描述

2.验证性质:

1.每一个节点不是红色就是黑色。
2.根节点必须是黑色。
3.如果一个节点是红色它的两个孩子节点是黑色---->不存在连续的红节点。
4.一个根节点出去的所有路径,路径的黑色节点个树必须相同。
5.每一个叶子节点都是黑色的,这个叶子是nullptr不是实际已经开辟好的节点。

1.验证红黑树需要注意不要去验证结论,应该验证控制条件的存在。
2.结论:任意一条路径长度都不会大于最短路径的二倍.
控制条件:
1.根节点是黑色.
2.没有连续的红色节点出现。
3.每一条路径的黑色节点个数相同。

在这里插入图片描述

1.根节点是黑色

1.直接去判断根节点的颜色是不是黑色.

2. 没有连续的红色节点出现

1.当前节点的颜色是红节点,父亲是红节点就出现了连续的红节点.
2.出现了连续的红节点就返回false.

3.每一条路径的黑色节点个数相同

1.先去计算出来最左或者最右路径的黑色节点的个数.
2.进行传参在递归过程到叶子节点去判断计算路径和递归路径的黑色节点的个数是否相同.

bool _IsRBTree(Node* cur , int count , int& refnumber)
	{
		if (cur == nullptr)
		{
			//记录数据的判断:
			if (count == refnumber)
				return true;

			cout << "当前黑色节点的路径长度不匹配" << endl;
			return false;
		}

		//判断没有连续的红色节点出现:如果当前子节点为红,父节点就不可以是红。
		if (cur->c == RED && cur->_parent->c == RED)
		{
			cout<< "存在连续的红色节点出现问题" << endl;
			return false;
		}
		
		//记录长度:
		if (cur->c == BLACK)
			count++;

		return _IsRBTree(cur->_left,count,refnumber);
		return _IsRBTree(cur->_right, count, refnumber);
	}

	bool IsRBTree()
	{
		//1.根节点不是黑色判断:
		if (_root->c == RED)
		{
			cout << "根节点不是黑色" << endl;
			return false;
		}

		//2.计算最左或者最右路径的黑节点个数:
		Node* cur = _root;
		while (cur)
		{
			if (cur->c == BLACK)
				black++;
			cur = cur->_left;
		}

		int count = 0;
		return _IsRBTree(_root,count,black);
	}

4.红黑树和AVL树的比较:

1.插入相同的数据比较红黑树和AVL树.
2.比较插入数据的总时间
3.比较插入数据的高度
4.比较插入数据的旋转次数

1. 随机值判断:


void text_4()
{
	AVL_Tree<int> T1;
	RBTree<int> T2;

	vector<int> num;
	//生成随机值:

	int count = 100;
	srand(time(nullptr));

	for (int i = 0; i < count; i++)
	{
		num.push_back(i+rand());
	}

	int b1 = clock();
	for (auto e : num)
	{
		//cout << "e:" << e << endl;
		T1.Insert(make_pair(e, e));
	}
	int e1 = clock();
	for (auto e : num)
	{
		//cout << "e:" << e << endl;
		T2.insert(make_pair(e, e));
	}
	int e2 = clock();

	//插入时间:
	cout << "AVLinserttime :" << e1 - b1 << endl;
	cout << "RBinserttime :" << e2 - e1 << endl;
	//高度:
	cout << "AVLhight: " << T1.hight() << endl;
	cout << "RBhight: " << T2.hight() << endl;
	//旋转次数:
	cout << "AVLspin:" << T1.spin() << endl;
	cout << "RBspin:" << T2.spin() << endl;

}
1.100

在这里插入图片描述

2.10000

在这里插入图片描述

3.1000000

在这里插入图片描述

4.10000000

在这里插入图片描述

2.有序值判断:

void text_4()
{
	AVL_Tree<int> T1;
	RBTree<int> T2;

	vector<int> num;
	//生成随机值:

	int count = 10000000;
	//srand(time(nullptr));

	for (int i = 0; i < count; i++)
	{
		num.push_back(i);
	}

	int b1 = clock();
	for (auto e : num)
	{
		//cout << "e:" << e << endl;
		T1.Insert(make_pair(e, e));
	}
	int e1 = clock();
	for (auto e : num)
	{
		//cout << "e:" << e << endl;
		T2.insert(make_pair(e, e));
	}
	int e2 = clock();

	//插入时间:
	cout << "AVLinserttime :" << e1 - b1 << endl;
	cout << "RBinserttime :" << e2 - e1 << endl;
	//高度:
	cout << "AVLhight: " << T1.hight() << endl;
	cout << "RBhight: " << T2.hight() << endl;
	//旋转次数:
	cout << "AVLspin:" << T1.spin() << endl;
	cout << "RBspin:" << T2.spin() << endl;

}
1.100

在这里插入图片描述

2.10000

在这里插入图片描述

3.1000000

在这里插入图片描述

4.10000000

在这里插入图片描述

三.map和set的封装:

1.概念:

1.set

1.set底层使用红黑树进行数据的存储。
2.进一步的封装是因为BRTree的结构并不适合关联式容器set。
3.set同时需要实现多种容器的方法所以进行进一步的封装BRTree是非常有必要的。
4.进一步的封装有利于控制模板参数类型。

2.map

1.map底层使用红黑树进行数据的存储。
2.进一步的封装是因为BRTree的结构并不适合关联式容器map。
3.map同时需要实现多种容器的方法所以进行进一步的封装BRTree是非常有必要的。
4.进一步的封装有利于控制模板参数类型。

2.基本结构:

1.set

#include"RBTree.h"

namespace sfpy {
	template<class T>
	class myset {
	public:

	private:
		RBTree<T,T> _t;
	};
}

2.map

#include"RBTree.h"

namespace sfpy {
	template<class T , class V >
	class mymap {
	public:

	private:
		RBTree<T, pair<T, V>> _t;
	};
}

在这里插入图片描述

3.插入

1.set

在这里插入图片描述

如何统一数据大小的比较?
1.在set中实现一个仿函数并且重载operator()提取可以进行比较的数值。
2.直接实现一个内部类并且重载operator()方法并且考虑返回的类型值。

在这里插入图片描述

2.map

在这里插入图片描述

如何统一数据大小的比较?
1.在set中实现一个仿函数并且重载operator()提取可以进行比较的数值。
2.map中的问题:pair这个数据类型进行大小的比较是first大就大,在first相同的情况下,second大就大,类型本身进行大小的比较是不符合我们当前的要求的,进行仿函数重载提取数据直接进行比较。
3.直接实现一个内部类并且重载operator()方法并且考虑返回的类型值。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.总结:

1.分别在myset和mymap中去实现两个类并且重载operator()进行比较数据值的提取。
2.在operator()定义的时候要考虑返回值的类型是否是可以进行比较的数值。
3.优点:通过一个模板参数可以实例化两份不同的树,不需要自己手写。

4.迭代器实现:

1.迭代器本质就是对指针的二次封装,并且重载适合的方法,为什么需要进行二次封装?
2.因为:本身的结构并不可以满足++ – * 等需求!
2.对于链表,顺序表结构,指针和迭代器进行++ – * 操作效果相同不需要非常复杂的进行封装。
3.对于set和map这样的结构底层是通过红黑树进行实现的,就红黑树来说没有好的方法提供给我们去实现++ – * 等操作。

1.set

namespace sfpy {
	template<class T>
	class myset {
	public:
		struct bitter {
			const T& operator()(const T& x)
			{
				return x;
			}
		};

		//1.插入:
		bool _insert(T x)
		{
			return _t.insert(x);
		}

		//2.迭代器:
		typedef typename RBTree<T, T, bitter>::_iterator iterator;

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

		iterator end()
		{
			return nullptr;
		}

	private:
		RBTree<T,T, bitter> _t;
	};
}

2.map

namespace sfpy {
	template<class T , class V >
	class mymap {
	public:
		struct bitter {
			const T operator()(const pair<T,V>& x)
			{
				return x.first;
			}
		};

		//1.插入:
		bool _insert(pair<T, V> x)
		{
			return _t.insert(x);
		}

		//2.迭代器:
		typedef  typename RBTree<T, pair<T, V>, bitter>::_iterator iterator;

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

		iterator end()
		{
			return nullptr;
		}

	private:
		RBTree<T, pair<T, V>, bitter> _t;
	};
}

3.红黑树部分中实现迭代器的++ – != *

1.实现operator++

实现operator++
1.中序遍历:左子树 根节点 右子树
2.假设:当前节点是8说明左树已经遍历完了
–>判断当前右子树为不为空
---->右子树不是空找右子树的最左节点。
3.假设:当前节点是11,11(cur)是父亲(parent)的右
可以正常回去—>进行cur和parent的更新向上找下一个节点
—>直到parent->left == cur 说明当前的parent节点还没有遍历到。

在这里插入图片描述

2.实现operator–

实现operator–
1.++ 找右数的最左节点 ,–找左数的最右节点。
2.其他内容保持和++相反。

template<class T , class ret , class ptr>
struct RBT_Iterator {
	typedef RBTreeNode<T> Node;
	//返回一个迭代器类型
	typedef RBT_Iterator<T,ret,ptr> self;
	Node* node;

	RBT_Iterator(Node* x = nullptr)
		:node(x)
	{}

	self& operator++()
	{
		//左 中 右
		if (node->_right)
		{
			Node* cur = node->_right;
			//Node* cur = node;
			//找当前节点的最左节点:
			while (cur->_left)
			{
				cur = cur->_left;
			}
			node = cur;
		}
		//cur->right 没有的!
		else
		{

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

		return *this;
	}

	self& operator--()
	{
		//右 中 左
		if (node->_left)
		{
			Node* cur = node->_left;
			//找当前节点的最右节点:
			while (cur->_right)
			{
				cur = cur->_right;
			}
			node = cur;
		}
		//cur->right 没有的!
		else
		{

			Node* cur = node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = cur->_parent;
			}
			node = parent;
		}
		return *this;
	}

	ret operator*()
	{
		return node->_date;
	}

	ptr operator->()
	{
		return &(node->_date);
	}

	bool operator!=(const self& x)
	{
		return node != x.node;
	}
};

template<class K , class V , class Bitter>
class RBTree {
	typedef RBTreeNode<V> Node;
public:
	typedef RBT_Iterator<V, const V& ,const V*> _iterator;
	//1.插入:

5.查找:

1.我们的set和map在封装红黑树的时候对当前封装的红黑树的类模板进行重新的规划。
2.对于set :RBTree<T,T, bitter> _t; ,set不需要实现find set存数据所决定。
3. 对于map ==RBTree<T, pair<T, V>, bitter> _t;==主要是为了方便实现find的查找功能。

1.map

//3.查找:
		V find(T x)
		{
			pair<T, V> ret = _t._find(make_pair(x, V()));
			return ret.second;
		}


	//找数据:
	V _find(V x)
	{
		Bitter kot;
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (kot(x) > kot(cur->_date))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(x) < kot(cur->_date))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return cur->_date;
			}
		}

		return V();
	}

6.operator[]的重载

1.map特有:

1.operator[]是通过修改insert返回值类型为pair<iterator,bool>类型进行的一个实现:
2.返回值ret接受到返回值,访问迭代器中并且因为迭代器重载了operator->拿到对应的数据。
3.注意:新增节点和查询节点的一个区别,通过新增一个pair类型的数据去进行查询,查询到就就返回查询到的节点的迭代器,查询不到就返回新增。

2.operator[]重载

//4.重载operator[]--->insert进行重写
		V& operator[](T x)
		{
			pair<iterator,bool> ret =  _t.insert(make_pair(x,V()));
			return ret.first->second;
		}

3.insert的优化

//1.插入:
	pair<_iterator,bool> insert(V x)
	{
		Node* newnode = new Node(x);
		Bitter kot;
		//1.开始_root == nullptr:
		if (_root == nullptr)
		{
			_root = newnode;
			_root->c = BLACK;
			return make_pair(_iterator(newnode), true);
		}
		//2.有节点的情况下进行插入:
		else
		{
			//2_1:向下找插入位置:
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				if (kot(x) > kot(cur->_date))
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (kot(x) < kot(cur->_date))
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return make_pair(_iterator(cur), false);
				}
			}

			//2_2:新增节点:
			if (kot(x) > kot(parent->_date))
			{
				parent->_right = newnode;
				newnode->_parent = parent;
			}
			else if (kot(x) < kot(parent->_date))
			{
				parent->_left = newnode;
				newnode->_parent = parent;
			}

			//确定一下新增节点:
			cur = newnode;

			//2_3:向上进行调整:
			while (parent && parent->c == RED)
			{
				Node* grandfather = parent->_parent;
				if (parent == grandfather->_left)
				{
					Node* uncle = grandfather->_right;
					//1.叔叔存在且为红:
					if (uncle && uncle->c == RED)
					{
						//1-1:变色:
						parent->c = BLACK;
						uncle->c = BLACK;
						grandfather->c = RED;
						//1-2:数据更新
						cur = grandfather;
						parent = cur->_parent;
					}
					//2.叔叔不存在/存在且为黑:
					else
					{
						//右旋+变色
						if (cur == parent->_left)
						{
							right_turn(grandfather);
							grandfather->c = RED;
							parent->c = BLACK;
						}
						//左旋+右旋+变色
						else
						{
							left_turn(parent);
							right_turn(grandfather);
							grandfather->c = RED;
							cur->c = BLACK;
						}
						break;
					}

					
				}

				else if (parent == grandfather->_right)
				{
					Node* uncle = grandfather->_left;
					//1.叔叔存在且为红:
					if (uncle && uncle->c == RED)
					{
						//1-1:变色:
						parent->c = BLACK;
						uncle->c = BLACK;
						grandfather->c = RED;
						//1-2:数据更新
						cur = grandfather;
						parent = cur->_parent;
					}

					//2.叔叔不存在/存在且为黑:
					else
					{
						//左旋+变色
						if (cur == parent->_right)
						{
							left_turn(grandfather);
							grandfather->c = RED;
							parent->c = BLACK;
						}
						//右旋+左旋+变色
						else
						{
							right_turn(parent);
							left_turn(grandfather);
							grandfather->c = RED;
							cur->c = BLACK;
						}

						break;
					}

					
				}
			}

			//处理根节点的特殊情况:
			_root->c = BLACK;
			return make_pair(_iterator(newnode), true);
		}
	}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Wi-Fi 6E简介:扩展Wi-Fi的频谱资源

一、Wi-Fi 6E是什么&#xff1f; Wi-Fi 6E是Wi-Fi 6的一个增强版本&#xff0c;其中的“E”代表“Extended”。它采用了相同的技术标准&#xff0c;但使用了更高的频段。Wi-Fi 6E在5 GHz频段之外引入了新的6 GHz频段&#xff0c;为用户提供了更多的可用频谱&#xff0c;以便提…

滑动窗口算法(1)

目录 基本概念 209.长度最小的子数组 一、题目描述 二、思路解析 三、代码 3.无重复字符的最长子串 一、题目描述 二、思路解析 三、代码 1004.最大连续1的个数 一、题目描述 二、思路解析 三、代码 1658.将x减到0的最小操作数 一、题目描述 二、思路解析 三、…

JSP-内置对象

Out对象 作用&#xff1a;用来给页面输出数据。 主要方法&#xff1a; Print(string) </br>换行 Println(string) 方法同上 <%page language"java" contentType"text/html;charsetUTF-8"%> <html><head><title>test.…

基于深度学习的图像去雨去雾

基于深度学习的图像去雨去雾 文末附有源码下载地址 b站视频地址&#xff1a; https://www.bilibili.com/video/BV1Jr421p7cT/ 基于深度学习的图像去雨去雾&#xff0c;使用的网络为unet&#xff0c; 网络代码&#xff1a; import torch import torch.nn as nn from torchsumm…

Prompt提示工程上手指南:基础原理及实践(二)-Prompt主流策略

前言 上篇文章将Prompt提示工程大体概念和具体工作流程阐述清楚了&#xff0c;我们知道Prompt工程是指人们向生成性人工智能&#xff08;AI&#xff09;服务输入提示以生成文本或图像的过程中&#xff0c;对这些提示进行精炼的过程。生成人工智能是一个根据人类和机器产生的数…

【Python】使用plt库绘制动态曲线图,并导出为GIF或MP4

一、绘制初始图像 正常使用plt进行绘图&#xff0c;这里举例一个正弦函数&#xff1a; 二、绘制动态图的每一帧 思路&#xff1a; 根据横坐标点数绘制每一帧画面每次在当前坐标处&#xff0c;绘制一个点和垂直的线&#xff0c;来表示当前点可以在点上加个坐标等样式来增加…

Gut Microbes | 新生儿微生物组研究的方法学挑战

摘要 新生儿出生后&#xff0c;肠道菌群的定植对新生儿的健康发育起着至关重要的作用&#xff0c;并影响其日后的健康和疾病。了解新生儿肠道菌群的发育以及其与新生儿宿主的相互作用是一个重要的研究领域。然而&#xff0c;该领域的研究必须解决影响研究方法设计和实施的一系…

【Java系列】OOM 时,JVM 堆栈信息保存和分析

一、前言 在日常开发中&#xff0c;即使代码写得再谨慎&#xff0c;免不了还是会发生各种意外的事件&#xff0c;比如服务器内存突然飙高&#xff0c;又或者发生内存溢出(OOM)。当发生这种情况时&#xff0c;我们怎么去排查&#xff0c;怎么去分析原因呢&#xff1f; 一般遇到…

展厅设计中灯光的要点都是什么

1、白炽灯 白炽灯也就是普通普通白炽灯泡白炽灯有显色性强&#xff0c;开灯即亮&#xff0c;明暗可调&#xff0c;结构简单&#xff0c;造价低等优点&#xff0c;但缺点是使用寿命短&#xff0c;光效较低展厅设计中常使用于走道和其他部位。 2、卤钨灯 充气白炽灯填充气体中含有…

代码随想录day20(1)二叉树:二叉树的最小深度(leetcode111)

题目要求&#xff1a;求出一棵二叉树的最小深度 思路&#xff1a;最小深度指的是从根节点到最近叶子节点的最短路径上的节点数量&#xff08;左右孩子必须都为空&#xff01;&#xff09;。思路类似于求二叉树的最大深度&#xff0c;仍然采用后序遍历&#xff0c;增加判断只有…

Docker启动安装nacos(踩过坑版)

1、Docker 拉取镜像 docker pull nacos/nacos-server:v2.1.0 2、创建宿主机挂载目录 mkdir -p /mydata/nacos/logs/ mkdir -p /mydata/nacos/conf/ 3、启动nacos并复制文件到宿主机&#xff0c;关闭容器 启动容器 docker run -p 8848:8848 --name nacos -d nacos/nacos-se…

通过Maven创建Web工程

通过Maven创建Web工程 方式一方式二 方式一 1.先创建一个Maven工程 2.把该Maven模块的pom文件里添加一个war 3.选中该Maven模块 点击项目架构 4.手动添加一个Web架构 方式二 1.也是new一个模块 但是直接配置好Web 2.这里就是我IDEA对Maven的设置 3.第一次创建 可能…

【C++】STL--String

这一节主要总结string类的常见接口&#xff0c;以及完成了string类的模拟实现。 目录 标准库的String类 string类常见接口 string类对象的常见构造 string析构函数&#xff1a;~string string类对象的容量操作 string类对象的访问及遍历操作 string类对象的修改操作 s…

jvm题库详解

1、JVM内存模型 注意&#xff1a;这个是基于jdk1.8之前的虚拟机&#xff0c;在jdk1.8后 已经没有方法区&#xff0c;一并合并到堆中的元空间了 JVM内存区域总共分为两种类型 线程私有区域&#xff1a;程序计数器、本地方法栈和虚拟机栈 线程共享区域&#xff1a;堆&#xff08…

让若依生成的service、mapper继承mybatisPlus的基类

前言&#xff1a;若依继承mybatisPlus后&#xff0c;生成代码都要手动去service、serviceImpl、mapper文件去继承mybatisplus的基类&#xff0c;繁琐死了。这里通过修改若依生成模版从而达到生成文件后直接使用mybatisPlus的方法。 一、首先找到若依生成模版文件位置&#xff…

顶顶通呼叫中心中间件-群集模式配置

文章目录 群集模式介绍联系我们配置流程群集模式下呼叫线路配置 群集模式介绍 在大规模的外呼或者呼入系统&#xff0c;比如整个系统需要1万并发&#xff0c;单机最高也就3000-5000并发&#xff0c;这时候就需要多机群集了。顶顶通呼叫中心中间件使用的是 redis 数据库&#x…

你选的Six Sigma咨询公司靠谱吗?保姆级避坑指南

近年来&#xff0c;企业为了追求更高的运营效率和产品质量&#xff0c;纷纷寻求Six Sigma这样的先进管理方法。然而&#xff0c;市场上的咨询公司琳琅满目&#xff0c;如何选择一家真正靠谱、能带来实际效益的咨询公司呢&#xff1f; 一、了解公司背景和实力 在选择Six Sigma咨…

msdn我告诉你itellyou做一个安静的工具站,各种windows镜像下载,iso体积都是很小的那种

官网地址&#xff1a;MSDN, 我告诉你 - 做一个安静的工具站 可以看到里面集成了各种操作系统&#xff0c;可以下载使用。 或者在他的新站点&#xff1a;登录 里面有最新的windows11系统可以下载&#xff0c;但是需要登陆之后才可以&#xff0c;随便第三方账号登陆即可&…

【Devin AI】全球首位AI程序员登场,程序员该如何保住饭碗?编程新纪元的革命已到来!

程序员们&#xff0c;警惕&#xff01;我们的饭碗要被砸了&#xff01; 一觉醒来&#xff0c;全球首位AI程序员 Devin 上线了&#xff01;直接引爆整个科技圈。 Devin被介绍为世界首个完全自主的AI软件工程师。只需一句指令&#xff0c;它可端到端地构建和部署整个开发项目。 …

Ajax学习笔记(一):原生AJAX、HTTP协议、AJAX案例准备工作、发送AJAX请求、AJAX 请求状态

目录 一、原生AJAX 1.1AJAX 简介 1.2 XML 简介 1.3 AJAX的特点 二、HTTP协议 三、AJAX案例准备工作 四、发送AJAX请求 1.发送GET请求 2.发送POST请求 3.JSON响应 IE缓存问题&#xff1a; 五、AJAX 请求状态 一、原生AJAX 1.1AJAX 简介 AJAX 全称为 Asynchronous …