哈希知识详解

一、哈希

以前,在面对海量数据的查找时,最快就是红黑树 logN,无法满足需求。

于是探索出了另一种用关键字 key 值与其存储位置建立映射的思想,让查找效率提升到 O(1) ,这个就是哈希。

二、哈希函数

  1、直接定值法

        ①、直接定值法:用当前的值与数组建立一一对应的关系。

        例如,a,b,c,d 在数组 v 上的映射为 v[0],v[1],v[2],v[3]

        ②、直接定值法的缺点:如果给的值直接跨度较大就非常消耗空间。

        例如:1,1000 在数组 v 上的映射为 v[1] v[1000] 。为了存两个数开了1000多空间,十分浪费。

  2、除留余数法

        除留余数法就是:映射位置为 key 值模上空间大小。( i = key % 空间大小 )

三、哈希冲突

        用上述方法可能会有哈希冲突的问题(也就是不同的 key 值 映射到了同一个地方)

        如:除留余数法,空间大小为7,key值 1 和 8 会映射在同一个地方(1%7 = 8%7 = 1)

四、哈希冲突解决

1、闭散列(开放定值法)

        假设空间大小为 N

        线性探测:假设用除留余数法,i = key % N,如果找到的当前位置有值了,那就向后找,直到找到空位置,放进去。

        查找:执行 i = (i + 1) % N,不断向后查找(后面找完了,就从第一个位置开始继续找),直到找到空位置。

        可以看到,哈希冲突越多,我们查找的效率越低。因此,我们可以想办法让数据的个数和空间大小维持在一个比例,降低哈希冲突。也就是维持 负载因子 / 载荷因子 的大小

        (负载因子 / 载荷因子 = 数据个数 / 空间大小 )。

        闭散列(开放定值法) 一般将这个比例维持在 0.7 左右。

        删除:如上图,如果我们删除 8,那么查找 16 的时候就找到空位置 2,无法找到 16。因此,我们删除不能只是简单的将值删除,还应该将表的每个位置增设标志位。

 

         闭散列代码

        插入:

        1、判断当前 负载因子 / 载荷因子 是否超过 0.7,如果超过,就扩容。

        2、因为扩容后的空间大小变化了,因此,所有的数据需要重新插入,我们可以定义一个临时对象 HashTable temp,把当前所有数据插入 temp 中,再将 temp 中的数据换出来,完成重新插入。

        3、根据新的空间大小和 key 值,找到映射位置。如果当前位置有值,就向后查找空闲位置(标志位不为 EXIST),找到就插入

        查找:

        1、先判断当前 key 值的映射位置 keyi 是否正确,如果不对,继续向后查找

        2、 如果查找到了,返回下标;如果没找到,找了一圈回到 keyi ,则返回 -1

        删除:

        1、通过 key 用 Find 找到 keyi,如果不存在,就返回

        2、如果存在,则将标志位设为 DELETE 并 --_size  

// 定义的状态
enum Status
{
	EMPTY,
	EXIST,
	DELETE
};

// 哈希表中的数据
template<class K, class V>
struct HashData
{
	typedef HashData<K, V> self;
public:
	HashData()
		:_status(EMPTY)
	{}

	/*HashData(const std::pair<K, V>& value)
	{
		_data = value;
		_status = EXIST;
	}

	self& operator=(const self& val)
	{
		_data = val._data;
		_status = val._status;

		return *this;
	}*/
public:
	std::pair<K, V> _data; // 数据
	Status _status; // 状态
};


template<class K, class V>
class HashTable
{
public:
	static const double rate;
public:
	HashTable(int n = 0)
	{
		_table.resize(n);
	}

	void Insert(const std::pair<K, V>& value)
	{
		// 空间大小
		int n = _table.size();

		// 如果比例超过 rate 就扩容
		if (_size >= rate * n)
		{
			// 扩容为原来的 2 倍 (哈希表的大小最好为素数,这里就简化一下)
			int newcapacity = n == 0 ? 4 : 2 * n;

			// 因为扩容后空间大小变了,因此每个数据的映射位置也会变化,所以要重新插入
			// 定义一个临时的哈希表
			HashTable<K, V> temp(newcapacity);
			// 把原来的数据重新插入新表中
			for (auto e : _table)
			{
				if(e._status == EXIST)
					temp.Insert(e._data);
			}

			// 将新表换出来
			_table.swap(temp._table);
			_size = temp._size;
			n = newcapacity;
		}
		// 通过除留余数法 找到对应下标
		int keyi = value.first % n;

		// 找到第一个标记位不为 EXIST 的位置,放入
		while (_table[keyi]._status == EXIST)
		{
			keyi = (keyi + 1) % n;
		}
		_table[keyi]._data = value;
		_table[keyi]._status = EXIST;
		_size++;
	}
	
