map和set底层实现【C++】

news2024/11/8 21:54:01

文章目录

  • map和set模板参数
  • 红黑树结点中的数据
  • 模板参数中的仿函数
  • 正向迭代器
    • ++运算符重载
    • --运算符重载
  • 库里的写法
  • set
  • map
  • RBTree

map和set模板参数

set是K模型的容器,而map是KV模型的容器
如何用一棵KV模型的红黑树同时实现map和set

  template<class K ,class V>
	class map
	{
	// ...
	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t; //map,MapKeyOfT是仿函数
	};
	class set
	{
	//...
	private:
		RBTree<K,K,SetKeyOfT> _t;
	};//set,SetKeyOfT是仿函数

红黑树结点中的数据

红黑树的节点是K还是T,对于set都是一样的 ,对于map,底层红黑树就只能存储T了。底层红黑树不知道上层容器是map还是set,因此红黑树的结点当中直接存储T就行了。

当上层容器是set,结点当中存储的是键值Key;当上层容器是map,结点当中存储的就是<Key, Value>键值对。

template<class T >
struct RBTreeNode
{
	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{

	}
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Colour _col;//颜色
};

模板参数中的仿函数

	class set
	{
		//仿函数
		struct SetKeyOfT
		{
			const K & operator()( const K &key)
			{
				return key;
			}
		};
		private:
		RBTree<K,K,SetKeyOfT> _t;
	};
class map
	{
		//仿函数
		struct MapKeyOfT
		{
			 
			const K& operator()( const pair<K,V> & kv)
			{
				 return kv.first;
			}
		};
		private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};

当上层容器是set,T就是键值Key,直接用T进行比较即可
当上层容器是map,此时我们需要从<Key, Value>键值对当中取出键值Key后,再用Key值进行比较
上层容器map需要向底层红黑树提供一个仿函数,用于获取T当中的键值Key,当底层红黑树当中需要比较两个结点的键值时,就可以通过这个仿函数来获取T当中的键值了

对于底层红黑树,它并不知道上层容器是map还是set,因此当需要进行两个结点键值的比较时,底层红黑树都会通过传入的仿函数来获取键值Key,进而进行两个结点键值的比较。

因此,set容器也需要向底层红黑树传入一个仿函数,虽然这个仿函数单独看起来没什么用,但却是必不可少的。

当底层红黑树需要进行两个结点之间键值的比较时,都会通过传入的仿函数来获取相应结点的键值,然后再进行比较 例如:

//查找函数
iterator Find(const K& key)
{
	KeyOfT kot;
	Node* cur = _root;
	while (cur)
	{
		if (key < kot(cur->_data)) //key值小于该结点的值
		{
			cur = cur->_left; //在该结点的左子树当中查找
		}
		else if (key > kot(cur->_data)) //key值大于该结点的值
		{
			cur = cur->_right; //在该结点的右子树当中查找
		}
		else //找到了目标结点
		{
			return iterator(cur); //返回该结点
		}
	}
	return end(); //查找失败
}

正向迭代器

struct  _TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef _TreeIterator<T,Ptr,Ref> Self;
	typedef _TreeIterator<T, T*, T&> iterator;
};
//构造函数
__TreeIterator(Node* node)
	:_node(node) //根据所给结点指针构造一个正向迭代器
{}

++运算符重载

1、右子树不为空,访问右树的最左节点(即为下一个节点)
2、如果当前结点的右子树为空,则++操作后应该在该结点的祖先结点中,找到孩子不在父亲右的祖先。


	Self & operator++()//前置++,返回++之后的值
	{
		//右子树不为空,访问右树的最左节点
		//左子树不为空,如何解决?
		if (_node->_right != nullptr)
		{
			//右树的最左节点(右树的最小节点)
			Node* subLeft = _node->_right;
			while (subLeft->_left != nullptr)
			{
				subLeft = subLeft->_left;
			}
			_node = subLeft;
		 }
		else//_node->_left != nulltptr
		{
			// 下一个要访问的节点,找孩子是父亲左的那个祖先节点

			Node* cur = _node;
			Node* parent = cur->_parent;

			while (parent!=nullptr)
			{
				if (parent->_left != cur)
				{
					//往上更新
					cur = parent;
					parent = parent->_parent;
				}
				else//parent->_left == cur
				{
					break;
				}
			
			}
			_node = parent;
		}
		return *this;
	}

