【C++】使用红黑树封装map与set

news2024/12/23 11:30:28

文章目录

  • 1. 源码分析
  • 2. 调整红黑树的结构搭建map、set
  • 3. 红黑树的迭代器
    • 3.1 普通迭代器
    • 3.2 const迭代器
    • 3.3 map的operator[ ]
  • 4. 完整代码
    • 4.1 RBTree
    • 4.2 MyMap
    • 4.3 MySet

在这里插入图片描述

对于map与set,它们一个是KV模型,一个是K模型,那我们要写两个红黑树吗?
我们来看一下源码

1. 源码分析

在这里插入图片描述
我很疑惑为什么它们两个都传了两个参数给底层的红黑树,那再看一下红黑树的源码

在这里插入图片描述

我们可以发现,红黑树在底层不知道你传给它的是什么,为了兼容map与set它搞了两个参数。

但是搞了两个参数后,第一个模板参数Key是不是有点多余呢?

  • 对于set而言,两个K确实是有点多余;对于map来说,pair里面也有K,好像也有点多余
  • 如果只留第二个模板参数,_rb_tree_node< Value >,为set时 Value为K;为 map时 Value为pair<K,V>,好像也没问题。
  • 对于插入操作,set是k,map是pair还挺好;但是对于查找操作时,set使用Value没问题;map查找时也要使用Value,但map的Value是一个pair(有K,V),那我既然知道了K和V,我还查找什么呢?所以第一个模板参数不多于,反而是必须

但还有一个问题,在插入操作时,涉及数据比较大小。但是map的Value是一个pair,还得取里面的key;set的Value就是key,此时就比较麻烦。pair默认的比较规则是first比完second比,不符合我们的预期。因此我们还要给红黑树传递一个仿函数,以便获取key。

2. 调整红黑树的结构搭建map、set

那我们之前实现的红黑树的结构就得做一下调整了
在这里插入图片描述
除了上面呈现出来的模板参数以外,还可以传递自定义的比较规则模板Compare。

下面是map与set的基本框架

原来红黑树中涉及数据比较大小的地方,都得回调传递过来的KeyOfT获取Key后再比较
在这里插入图片描述

3. 红黑树的迭代器

3.1 普通迭代器

红黑树的迭代器无非也就是包含一个节点的指针,然后重载指针的各种操作即可。
在这里插入图片描述

一般迭代器的实现都是按照中序遍历,遍历完是有序的。所以红黑树迭代器的begin(),应该是树最左侧的节点;对于迭代器的end(),我们就按照NULL来处理。

在这里插入图片描述
那红黑树迭代器的++与- -操作是如何实现的呢?

对于++操作:
在这里插入图片描述

对于- -操作:为了方便_node == nullptr时找树最右侧的节点,我们再给迭代器加一个指针root记录树的根。
在这里插入图片描述

在这里插入图片描述