    // 找到了就返回下标,没找到返回 -1
	int Find(const K& key)
	{
		int n = _table.size();
		// 通过除留余数法 找到对应下标
		int keyi = key % n;
		if (_table[keyi]._data.first == key)
		{
			return keyi;
		}

		// 从第 2 个位置继续查找
		int pos = (keyi + 1) % n;
		while (_table[pos]._status != EMPTY)
		{
			// 转一圈回来了,全是 DELETE 和 EXIST
			if (pos == keyi)
				break;
			if (_table[pos]._data.first == key)
			{
				return pos;
			}
			pos = (pos + 1) % n;
		}

		return -1;
	}

	void Erase(const K& key)
	{
		int n = _table.size();
		// 如果没找到,就直接返回
		int pos = Find(key);
		if (pos == -1) return;

		// size-- 并设置标志位
		_table[pos]._status = DELETE;
		_size--;
	}

private:
	std::vector<HashData<K, V>> _table; // 哈希表
	int _size; // 当前负载
};
template<class K, class V>
const double HashTable<K, V>::rate = 0.7; // 比例为 0.7

 2、哈希桶

        与闭散列不同,哈希桶(也叫做拉链法) 是在对应位置放单链表,当有哈希冲突的时候,就把值尾插挂在对应位置的单链表下。

        优化:当一个位置的单链表的节点个数超过 8 个时,可以考虑挂红黑树。


          哈希桶的结构

// 哈希表下挂的 单链表的节点
template<class T>
struct HashNode
{
	HashNode()
	{}

	HashNode(const T& val)
		:_val(val)
	{}

	HashNode<T>* _next = nullptr; // next 指针,指向下一个位置
	T _val = T(); // 值
};

// K 表示 key(关键字) 的类型
// T 表示 值 的类型
// KOFT 表示通过 T 找到对应的 key 的仿函数(封装map和set就可以一起使用了)
// map 的值是 pair<K, T>,set 的值和 key 一样,因此,通过传入仿函数就可以让它们一起使用
// HASH 表示哈希函数,如,string 就需要通过哈希函数转化才能求位置
template<class K, class T, class KOFT, class HASH>
class HashBucket
{
    // typedef
	using node = HashNode<T>;
    // 设置 负载因子 / 载荷因子 的比例
	static const double rate;// 静态成员变量,类内定义,类外初始化
		
public:
	HashBucket(int n = 0)
	{
        // 将数组的大小开为 n
		_table.resize(n, nullptr);
	}

private:
	std::vector<node*> _table;// 哈希表,每个位置存一个单链表
	size_t _size;// 数据个数
};
template<class K, class T, class KOFT, class HASH>
const double HashBucket<K, T, KOFT, HASH>::rate = 0.7;// 初始化比例

         查找方法

        1、先判断哈希表是否为空

        2、通过哈希函数得到值,用除留余数法得到对应的下标

        3、通过下标找到单链表 _table[keyi] ,在这个单链表中遍历查找即可

iterator Find(const K& key)
{
    // 如果表是空的,那么不需要查找
	if (_table.size() == 0)
		return end();

	// 通过哈希函数,除留余数法 得到对应下标 keyi
	HASH hashfunc;
	int keyi = hashfunc(key) % _table.size();

	KOFT kot;

	// 从单链表中查找值
	node* head = _table[keyi];
	while (head)
	{
		// 找到返回
		if (kot(head->_val) == key)
		{
			return iterator(head, this);
		}
		head = head->_next;
	}
	// 没找到返回 end()
	return end();
}

        插入方法

        1、先判断 key 值是否已经存在,存在就不插入了

        2、如果 负载因子 / 载荷因子 的比例超过 定义的 rate,扩容

                扩容思想和上面的闭散列类似,因为哈希桶的大小发生变化,要重新插入原有值。我们可以先定义一个临时的大小为 newcapacity 的哈希桶,再将原本的数据插入新的临时哈希桶中,只要把临时桶的资源换出来,就完成了重新插入。

        3、通过哈希函数和 key 找到对应下标,通过下标找到该插入的单链表

        4、创建新节点,头插到单链表中,将 _size++ 完成插入 