–运算符重载

1、左子树存在 ,找当前节点的左子树中最右边的节点,就是所需要的节点
2、如果当前结点的左子树为空,则–操作后应该在该结点的祖先结点中,找到孩子不在父亲左的祖先。

	Self& operator--()//前置-- 
		//右子树 、根 、左子树(中序反过来) 
	{
		 
		// 左子树存在 
		//将当前节点的左子树中最右边的节点,就是所需要的节点
		if (_node->_left != nullptr)
		{
			Node* subRight = _node->_left;
			while (subRight && subRight->_right)
			{
				subRight = subRight->_right;
			}
			_node = subRight;
		}
		//右子树存在
		//找孩子是父亲的右的那个节点
		else//_node->_right != nullptr
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent!=nullptr)
			{
				//向上调整 
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;		
		}
		return *this;
	}

库里的写法

上述所实现的迭代器是有缺陷的,因为理论上我们对end()位置的正向迭代器进行–操作后,应该得到最后一个结点的正向迭代器,但我们实现end()时,是直接返回由nullptr构造得到的正向迭代器的,因此上述实现的代码无法完成此操作

C++SLT库当中的实现逻辑:
在这里插入图片描述
在红黑树的根结点处增加了一个头结点,
该头结点的左指针指向红黑树当中的最左结点,
右指针指向红黑树当中的最右结点,父指针指向红黑树的根结点。

在该结构下,实现begin()时,直接用头结点的左孩子构造一个正向迭代器即可,实现rbegin()时,直接用头结点的右孩子构造一个反向迭代器即可(实际是先用该结点构造一个正向迭代器,再用正向迭代器构造出反向迭代器),而实现end()和rend()时,直接用头结点构造出正向和反向迭代器即可。此后,通过对逻辑的控制,就可以实现end()进行–操作后得到最后一个结点的正向迭代器。

set

#pragma once 

#include "RBTree.h"
namespace cxq
{
	template<class K>//set只有一个模板参数
	class set
	{
		//仿函数
		struct SetKeyOfT
		{
			const K & operator()( const K &key)
			{
				return key;
			}
		};
	public:
		typedef  typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef  typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

	
		iterator begin() const 
		{
			return _t.begin();
		}
		iterator end() const 
		{
			return _t.end();
		}
		pair<iterator, bool> insert(const K &key)
		{
			
			//return _t.insert(kv); //这样写return的是pair<RBTree::iterator , bool>,所以不能这样写

			//库里面的写法
			pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key); //这个iterator是普通迭代器
			return pair<iterator, bool>(ret.first, ret.second);//这个iterator是const_iterator
		}
	private:
		RBTree<K,K,SetKeyOfT> _t;
	};
}

map

#pragma once 
#include"RBTree.h"
namespace cxq
{
  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>::const_iterator  const_iterator;

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

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

		 pair<iterator, bool> insert(const pair<K,V > & kv)
		{
			return _t.Insert(kv);
		}
	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;

	};
}

RBTree

#pragma once 
#include<iostream>
#include<assert.h>
#include<vector>
using namespace std;
enum Colour
{
	RED,
	BLACK
};

template<class T >
struct RBTreeNode
{
	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{

	}
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Colour _col;//颜色


};