template<class Value>
	class RBTreeIterator
	{
		typedef TreeNode<Value> Node;
		typedef RBTreeIterator<Value> Self;

	private:
		Node* _node;
		Node* _root;
	public:
		RBTreeIterator(Node* data,Node* root)
			:_node(data)
			,_root(root)
		{}

		Self& operator++()
		{
			//右树存在,找右树的最左侧节点
			if (_node->_right)
			{
				Node* leftMost = _node->_right;
				while (leftMost && leftMost->_left)
				{
					leftMost = leftMost->_left;
				}
				_node = leftMost;//直接跳转至
			}
			else  //右树不存在,继续向上
			{
				Node* cur = _node;
				Node* parent = cur->_parent;
				while (parent && parent->_right == cur)
				{
					cur = parent;
					parent = cur->_parent;
				}
				//parent不存在或者该遍历根了
				_node = parent;
			}
			return *this;
		}
		Self& operator--()
		{
			//特殊处理end(),找整棵树的最右侧节点
			if (_node == nullptr)
			{
				Node* rightMost = _root;
				while (rightMost && rightMost->_right)
				{
					rightMost = rightMost->_right;
				}
				_node = rightMost;
			}
			//左子树存在,找左子树最右侧节点
			else if (_node->_left)
			{
				Node* rightMost = _node->_left;
				while (rightMost && rightMost->_right)
				{
					rightMost = rightMost->_right;
				}
				_node = rightMost;
			}
			else
			{
				//左子树不存在,当前树遍历完,继续向上
				Node* cur = _node;
				Node* parent = cur->_parent;
				while (parent && parent->_left == cur)
				{
					cur = parent;
					parent = cur->_parent;
				}
				//parent不存在或者该遍历根了
				_node = parent;
			}
			return *this;
		}

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

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

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

3.2 const迭代器

对于const迭代器而言,无非就是数据不能被修改,那也就是针对迭代器的operator* 与operator->了。

那就先改造底层的迭代器
在这里插入图片描述

然后map与set在上层封一下就行了

在这里插入图片描述
通过上面的我们可以看到,使用const迭代器以后确实都不能修改了;但是普通迭代器存在一个问题:
在这里插入图片描述

所以上层在传递给下层时,我们可以将k设置为const
在这里插入图片描述
到这里我们的迭代器就完美了。

3.3 map的operator[ ]

我们都知道map重载的[ ]既可以充当插入,也可以充当修改。那么在底层它用的就是insert。(点击可看map的使用)
那我们就需要改变一下insert的返回值了。
在这里插入图片描述
在这里插入图片描述

到这里我们map与set的封装就结束了。

4. 完整代码

4.1 RBTree

#pragma once
namespace my
{
	//颜色
	enum Color
	{
		RED = 0,
		BLACK
	};

	template<class Value>
	struct TreeNode
	{
		Value _data;
		TreeNode<Value>* _left;
		TreeNode<Value>* _right;
		TreeNode<Value>* _parent;//前驱节点
		Color _col;

		TreeNode(const Value& data)
			: _data(data)
			, _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _col(RED)//默认新节点为红色的
		{}
	};

	template<class Value,class Ref,class Ptr>
	class RBTreeIterator
	{
		typedef TreeNode<Value> Node;
		typedef RBTreeIterator<Value,Ref,Ptr> Self;

	private:
		Node* _node;
		Node* _root;
	public:
		RBTreeIterator(Node* data,Node* root)
			:_node(data)
			,_root(root)
		{}

		Self& operator++()
		{
			//右树存在,找右树的最左侧节点
			if (_node->_right)
			{
				Node* leftMost = _node->_right;
				while (leftMost && leftMost->_left)
				{
					leftMost = leftMost->_left;
				}
				_node = leftMost;//直接跳转至
			}
			else  //右树不存在,继续向上
			{
				Node* cur = _node;
				Node* parent = cur->_parent;
				while (parent && parent->_right == cur)
				{
					cur = parent;
					parent = cur->_parent;
				}
				//parent不存在或者该遍历根了
				_node = parent;
			}
			return *this;
		}
		Self& operator--()
		{
			//特殊处理end(),找整棵树的最右侧节点
			if (_node == nullptr)
			{
				Node* rightMost = _root;
				while (rightMost && rightMost->_right)
				{
					rightMost = rightMost->_right;
				}
				_node = rightMost;
			}
			//左子树存在,找左子树最右侧节点
			else if (_node->_left)
			{
				Node* rightMost = _node->_left;
				while (rightMost && rightMost->_right)
				{
					rightMost = rightMost->_right;
				}
				_node = rightMost;
			}
			else
			{
				//左子树不存在,当前树遍历完,继续向上
				Node* cur = _node;
				Node* parent = cur->_parent;
				while (parent && parent->_left == cur)
				{
					cur = parent;
					parent = cur->_parent;
				}
				//parent不存在或者该遍历根了
				_node = parent;
			}
			return *this;
		}

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

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

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

	template<class K,class Value,class KeyOfT>
	class RBTree
	{
		typedef TreeNode<Value> Node;
	public:
		typedef RBTreeIterator<Value,Value&,Value*> Iterator;
		typedef RBTreeIterator<Value,const Value&,const Value*> ConstIterator;

		Iterator Begin()
		{
			Node* leftMost = _root;
			//找最左侧的节点
			while (leftMost && leftMost->_left)
			{
				leftMost = leftMost->_left;
			}

			return Iterator(leftMost,_root);
		}
		Iterator End()
		{
			return Iterator(nullptr,_root);
		} 

		ConstIterator Begin() const
		{
			Node* leftMost = _root;
			//找最左侧的节点
			while (leftMost && leftMost->_left)
			{
				leftMost = leftMost->_left;
			}

			return ConstIterator(leftMost,_root);
		}
		ConstIterator End() const
		{
			return ConstIterator(nullptr,_root);
		}

		RBTree() = default;
		
		RBTree(const RBTree<K, Value,KeyOfT>& tree)
		{
			this->_root = Copy(tree._root);
		}
		//类里面直接写类名,无需传递模板参数也可。
		RBTree& operator=(RBTree tree)
		{
			swap(this->_root, tree._root);//现代写法
			return *this;
		}
		~RBTree()
		{
			Destroy(_root);
		}

		pair<Iterator,bool> insert(const Value& data)
		{
			if (_root == nullptr)
			{
				_root = new Node(data);
				//如果插入的节点是头节点,需要遵守规则2(根是黑的)
				//需要默认的红色改为黑
				_root->_col = BLACK;
				return make_pair(Iterator(_root,_root),true);
			}
			KeyOfT kot;
			Node* cur = _root;
			Node* parent = cur->_parent;
			while (cur)
			{
				if (kot(cur->_data) < kot(data))
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (kot(cur->_data) > kot(data))
				{
					parent = cur;
					cur = cur->_left;
				}
				else
					return make_pair(Iterator(cur, _root), false);
			}
			//孩子连父亲
			cur = new Node(data);
			Node* newnode = cur;
			cur->_parent = parent;
			//父亲连孩子
			if (kot(parent->_data) < kot(data))
				parent->_right = cur;
			else
				parent->_left = cur;

			//检查是否违反红黑树的规则
			//如果父亲存在且为红,则违反规则
			while (parent && parent->_col == RED)
			{
				Node* grandfather = parent->_parent;
				if (grandfather->_left == parent)
				{
					//根据爷爷节点找叔叔
					Node* uncle = grandfather->_right;

					//如果叔叔存在且为红,仅需要变色
					if (uncle && uncle->_col == RED)
					{
						parent->_col = BLACK;
						uncle->_col = BLACK;
						grandfather->_col = RED;
						//然后继续向上调整
						cur = grandfather;
						parent = cur->_parent;
					}
					//如果叔叔不存在 或者存在且为黑,需要旋转+变色
					else
					{
						//判断单旋(直树)还是双旋(弯树)
						//单旋
						if (parent->_left == cur)
						{	
							//       g
							//     p   u
							//	c
							RotateR(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						//双旋
						else
						{
							//        g
							//     p     u
							//	     c
							RotateL(parent);
							RotateR(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;

						}
						break;//旋转后已经符合规则,无需再调整了
					}
				}
				//grandfather->_right == parent
				else
				{
					Node* uncle = grandfather->_left;
					//如果叔叔存在且为红,仅需要变色
					if (uncle && uncle->_col == RED)
					{
						parent->_col = BLACK;
						uncle->_col = BLACK;
						grandfather->_col = RED;
						//继续调整
						cur = grandfather;
						parent = cur->_parent;
					}
					//如果叔叔不存在 或者存在且为黑,需要旋转+变色
					else
					{
						//    g
						// u     p
						//           c
						if (parent->_right == cur)
						{
							RotateL(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						//      g
						//   u      p
						//	      c
						else
						{
							RotateR(parent);
							RotateL(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}
						break;
					}
				}
			}
			//父亲不存在,或者父亲为黑时,无需判断,直接将根变黑
			_root->_col = BLACK;
			return make_pair(Iterator(newnode, _root), true);
		}

		Iterator* find(const K& key)
		{
			Node* cur = _root;
			KeyOfT kot;
			while (cur)
			{
				if ( kot(cur->_data)< key)
					cur = cur->_right;
				else if (kot(cur->_data) > key)
					cur = cur->_left;
				else
					return  Iterator(cur,_root);
			}
			return End();
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

		bool IsRBTree()
		{
			Node* root = _root;
			if (root == nullptr)//空树也是
				return true;
			
			if (root->_col == RED)
			{
				cout << "根节点为红色,违反规则" << endl;
				return false;
			}

			//先统计最左侧路径上有多少黑节点作为标准
			Node* cur = root;
			int blackNum = 0;
			while (cur)
			{
				if (cur->_col == BLACK)
					blackNum++;
				cur = cur->_left;
			}
			//k用来记录路径中黑节点的个数
			int k = 0;
			//在依据标准黑节点的个数,比较其它路径
			return _IsRBTree(root, blackNum, k);
		}

		int Size()
		{
			return _Size(_root);
		}

		int Height()
		{
			return _Height(_root);
		}

	private:
		int _Height(Node* root)
		{
			if (root == nullptr)
				return 0;
			int leftH = _Height(root->_left);
			int rightH = _Height(root->_right);

			return leftH > rightH ? leftH + 1 : rightH + 1;
		}

		int _Size(Node* root)
		{
			if (root == nullptr)
				return 0;

			return 1 + _Size(root->_left) + _Size(root->_right);
		}

		bool _IsRBTree(Node* root, int blackNum, int k)
		{
			//一条路径走完,比较黑节点的个数
			if (root == nullptr)
			{
				if (blackNum != k)
				{
					cout << "路径黑节点个数不相等,违反规则" << endl;
					return false;
				}
				return true;
			}
			Node* parent = root->_parent;
			if (parent && root->_col == RED && parent->_col == RED)
			{
				cout << "存在连续的红节点,违反规则" << endl;
				return false;
			}

			//统计黑节点
			if (root->_col == BLACK)
				k++;
			//统计完根,在依次统计左右子树
			return _IsRBTree(root->_left, blackNum, k)
				&& _IsRBTree(root->_right, blackNum, k);
		}

		void RotateR(Node* parent)
		{
			Node* SubL = parent->_left;
			Node* SubLR = SubL->_right;

			//降级连兵
			parent->_left = SubLR;
			if (SubLR)
				SubLR->_parent = parent;
			Node* parentParent = parent->_parent;
			//升级连将
			SubL->_right = parent;
			parent->_parent = SubL;

			//将连将
			SubL->_parent = parentParent;
			if (parentParent == nullptr)
			{
				_root = SubL;
			}
			else
			{
				if (parentParent->_left == parent)
					parentParent->_left = SubL;
				else
					parentParent->_right = SubL;
			}
		}

		void RotateL(Node* parent)
		{
			Node* SubR = parent->_right;
			Node* SubRL = SubR->_left;

			//降级连兵
			parent->_right = SubRL;
			if (SubRL)
				SubRL->_parent = parent;
			Node* parentParent = parent->_parent;
			//升级连将
			SubR->_left = parent;
			parent->_parent = SubR;

			//将连将
			SubR->_parent = parentParent;
			if (parentParent == nullptr)
				_root = SubR;
			else
			{
				if (parentParent->_left == parent)
					parentParent->_left = SubR;
				else
					parentParent->_right = SubR;
			}
		}
		void Destroy(Node* root)
		{
			if (root == nullptr)
				return;
			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
		}

		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;
			_root->_left = Copy(root->_left);
			_root->_right = Copy(root->_right);
			return _root;
		}

		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;
			_InOrder(root->_left);
			cout << root->_kv.first << " ";
			_InOrder(root->_right);
		}

		Node* _root;
	};
}

4.2 MyMap

#pragma once
#include"RBTree.h"

namespace my
{
	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<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>::ConstIterator const_iterator;
		const_iterator begin()const
		{
			return _t.Begin();
		}

		const_iterator end() const
		{
			return _t.End();
		}
		iterator begin()
		{
			return _t.Begin();
		}

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

		pair<iterator, bool> insert(const pair<K, V> kv)
		{
			return _t.insert(kv);
		}

		bool find(const K& key)
		{
			return _t.find(key);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key,V()));
			//如果key存在,则返回其对应的value
			//如果key不存在,则插入它,其value为类型的默认值
			return ret.first->second;
		}
	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};

	//void MapPrint(const map<string, string>& m)
	//{
	//	map<string, string>::const_iterator it = m.end();
	//	while (it != m.begin())
	//	{
	//		it->first += "x";
	//		it->second += "y";
	//		--it;
	//		cout << it->first << " " << it->second << endl;
	//	}
	//}

	void testmap()
	{
		map<string, string> m;
		m.insert({ "right","右"});
		m.insert({ "up","上"});
		m.insert({ "down","下"});
		m.insert({ "left","左"});

		m["left"] = "修改左";
		m["offer"] = "新增";

		//cout << m.find(3) << endl;

		//for (auto& e : m)
		//{
		//	cout << e.first << " " << e.second << endl;
		//}
		//MapPrint(m);

		map<string,string>::iterator it = m.end();
		while (it != m.begin())
		{
			//it->second += "y";
			//it->first += "x";
			--it;
			cout << it->first << " " << it->second << endl;
		}
		cout << endl;
	}
}

4.3 MySet

#pragma once
#include"RBTree.h"

namespace my
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, const K,SetKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, const K,SetKeyOfT>::ConstIterator const_iterator;

		const_iterator begin() const
		{
			return _t.Begin();
		}
		const_iterator end() const
		{
			return _t.End();
		}

		iterator begin()
		{
			return _t.Begin();
		}
		iterator end()
		{
			return _t.End();
		}

		pair<iterator, bool> insert(const K& key)
		{
			return _t.insert(key);
		}

		bool find(const K& key)
		{
			return _t.find(key);
		}
	private:
		RBTree<K, const K, SetKeyOfT> _t;
	};

	//void SetPrint(const set<int>& s)
	//{
	//	set<int>::const_iterator it = s.end();
	//	while (it != s.begin())
	//	{
	//		//*it = 20;
	//		--it;
	//		cout << *it << " ";
	//	}
	//	cout << endl;
	//}

	void testset()
	{
		set<int> s;
		pair<set<int>::iterator,bool> ret = s.insert(5);
		cout << *(ret.first) << endl;//查看insert的返回值

		s.insert(2);
		s.insert(8);
		s.insert(15);
		s.insert(2);
		s.insert(9);
		set<int>::iterator it = s.end();
		while (it != s.begin())
		{
			//*it = 10;
			--it;
			cout << *it <<" ";
		}
		cout << endl;
		//SetPrint(s);
	}
}

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

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

相关文章

基于Springboot网上蛋糕售卖店管理系统的设计与实现--论文pf

TOC springboot504基于Springboot网上蛋糕售卖店管理系统的设计与实现--论文pf 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和…

解决springboot中Aspect注解不生效问题

如下图所示&#xff0c;配置了一个注解类型的Aspect&#xff0c;结果一直不生效 运行结果可以看到&#xff0c;其他非注解类型的Aspect都顺利执行了&#xff0c;但是这个注解的切面就是没有执行 当时也在网上搜了半天&#xff0c;包括在启动类增加配置&#xff0c;接口都要加上…

Java语言程序设计基础篇_编程练习题**16.30(模式识别:连续四个相同的数)

目录 题目&#xff1a;**16.30&#xff08;模式识别&#xff1a;连续四个相同的数&#xff09; 习题思路 代码示例 结果展示 题目&#xff1a;**16.30&#xff08;模式识别&#xff1a;连续四个相同的数&#xff09; 为编程练习题8.19编写一个GUI程序。让用户在6行7列的网格的…

实时手势识别(2)- 基于关键点分类实现零样本图片的任意手势的识别

目录 前言 1.实现效果 2.关键点分类网络 3.KPNet训练测试数据准备 4.训练结果 4.1训练过程可视化 4.2验证集上的混淆矩阵 4.测试结果 4.1不同规模模型的测试结果对比 4.2分类结果投影到第一象限 4.3测试集上的混淆矩阵 4.4 二义性手势结果 4.5视频实测 5.零样本的…

深入理解计算机系统 CSAPP 实验lab:Architecture Lab

前期准备参考: 深入理解计算机系统 CSAPP 第四章 Y86-64模拟器 安装与使用-CSDN博客 writeup上写了要求,这里就不赘述了. Part A: sum.ys: # Execution begins at address 0 .pos 0irmovq stack, %rsp # Set up stack pointercall main # Execute main programhalt # …

【CRT实用小技巧(1)】一键发送命令到所有会话窗口

CRT实用小技巧&#xff08;1&#xff09;一键发送命令到所有会话窗口 在view&#xff08;视图&#xff09;中打开command windows&#xff08;命令窗口&#xff09; 打开后&#xff0c;可以看到下面的命令窗口了 然后再下面的命令窗口中&#xff0c;右键出菜单&#xff0c;选择…

centos7突然掉电后启动报错/dev/mapper/centos-root does not exist

问题现象 物理服务器SR588已安装centos7&#xff0c;突然掉电后系统进不去&#xff0c;提示/dev/mapper/centos-root does not exist 问题解决过程 网上搜索相关的解决方法 1、 用此方法不行 2、 用此方法也是不行 3、 分析查看ls /dev/mapper&#xff0c;目录底下没有cent…

Kafka运行机制(一):Kafka集群启动,controller选举,生产消费流程

前置知识 Kafka基本概念https://blog.csdn.net/dxh9231028/article/details/141270920?spm1001.2014.3001.5501 1. Kafka集群启动 Kafka在启动集群中的各个broker时&#xff0c;broker会向controller注册自己&#xff0c;并且从controller节点同步集群元数据。 broker是Kaf…

栈和堆的区别

存储内容不同&#xff1a; 栈区&#xff1a;主要存储函数调用时的局部变量、函数参数等。栈底是函数调用后的下一条指令。 堆区&#xff1a;存储动态分配的内存&#xff0c;如使用 new 分配的内存&#xff0c;具体内容由程序员安排。 管理方式不同&#xff1a; 栈区&#x…

牛客刷题二叉树(三)

1.输出二叉树的右视图 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可** 求二叉树的右视图* param preOrder int整型一维数组 先序遍历* param inOrder int整型一维数组 中序…

【无线通信发展史⑤】万有引力定律的推导前奏3.0,本次学习开普勒三大定律,成就“天空立法者”的美名

前言&#xff1a;用这几个问答形式来解读下我这个系列的来龙去脉。如果大家觉得本篇文章不水的话希望帮忙点赞收藏加关注&#xff0c;你们的鼓舞是我继续更新的动力。 我为什么会写这个系列呢&#xff1f; 首先肯定是因为我本身就是一名从业通信者&#xff0c;想着更加了解自…

高性能 Web 服务器:让网页瞬间绽放的魔法引擎(中)

目录 一.Nginx版本和安装方式:源码编译安装 1.验证版本及编译参数 2.使用安装完成的二进制文件nginx 3.Nginx 启动文件 二.平滑升级和回滚 三.全局配置 实现 nginx 的高并发配置 四.核心配置&#xff1a;新建一个 PC web 站点 五.核心配置&#xff1a;location的详细使用…

vscode 远程控制ssh操作步骤

1.下载拓展Remote - SSH 打开Visual Studio Code&#xff0c;进入拓展市场(CtrlShiftX)&#xff0c;下载拓展Remote - SSH 2.选择远程(隧道/SSH)类别 点击远程资源管理器选项卡&#xff0c;并选择远程(隧道/SSH)类别 4.在弹出的选择配置文件中&#xff0c;点击第一个 5. 连接到…

基于PHP网上投票系统---附源码121500

摘 要 随着全球Internet的迅猛发展和计算机应用的普及&#xff0c;特别是近几年无线网络的广阔覆盖以及无线终端设备的爆炸式增长&#xff0c;使得人们能够随时随地的访问网络&#xff0c;以获取最新信息、参与网络活动、和他人在线互动。为了能及时地了解民情民意&#xff0c;…

深度学习基础—学习率衰减与局部最优问题

1.学习率衰减 下图中&#xff0c;蓝色的线是min-batch梯度下降法过程中较大学习率的的优化路径&#xff0c;绿线是较小学习率的优化路径。 如果使用min-batch梯度下降法&#xff0c;在模型的学习过程中&#xff0c;会有很多噪声&#xff0c;在靠近最小值的时候&#xff0c;由于…

考试:数据库系统(02)

关系代数 ◆并&#xff1a;结果是两张表中所有记录数合并&#xff0c;相同记录只显示一次。 ◆交&#xff1a;结果是两张表中相同的记录。 ◆差&#xff1a;S1-S2, 结果是S1表中有而S2表中没有的那些记录。 ◆笛卡尔积&#xff1a;S1*S2,产生的结果包括S1和S2的所有属性列&…

Redis7基础篇(二)

目录 持化双雄 RDB 案例演示 优势 劣势 AOF 案例演示 正常恢复 异常恢复 优点 缺点​编辑 aof重写机制 ​编辑​编辑重写的原理​编辑 小总结 rdb和aof的混合持久化 纯缓存模式 redis的持久化最终还是靠硬盘 持化双雄 redis提供了一rdb aof 不持久化 rdbaof的…

ansible相关模块

copy模块(重点) copy模块⽤于对⽂件的远程拷⻉操作&#xff08;如把本地的⽂件拷⻉到远程 的机器上) https://docs.ansible.com/ansible/latest/modules/copy_module.htm l#copy-module 在master上准备⼀个⽂件&#xff0c;拷⻉此⽂件到group1的所有机器上 使⽤content参数直…

Linux之 宝塔面板 通过binlog日志恢复被删除表的数据

参考文章 查找mysql的binlog文件位置 这个命令是用来将MySQL的二进制日志文件(mysql-bin.000021)转换成SQL格式的文件(000021.sql)。 /www/server/mysql/bin/mysqlbinlog --base64-outputDECODE-ROWS -v mysql-bin.000021 > /www/000021.sql 提取sql语句 [rootdata]# …

访问网站出现“此站点不安全”如何解决

在网络浏览中&#xff0c;我们经常会遇到浏览器地址栏出现“此站点不安全”的警告。这通常意味着网站没有使用SSL&#xff08;安全套接层&#xff09;加密来保护用户数据的安全。那么&#xff0c;如何通过获得并安装SSL证书来消除这一警告&#xff0c;确保网站的安全可靠呢&…