// 设计前面的 iterator 可以帮助 运算符[], 拿到迭代器
std::pair<iterator, bool> Insert(const T& val)
{
	// 拿到 key 值
	KOFT kot;
	const K& key = kot(val);

	// 通过Find判断是否存在对应的 key
	iterator it = Find(key);
	if (it != end())
		return std::make_pair(it, false);

	int n = _table.size();
	if (_size >= n * rate)
	{
		// 如果 size 为空,设置初始容量为 4,否则新容量为原来的 2 倍
		int newcapacity = _size == 0 ? 4 : 2 * n;

		// 设置临时对象
		HashBucket temp(newcapacity);
		for (node* head : _table)
		{
			// 因为容量发生变化,所以所有数据的映射位置发生变化
			// 将所有数据重新插入 temp
			while (head)
			{
				temp.Insert(head->_val);
				head = head->_next;
			}
		}

		// 将 temp 中的资源换出来
		_table.swap(temp._table);
		_size = temp._size;
		n = newcapacity;
	}
			
	// 得到对应下标 keyi
	HASH hashfunc;
	int keyi = hashfunc(key) % n;

	// 创建新节点
	node* newnode = new node(val);

	// 头插
	newnode->_next = _table[keyi];
	_table[keyi] = newnode;

	// ++size
	++_size;

	return std::make_pair(iterator(newnode, this), true);
}

        删除方法

        1、和插入一样,先进行查找,如果没找到 key,就直接返回

        2、通过 key 和哈希函数得到对应的下标,找到对应单链表

        3、问题转化为在单链表中查找节点,找到返回即可

bool Erase(const K& key)
{
	// 先进行查找,没找到直接返回
	iterator iter = Find(key);
	if (iter == end())
		return false;

	// 通过 key 得到下标
	HASH hashfunc;
	int keyi = hashfunc(key) % _table.size();

	KOFT kot;

	node* prev = nullptr;
	node* cur = _table[keyi];
	while (cur)
	{
		if (kot(cur->_val) == key)
		{
			if (prev == nullptr)
				_table[keyi] = cur->_next;
			else
				prev->_next = cur->_next;
					
			delete cur;
			break;
		}
		prev = cur;
		cur = cur->_next;
	}
	return true;
}

         析构

        遍历哈希表,将所有单链表释放。 

~HashBucket()
{
	// 释放所有单链表的节点
	for (node* head : _table)
	{
		while (head)
		{
			node* next = head->_next;
			delete head;
			head = next;
		}
	}
}

        迭代器 

        1、迭代器基本结构 

        哈希桶的迭代器需要节点,因为进行++操作时,需要查找表的下一个非空节点,因此还需要这个哈希桶的指针。

         哈希桶的 operator== 和  operator!= 判断节点的地址是否相同即可

        解引用*和->,* 返回值引用,-> 返回值的地址

        注意:因为通过哈希表指针使用了私有成员 _table ,因此要把迭代器设为友元,或设置 GetTable() 函数返回。

// 声明
template<class K, class T, class KOFT, class HASH>
class HashBucket;

template<class K, class T, class KOFT, class HASH>
class __HashIterator
{
public:
	typedef HashNode<T> node;
	typedef HashBucket<K, T, KOFT, HASH> HT;
	typedef __HashIterator<K, T, KOFT, HASH> self;

	__HashIterator(node* node, HT* ht)
		:_node(node)
		,_ht(ht)
	{}

	bool operator!=(const self& iter)
	{
		return _node != iter._node;
	}
	bool operator==(const self& iter)
	{
		return _node == iter._node;
	}
	T& operator*()
	{
		return _node->_val;
	}
	T* operator->()
	{
		return &(_node->_val);
	}

	node* _node;
	HT* _ht;
};

        2、迭代器 ++

        ++iterator,前置++,返回自己

        思路:找到下一个不为空的节点,因为是++,所以不需要回到开头,后面都为空就返回。