template <class T,class Ptr,class Ref>
struct  _TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef _TreeIterator<T,Ptr,Ref> Self;
	typedef _TreeIterator<T, T*, T&> iterator;

	//??
	_TreeIterator(const iterator & it)
		:_node(it._node)
	{

	}


	//构造函数 
	_TreeIterator(Node* node)
		:_node(node)
	{

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

	 Ptr operator->()// T * operator->()
	{
		return &_node->_data;
	}
	bool operator!=(const Self & s)
	{
		return _node != s._node;
	}
	Self& operator--()//前置-- 
		//右子树 、根 、左子树(中序反过来) 
	{
		 
		// 左子树存在 
		//将当前节点的左子树中最右边的节点,就是所需要的节点
		if (_node->_left != nullptr)
		{
			Node* subRight = _node->_left;
			while (subRight && subRight->_right)
			{
				subRight = subRight->_right;
			}
			_node = subRight;
		}
		//右子树存在
		//找孩子是父亲的右的那个节点
		else//_node->_right != nullptr
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent!=nullptr)
			{
				//向上调整 
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;		
		}
		return *this;
	}
	Self & operator++()//前置++,返回++之后的值
	{
		//右子树不为空,访问右树的最左节点
		//左子树不为空,如何解决?
		if (_node->_right != nullptr)
		{
			//右树的最左节点(右树的最小节点)
			Node* subLeft = _node->_right;
			while (subLeft->_left != nullptr)
			{
				subLeft = subLeft->_left;
			}
			_node = subLeft;
		 }
		else//_node->_left != nulltptr
		{
			// 下一个要访问的节点,找孩子是父亲左的那个祖先节点

			Node* cur = _node;
			Node* parent = cur->_parent;

			while (parent!=nullptr && cur == parent->_right)
			{
				//	往上更新
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
	Node* _node;

};


template <class K, class T,class KeyOfT >
struct RBTree
{
	typedef RBTreeNode<T> Node;

public:
	//同一个类模板,传的不同参数,实例化不同的类型
	typedef _TreeIterator<T,T* ,T&> iterator;
	typedef _TreeIterator<T, const T* , const T &> const_iterator;//const的位置如何选择 
public:
	iterator begin()
	{
		//最左边即最小值节点
		Node* leftMin = _root;
		while (leftMin!=nullptr && leftMin->_left!=nullptr)
		{
			leftMin = leftMin->_left;

		}
		return iterator(leftMin);//支持单参数构造支持隐式类型转换

	}
	iterator end() 
	{
		return iterator(nullptr);//支持单参数构造支持隐式类型转换
	}


	const_iterator begin()const
	{
		//最左边即最小值节点
		Node* leftMin = _root;
		while (leftMin != nullptr && leftMin->_left != nullptr)
		{
			leftMin = leftMin->_left;

		}
		return const_iterator(leftMin);//支持单参数构造支持隐式类型转换

	}
	const_iterator end()const
	{
		return const_iterator(nullptr);//支持单参数构造支持隐式类型转换
	}
	Node * Find(const K & key)
	{
		Node* cur = _root;
		KeyOfT kot;
		while (cur!= nullptr)
		{	
			if (kot(cur->_data) < key)
			{
				cur = cur->_left;
			}
			else if (kot(cur->_data) > key)
			{
				cur = cur->_right;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
	pair<iterator, bool> Insert(const T & data)
	{
		//找到插入位置

		//空树
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);
		}
		//不是空树 
		Node* cur = _root;
		Node* parent = nullptr;
		KeyOfT kot;

		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);
		cur->_col = RED;
		Node* newnode = cur;
		//将插入节点插入到树中 
		if (kot(parent->_data) > kot(data) ) //用仿函数 
		{
			parent->_left = cur;
		}
		else//kot(parent->_data) < kot(data)
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//如果插入节点的父节点是红色,分三种情况进行调整 
		while (parent!=nullptr &&parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			assert(grandfather);
			assert(grandfather->_col == BLACK);


			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//插入结点的叔叔存在,且颜色是红色
				if (uncle != nullptr && uncle->_col == RED)
				{


					//parent 、uncle  变黑
					//grandfather变红
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					//向上处理 
					cur = grandfather;
					parent = cur->_parent;
				}
				//插入结点的叔叔存在,且颜色是黑色
				//插入节点的叔叔不存在
				//这两种情况统一处理
				else  if (uncle != nullptr && uncle->_col == BLACK || uncle == nullptr)
				{
					//先旋转再变色

					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//cur == parent->_right 
					{
						RotateL(parent);
						RotateR(grandfather);

						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}

			}
			else//parent == grandfather->_right
			{
				Node* uncle = grandfather->_left;
				//插入结点的叔叔存在,且颜色是红色
				if (uncle != nullptr && uncle->_col == RED)
				{


					//parent 、uncle  变黑
					//grandfather变红
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					//向上处理 
					cur = grandfather;
					parent = cur->_parent;
				}
				//插入结点的叔叔存在,且颜色是黑色
				//插入节点的叔叔不存在
				//这两种情况统一处理
				else  if (uncle != nullptr && uncle->_col == BLACK || uncle == nullptr)
				{
					//先旋转再变色

					if (cur == parent->_left)
					{
						//双旋	
						
						RotateR(parent);
						RotateL(grandfather); 


						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					else//cur == parent->_right 
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}

			}
		}



		_root->_col = BLACK;
		return make_pair(iterator(newnode),true);
	}
	void RotateL(Node* parent)
	{
		_rotateCount++;
		Node* cur = parent->_right;

		Node* curleft = cur->_left;

		//parent和curleft链接
		parent->_right = curleft;
		if (curleft != nullptr)
		{
			
			curleft->_parent = parent;
		}
		//cur和parent链接
		cur->_left = parent;

	
		Node* ppnode = parent->_parent;
		parent->_parent = cur;

		//parent 是根节点
		if (parent == _root)
		{
			cur->_parent = nullptr;
			_root = cur;
		}
		else//parent 是一个子树 
		{
			//parent是一个左子树
			

		
			if (ppnode->_left == parent)
			{
				//3和ppnode链接
				ppnode->_left = cur;
				cur->_parent = ppnode;
			}
			//parent是一个右子树
			else
			{
				//3和ppnode链接
				ppnode->_right = cur;
				cur->_parent = ppnode;
			}
		}

	}
	void RotateR(Node* parent)
	{
		_rotateCount++;
		Node* cur = parent->_left;
		Node* curright = cur->_right;

		//cur和parent 链接 

		cur->_right = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = cur;

		//curright和parent链接 
		parent->_left = curright;
		if (curright != nullptr)
		{
			curright->_parent = parent;
		}



		
		if (parent == _root)//parent是根节点
		{
			cur->_parent = nullptr;
			_root = cur;//更新根节点
		}
		else//parent一个子树 
		{
			//parent是左子树 
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
				cur->_parent = ppnode;
			}
			else//(ppnode->_right == parent)
				//parent是右子树 
			{
				ppnode->_right = cur;
				cur->_parent = ppnode;
			}
		}


	}

	bool CheckColour(Node* root, int blacknum, int benchMark)
	{
		if (root == nullptr)
		{
			//blacknum是一条路径上的黑色节点总数 
			if (blacknum != benchMark)
			{
				return  false;
			}
			return false;
		}
		else if (root->_col == BLACK)
		{
			blacknum++;

		}
		else if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << "连续的红节点:" << root->_kv.first << endl;
			return false;
		}

		return   CheckColour(root->_left, blacknum, benchMark)
			&& CheckColour(root->_right, blacknum, benchMark);
	}
	bool IsBalance()
	{
	  return 	_IsBalance(_root);
	}

	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return  true;
		}
		//根节点必须是黑色
		if (root->_col != BLACK)
		{
			return false;
		}
		//基准值
		//求最左路径的黑色节点
		int benchMark = 0;
		Node* cur = _root;
		while (cur != nullptr)
		{
			if (cur->_col == BLACK)
			{
				benchMark++;
			}
			cur = cur->_left;
		}
		return CheckColour(root, 0, benchMark);
	}
	int Height()
	{
		return _Height(_root);
	}
    int _Height(Node *root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		return  leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}