self& operator++()
{
	// 如果 _node 为空,说明在 end() 位置,直接返回
	if (_node == nullptr)
		return *this;

	// 如果该节点的 next 不为空,返回它的下一个节点
	if (_node->_next)
	{
		_node = _node->_next;
		return *this;
	}

	// 如果它是该链表的最后一个节点,找下一条链表的头
	//  获取当前节点的映射位置 keyi
	KOFT kot;
	HASH hashfunc;
	const K& key = kot(_node->_val);
	int keyi = hashfunc(key) % _ht->_table.size();

	// 向后查找哈希表中不为空的链表
	while (++keyi < _ht->_table.size())
	{
		// 找到,_node 为当前链表的头,也就是 _table[keyi]
		if (_ht->_table[keyi])
		{
			_node = _ht->_table[keyi];
			break;
		}
	}
	// 没找到,_node 返回空
	if (keyi == _ht->_table.size())
		_node = nullptr;
	return *this;
}

        完整哈希桶代码 

namespace bucket
{
	// 哈希表下挂的 单链表的节点
	template<class T>
	struct HashNode
	{
		HashNode()
		{}

		HashNode(const T& val)
			:_val(val)
		{}

		HashNode<T>* _next = nullptr;
		T _val = T();
	};
	// 声明
	template<class K, class T, class KOFT, class HASH>
	class HashBucket;

	template<class K, class T, class KOFT, class HASH>
	class __HashIterator
	{
	public:
		typedef HashNode<T> node;
		typedef HashBucket<K, T, KOFT, HASH> HT;
		typedef __HashIterator<K, T, KOFT, HASH> self;

		__HashIterator(node* node, HT* ht)
			:_node(node)
			,_ht(ht)
		{}

		bool operator!=(const self& iter)
		{
			return _node != iter._node;
		}
		bool operator==(const self& iter)
		{
			return _node == iter._node;
		}
		T& operator*()
		{
			return _node->_val;
		}
		T* operator->()
		{
			return &(_node->_val);
		}

		self& operator++()
		{
			// 如果 _node 为空,说明在 end() 位置,直接返回
			if (_node == nullptr)
				return *this;

			// 如果该节点的 next 不为空,返回它的下一个节点
			if (_node->_next)
			{
				_node = _node->_next;
				return *this;
			}

			// 如果它是该链表的最后一个节点,找下一条链表的头
			//  获取当前节点的映射位置 keyi
			KOFT kot;
			HASH hashfunc;
			const K& key = kot(_node->_val);
			int keyi = hashfunc(key) % _ht->_table.size();

			// 向后查找哈希表中不为空的链表
			while (++keyi < _ht->_table.size())
			{
				// 找到,_node 为当前链表的头,也就是 _table[keyi]
				if (_ht->_table[keyi])
				{
					_node = _ht->_table[keyi];
					break;
				}
			}
			// 没找到,_node 返回空
			if (keyi == _ht->_table.size())
				_node = nullptr;
			return *this;
		}


		node* _node;
		HT* _ht;
	};

	template<class K, class T, class KOFT, class HASH>
	class HashBucket
	{
		using node = HashNode<T>;
		static const double rate;
		
	public:
		using iterator = __HashIterator<K, T, KOFT, HASH>;
		friend iterator;

		HashBucket(int n = 0)
		{
			_table.resize(n, nullptr);
		}

		// 设计前面的 iterator 可以帮助 运算符[], 拿到迭代器
		std::pair<iterator, bool> Insert(const T& val)
		{
			// 拿到 key 值
			KOFT kot;
			const K& key = kot(val);

			// 通过Find判断是否存在对应的 key
			iterator it = Find(key);
			if (it != end())
				return std::make_pair(it, false);

			int n = _table.size();
			if (_size >= n * rate)
			{
				// 如果 size 为空,设置初始容量为 4,否则新容量为原来的 2 倍
				int newcapacity = _size == 0 ? 4 : 2 * n;

				// 设置临时对象
				HashBucket temp(newcapacity);
				for (node* head : _table)
				{
					// 因为容量发生变化,所以所有数据的映射位置发生变化
					// 将所有数据重新插入 temp
					while (head)
					{
						temp.Insert(head->_val);
						head = head->_next;
					}
				}

				// 将 temp 中的资源换出来
				_table.swap(temp._table);
				_size = temp._size;
				n = newcapacity;
			}
			
			// 得到对应下标 keyi
			HASH hashfunc;
			int keyi = hashfunc(key) % n;

			// 创建新节点
			node* newnode = new node(val);

			// 头插
			newnode->_next = _table[keyi];
			_table[keyi] = newnode;

			// ++size
			++_size;

			return std::make_pair(iterator(newnode, this), true);
		}

		iterator begin()
		{
			// 找到第一个非空单链表的头
			for (auto head : _table)
			{
				if (head)
					return iterator(head, this);
			}

			return iterator(nullptr, this);
		}

		iterator end()
		{
			// 为空代表走到了结尾
			return iterator(nullptr, this);
		}

		iterator Find(const K& key)
		{
			if (_table.size() == 0)
				return end();
			// 得到对应下标 keyi
			HASH hashfunc;
			int keyi = hashfunc(key) % _table.size();

			KOFT kot;

			// 从单链表中查找值
			node* head = _table[keyi];
			while (head)
			{
				// 找到返回
				if (kot(head->_val) == key)
				{
					return iterator(head, this);
				}
				head = head->_next;
			}
			// 没找到返回 end()
			return end();
		}

		bool Erase(const K& key)
		{
			// 先进行查找,没找到直接返回
			iterator iter = Find(key);
			if (iter == end())
				return false;

			// 通过 key 得到下标
			HASH hashfunc;
			int keyi = hashfunc(key) % _table.size();

			KOFT kot;

			node* prev = nullptr;
			node* cur = _table[keyi];
			while (cur)
			{
				if (kot(cur->_val) == key)
				{
					if (prev == nullptr)
						_table[keyi] = cur->_next;
					else
						prev->_next = cur->_next;
					
					delete cur;
					break;
				}
				prev = cur;
				cur = cur->_next;
			}
			return true;
		}

		~HashBucket()
		{
			// 释放所有单链表的节点
			for (node* head : _table)
			{
				while (head)
				{
					node* next = head->_next;
					delete head;
					head = next;
				}
			}
		}

	private:
		std::vector<node*> _table;
		size_t _size;
	};

	template<class K, class T, class KOFT, class HASH>
	const double HashBucket<K, T, KOFT, HASH>::rate = 0.7;
}

五、通过哈希桶封装 unordered_map 和 unordered_set

        1、封装 unordered_set

        

// 设置哈希函数
template<class K>
class HashFunc
{
public:
	int operator()(const K& val)
	{
		return val;
	}
};
// 特化,string
template<>
int HashFunc<std::string>::operator()(const std::string& str)
{
	int key = 0;
	for (char ch : str)
	{
		key += ch;
	}

	return key;
}

// 通过 值 找 key
// 对于 unordered_set 关键字和值都是 key
template<class K>
class KOfT
{
public:
	const K& operator()(const K& key)
	{
		return key;
	}
};

// 哈希函数给了默认的,也可以外面传
// unordered_set<int> 第一个模版参数是 key
template<class K, class HASH= HashFunc<K>>
class unordered_set
{
public:
	// typedef
	using iterator = typename bucket::HashBucket<K, K, KOfT<K>, HASH>::iterator;

	// 插入、修改、查找等函数直接调用 哈希桶的接口即可
	std::pair<iterator, bool> Insert(const K& val)
	{
		return _ht.Insert(val);
	}

	bool Erase(const K& key)
	{
		return _ht.Erase(key);
	}

	iterator Find(const K& key)
	{
		return _ht.Find(key);
	}

	iterator begin()
	{
		return _ht.begin();
	}
	iterator end()
	{
		return _ht.end();
	}


private:
	// 关键字为 K, 值为 k
	bucket::HashBucket<K, K, KOfT<K>, HASH> _ht;
};

        2、封装 unordered_map

        哈希桶的 T 为值,在 unordered_set 中为 K,在 unordered_map 中为 pair<K, T> 

        unordered_map 中的 T 为第二个类型,如 unordered_map<string, int> 中,代表的是 int

// 设置的默认哈希函数
template<class K>
class HashFunc
{
public:
	int operator()(const K& val)
	{
		return val;
	}
};

// 特化
template<>
int HashFunc<std::string>::operator()(const std::string& str)
{
	int key = 0;
	for (char ch : str)
	{
		key += ch;
	}

	return key;
}

// 对于 unordered_map 关键字为 K, 值为 pair<K, T>
// 通过 KOfT 就可以实现 unordered_map 和 unordered_set 使用同一个类封装
template<class K, class T>
class KOfT
{
public:
	const K& operator()(const T& val)
	{
		return val.first;
	}
};