private:
	Node* _root = nullptr;
public:
	int _rotateCount = 0;
};

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

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

相关文章

简单的聊一聊如何使用CSS的父类Has选择器

最近的:has()选择器允许您对父元素和其他祖先应用样式&#xff0c;本文将向您展示如何在Web应用程序开发中使用它。 在CSS的世界中&#xff0c;选择器是驱动我们在网页上看到的美丽且响应式设计的工作的马。它们允许开发者根据元素的属性、位置和关系来选择和样式化HTML元素。 …

OPPO手机如何添加日程桌面插件?

对于不少网友来说&#xff0c;每天生活、学习、工作相关的日程事项非常多&#xff0c;为了避免自己在忙碌中忘记待办的日程&#xff0c;就会在手机的待办APP中逐条记录下来当天的日程安排。不过还有一些小伙伴表示&#xff0c;如果忘记定时查看这些待办事项怎么办呢&#xff1f…

智慧公厕:打造便捷环保的城市出行新体验

公共厕所&#xff0c;作为城市基础设施的一部分&#xff0c;一直以来备受关注。然而&#xff0c;传统的公厕管理方式往往难以满足人们对于卫生、舒适、便捷的需求。为了改善这一现状&#xff0c;智慧公厕应运而生。 智慧公厕&#xff0c;顾名思义&#xff0c;是运用物联网、云…