// unordered_map<string, int>
// K 为key,类型为string 
// T 类型为int
template<class K, class T, class HASH = HashFunc<K>>
class unordered_map
{
public:
	using iterator = typename bucket::HashBucket<K, std::pair<K, T>, KOfT<K, std::pair<K, T>>, HASH>::iterator;

	std::pair<iterator, bool> Insert(const std::pair<K, T>& val)
	{
		return _ht.Insert(val);
	}

	bool Erase(const K& key)
	{
		return _ht.Erase(key);
	}

	iterator Find(const K& key)
	{
		return _ht.Find(key);
	}

	T& operator[](const K& key)
	{
		// 运算符[] 就是如果存在,返回值引用;如果不存在,插入{key, 0},并返回值引用
		// 因此,我们只要插入{key, 0} 即可
		std::pair<iterator, bool> ret = Insert(std::make_pair(key, 0));

		return ret.first->second;
	}

	iterator begin()
	{
		return _ht.begin();
	}
	iterator end()
	{
		return _ht.end();
	}
private:
	// 调用哈希桶时,key 为 K,值为pair<K, T>
	bucket::HashBucket<K, std::pair<K, T>, KOfT<K, std::pair<K, T>>, HASH> _ht;
};

        感谢观看♪(・ω・)ノ

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

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

相关文章

红日靶场vulnstark 4靶机的测试报告[细节](一)

目录 一、测试环境 1、系统环境 2、注意事项 3、使用工具/软件 二、测试目的 三、操作过程 1、信息搜集 2、漏洞利用Getshell ①Struts 2 s2-045漏洞 手工利用s2-45漏洞 Msf综合利用 ②Tomcat框架(CVE-2017-12615) ③phpMyAdmin(CVE-2018-12613) 构造语句写入冰蝎木…

D92【python 接口自动化学习】- pytest基础用法

day92 pytest的skip和skipif用法 学习日期&#xff1a;20241208 学习目标&#xff1a;pytest基础用法 -- pytest的skip和skipif用法 学习笔记&#xff1a; 测试用例跳过 skip和skipif用法&#xff0c;测试用例跳过 pytest.mark.skip 跳过标记的用例 pytest.mark.skipif(1 …

【Java】类似王者荣耀游戏

r77683962/WangZheYouDianRongYao 运行效果图&#xff1a; 类似王者荣耀游戏运行效果图_哔哩哔哩_bilibili

【数字电路与逻辑设计】实验二 数值比较器

文章总览&#xff1a;YuanDaiMa2048博客文章总览 【数字电路与逻辑设计】实验二 数值比较器 一、实验内容二、设计过程&#xff08;一&#xff09;真值表&#xff08;二&#xff09;设计思路 三、源代码&#xff08;一&#xff09;代码说明&#xff1a;&#xff08;二&#xff…

探索十个 AI 对话提示词网站,提升交互体验

learning prompt 网址&#xff1a;Hello from Learning Prompt | Learning Prompt 简介&#xff1a;这是一个学习 提示词 的网站&#xff0c;有 ChatGPT&#xff0c;Midjourney 的提示词教程、技巧等&#xff0c;他在右上角有中文语言的选择&#xff0c;教程非常详尽 LangCha…

C# Decimal

文章目录 前言1. Decimal 的基本特性2. 基本用法示例3. 特殊值与转换4. 数学运算示例5. 精度处理示例6. 比较操作示例7. 货币计算示例8. Decimal 的保留小数位数9. 处理 Decimal 的溢出和下溢10. 避免浮点数计算误差总结 前言 decimal 是 C# 中一种用于表示高精度十进制数的关键…

数据库-mysql(基本语句)

演示工具&#xff1a;navicat 连接&#xff1a;mydb 一.操作数据库 1.创建数据库 ①create database 数据库名称 //普通创建 ②create database if not exists 数据库名称 //创建数据库&#xff0c;判断不存在&#xff0c;再创建&#xff1a; 使用指定数据库 use 数据库…

使用Tesseract进行图片文字识别

Tesseract介绍 Tesseract 是一个开源的光学字符识别&#xff08;OCR&#xff09;引擎&#xff0c;最初由 HP 在 1985 年至 1995 年间开发&#xff0c;后来被 Google 收购并开源。Tesseract 支持多种语言的文本识别&#xff0c;能够识别图片中的文字&#xff0c;并将其转换为可…

智慧营区解决方案