V90伺服驱动器控制(PN版本)

V90伺服驱动器脉冲控制常用参数和接线,请查看下面文章链接: SMART PLC和V90伺服实现外部脉冲位置控制-CSDN博客SMART PLC脉冲轴控功能块详细介绍请参看下面文章链接:S7-200 SMART PLC自定义脉冲轴控功能块AxisControl_FB(梯形图)_RXXW_Dor的博客-CSDN博客博途1200/1500PL…

django 项目基本配置

项目工程初始化 安装框架 pip install django使用命令创建项目 django-admin startproject 项目名称效果 根目录创建apps用以放置所有包 切换至apps目录创建子应用 python ../manage.py startapp usermuxi_shop_back/settings.py # Build paths inside the project lik…

Android JKS MD5 SHA1 公钥生成 私钥生成 APP备案 内容获取

1 查看 jks keytool -list -v -keystore /Users/lipengfei/Desktop/android/androidproject.jks密钥库类型: jks 密钥库提供方: SUN您的密钥库包含 1 个条目别名: ddgj 创建日期: 2018-11-16 条目类型: PrivateKeyEntry 证书链长度: 1 证书[1]: 所有者: CNcn, OUcn, Ocn, Lcn,…

Chrome浏览器免费广告拦截器插件 —— Adblock

背景&#xff1a;我们在浏览网页的过程中经常会有看到一些广告&#xff0c;这些广告不仅影响正常浏览&#xff0c;甚至会遮挡主要内容&#xff0c;给用户造成极大困扰。今天分享一去除广告或者指定元素的优秀插件——Adblock 屏蔽广告前&#xff1a;页面中总是会出现一些广告 …

CVE-2023-38831漏洞实例

一、漏洞样本 DIPLOMATIC-CAR-FOR-SALE-BMW.rar样本&#xff0c;在我们双击DIPLOMATIC-CAR-FOR-SALE-BMW.pdf解压并打开时&#xff0c;就会触发漏洞运行同目录下的同文件名程序DIPLOMATIC-CAR-FOR-SALE-BMW.pdf .cmd&#xff1a; 二、恶意载荷分析 释放文档并打开&#xff0c…

广告流量变现:解析数字时代的商机

在数字时代&#xff0c;广告流量变现成为了许多企业和个人的重要商机。通过巧妙地利用广告流量&#xff0c;可以实现盈利和增加收入的目标。本文将深入探讨广告流量变现的概念、方法和策略&#xff0c;帮助读者更好地把握这一商机。 一、广告流量变现的概念 广告流量变现是指通…

39 WEB漏洞-XXEXML之利用检测绕过全解

目录 涉及案例pikachu靶场xml数据传输测试-回显、玩法、协议、引入玩法-读文件玩法-内网探针或攻击内网应用(触发漏洞地址)玩法-RCE引入外部实体dtd无回显-读取文件协议-读文件&#xff08;绕过&#xff09;xxe-lab靶场登陆框xml数据传输测试-检测发现CTF-Jarvis-OJ-Web-XXE安全…