1. 数据处理与服务封装 该智慧化解决方案注重数据的全面采集与处理&#xff0c;包括网页数据、图片、视频等非结构化数据&#xff0c;以及系统数据、文本、关系型数据库等结构化数据。通过数据抽取、稽核、规整、解析、清洗等流程&#xff0c;实现数据的入库与存储&#xff0c…

信号level 1:信号的产生

目录 引言 基础知识&#xff08;背景&#xff09; 信号的产生 方法一&#xff1a;通过终端按键产生信号&#xff08;键盘按键输入&#xff09; 方式二&#xff1a;通过kill指令 方法3&#xff1a;系统调用 方法四&#xff1a;硬件异常 方法五&#xff1a;软件产生异常 进…

国城杯-misc-Tr4ffIc_w1th_Ste90 [WP]

打开流量包&#xff0c;发现主要是UDP流量&#xff0c;然后还有H264的视频数据 右击&#xff0c;UDP追踪流 然后更换为原始数据 另存为.ts的文件&#xff0c;打开就看到密码 !t15tH3^pAs5W#RD*f0RFL9 打开压缩包是一个文件和一个图片 import numpy as np import cv2 import …

【SpringMVC】参数传递 重定向与转发 REST风格

文章目录 参数传递重定向与转发REST风格 参数传递 ModelAndView&#xff1a;包含视图信息和模型数据信息 public ModelAndView index1(){// 返回页面ModelAndView modelAndView new ModelAndView("视图名");// 或// ModelAndView modelAndView new ModelAndView(…

Minio使用教程

Minio介绍 Minlo是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等&#xff0c;而一个对象文件可以是任意大小&#xf…

新手前端开发入职公司全流程

作为一名前端开发新手&#xff0c;进入一家公司开启职业生涯是既兴奋又充满挑战的旅程。今天就来和大家分享一下这个过程中的各个环节。 一、入职准备 在收到心仪公司的offer后&#xff0c;可别只顾着高兴啦。首先要准备好入职资料&#xff0c;像身份证、学历证明这些是必不可…

那智机器人报警白屏

那智机器人内存太多白屏报警&#xff01;&#xff01;&#xff01;下面来说说如何消除该报警吧&#xff01;&#xff01;&#xff01; 白屏报警的处理办法 解决办法&#xff1a;&#xff08;外部内存清理&#xff09; 升级操作者权限 &#xff08;当我们在维修见面不能看见我…

rk3568之mpp开发笔记mpp移植到开发板

前言&#xff1a; 大家好&#xff0c;今天给大家介绍的内容是rk平台的mpp编解码这块的内容&#xff0c;在rk目前看到有三套框架涉及到编解码内容&#xff1a; 1、rkmedia 2、rockit 3、mpp 这三种不同形式的编解码方式&#xff0c;后面再做详细的框架对比&#xff0c;今天我…

【设计模式】结构型设计模式总结之代理模式、装饰模式、外观模式、享元模式

文章目录 代理模式示例结构分类动态代理 装饰模式示例结构使用场景与代理模式区别Context 外观模式结构示例使用场景Context 享元模式结构示例使用场景Message 代理模式 代理模式&#xff08;Proxy Pattern&#xff09; 是一种结构型设计模式&#xff0c;它提供了一个代理对象…

CSS一些小点 —— 12.7

1. box-sizing: border-box box-sizing 属性&#xff0c;默认值为 content-box box-sizing: border-box 使padding和border的值不会再影响元素的宽高&#xff1b;padding和border的值算在指定宽高的内部&#xff08;但是外边距依然算做外部&#xff09; 2. overflow: hidden …

【毕业设计选题】深度学习类毕业设计选题参考 开题指导

目录 前言 毕设选题 开题指导建议 更多精选选题 选题帮助 最后 前言 大家好,这里是海浪学长毕设专题! 大四是整个大学期间最忙碌的时光&#xff0c;一边要忙着准备考研、考公、考教资或者实习为毕业后面临的升学就业做准备,一边要为毕业设计耗费大量精力。学长给大家整…

算法基础学习Day4(双指针)

文章目录 1.题目2.题目解答1.查找总价格为目标值的两个商品1.1题目及题目解析1.2算法学习暴力解法优化算法 1.3代码提交 2.三数之和2.1题目及题目解析2.2算法学习2.3代码提交 1.题目 LCR 179. 查找总价格为目标值的两个商品 - 力扣&#xff08;LeetCode&#xff09;15. 三数之…