智能低压配电房解决方案

随着科技的发展&#xff0c;数字化经济的提出&#xff0c;以及各行各业在数字化经济发展的浪潮中&#xff0c;配电房的数字化、智能化、安全化、节能化提供了新的解决方案和新思路。 系统概述&#xff1a; 力安科技智能低压配电房通过在电力设备终端加装电力探测器、物…

通信原理板块——数字数据压缩编码之霍夫曼编码

1、数字数据压缩编码基本原理 数据分为数字数据和模拟数据&#xff0c;此处的数据指的是数字数据或数字化后的模拟数据 (1)数字数据压缩编码要求 数据与语音或图像不同&#xff0c;对其压缩是不允许有任何损失&#xff0c;只能采用无损压缩的方式。压缩编码选用一种高效的编码表…

微信好友消息自动回复,让你轻松应对好友咨询

有许多用微信做业务、做微商的小伙伴&#xff0c;微信有时候消息太多看不过来&#xff0c;漏看消息&#xff0c;或者不知道怎么引导用户&#xff0c;让他们看到你想让他们看到的消息。微信上用户多微信上的信息容易漏掉&#xff0c;怎么能有时效的回复客户呢&#xff1f;此时你…

Mybatis-plus连接postgrel数据库主键自增问题

首先pg中没有直接设置主键自增这一说法&#xff0c;一般只能新建一个序列&#xff0c;可以使用Navicat创建 在mp的配置类中加入序列的配置&#xff1a; Bean public IKeyGenerator keyGenerator() {return new H2KeyGenerator(); }然后实体类的主键策略只能是INPUT&#xff0…

Ps:选框工具

Ps 的选框工具有四个&#xff0c;它们分别是&#xff1a; 矩形选框工具 Rectangular Marquee Tool 椭圆选框工具 Elliptical Marquee Tool 单行选框工具 Single Row Marquee Tool 单列选框工具 Single Column Marquee Tool 快捷键&#xff1a;M 单行和单列选框工具属于特殊…

MathType7.4最新免费版(公式编辑器)下载安装包附安装教程

MathType是一款专业的数学公式编辑器&#xff0c;理科生专用的必备工具&#xff0c;可应用于教育教学、科研机构、工程学、论文写作、期刊排版、编辑理科试卷等领域。可视化公式编辑器轻松创建数学方程式和化学公式。兼容Office Word、PowerPoint、Pages、Keynote、Numbers 等7…

WINCC趋势画面模板

加载按钮 Sub OnClick(Byval Item) Dim Chart,tag,ctrl,objTrendWnd,objTimeAxis,objValAxis,objTrendSet ChartScreenItems("组合框2")Chart tagChart.SelTextSet ctrl ScreenItems("控件1")threadSet objTrend…

为什么说99%的传统视频监控都有问题?

就传统的监控来说&#xff0c;主要还是通过摄像头单纯地记录画面&#xff0c;依赖人力在屏幕前“看”&#xff0c;工作人员长时间盯着十几个监控画面&#xff0c;十分容易疲劳导致注意力不集中&#xff0c;不能及时发现事故并解决。而且传统的视频监控仍以事后查证为主&#xf…

网上可以赚钱的软件,闲暇时间可用来薅羊毛做副业

正因为有了互联网&#xff0c;我们的生活变得越来越便利。我们可以在网上购物、休闲娱乐、与朋友交流&#xff0c;甚至可以通过网上做副业来赚取额外收入。生活中总会有一些业余时间&#xff0c;而很多人也不想浪费它&#xff0c;总想着做一点事情来弥补经济上的不足。因此&…

STP生成树协议详解

一、STP作用 如果链路断开或节点故障&#xff0c;那么互联的设备就无法正常通信了&#xff0c;这类网络问题叫做单点故障。没有备份的链路或节点&#xff0c;出现故障会直接断网。如果要提供 724 小时不间断的服务&#xff0c;那就需要在网络中提前部署冗余。避免出现单点故障…