【数据结构】哈希表的原理及其实现

news2024/12/23 14:55:08

文章目录

  • 哈希表的概念
  • 哈希函数的设计
    • 常见的哈希函数
  • 哈希冲突
    • 1. 闭散列
      • 代码实现
    • 2. 开散列
    • 拉链法的优点
  • 针对开散列哈希的扩展
  • 基于开散列拉链法封装哈希表
    • MyHash.h
  • 基于哈希表实现unordered_map类
    • Myunordered_map.h
  • 基于哈希表实现unordered_set类
    • Myunordered_map.h

哈希表的概念

哈希表,也被称为散列表。是一种通过键可以快速找到对应值的一种数据结构。像我们常使用的unordered系列的容器,其本质就是一个哈希表。哈希表最显著的特点就是查找数据的效率非常的高,平均查找的时间复杂度为O(1)。通过哈希函数将键值映射到数组的具体位置,再对其进行操作。

哈希表查找的过程为:通过键来找到数组中的一个位置,通过这个位置我们能找到该键唯一映射的值
哈希表的y重要设计模块就是哈希函数以及解决哈希冲突的方法。下面将对此做出解释:

哈希函数的设计

哈希表的性能的一个关键因素就是哈希函数的设计。具体的来说,哈希函数指的是通过键值找到映射到数组具体位置的方法。一个好的哈希函数能将键均匀的分布到哈希表的各个位置。

常见的哈希函数

  1. 直接定值法
    取关键字的某个线性函数为散列地址,该线性函数的返回值应该是一个整数。比如Hash(key)=A*Key+B
  2. 除留余数法
    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址

哈希冲突

什么叫哈希冲突?哈希冲突又叫做哈希碰撞。具体是指,多个键通过一个哈希函数得到的位置相同。造成哈希冲突的主要原因是,哈希表的大小是有限的,而输入的数据可能会非常多,因此不可能避免出现哈希冲突。

那出现哈希冲突之后该如何解决呢?下面给出几种常见的解决哈希冲突的方法:

1. 闭散列

闭散列又叫开放定址法。当发生哈希冲突时,如果该哈希表还有空位,那么就从冲突位置开始往后找,直到找到空位。该如何往后找空位呢?一个一个找嘛?

  • 线性探测:线性探测,从发生冲突的位置开始,一个一个往后面找,直到找到空位。
    在这里插入图片描述
  • 二次探测:从发生冲突的位置开始,使用一个二次探测公式来寻找下一个探测的位置,探测公式为:
    H(k)=(Hash(k)+C1*i+C2*(i^2))%p
    • 其中H(k)表示的是第i次探测的位置。
    • Hash(k)表示通过哈希函数得到的初始地址
    • C1C2是一个常数,C1通常为0,C2通常为1,即H(k)=(Hash(k)+(i^2))%p
    • i是探测的次数,从0开始
    • p是哈希表的大小

二次探测的优点是能有效减少一次探测中数据过于“集中”的问题,即连续的冲突位置,会形成长链。而二次探测会使得探测的位置更加分散。

研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。

装载因子又叫做负载因子,是哈希表中元素个数与哈希表大小的比值

代码实现

哈希表类主要有两个模块需要实现,一个是节点类,还有一个是存储节点的容器。容器我们选择使用STL中的vector。对于节点类,我们希望至少能实现以下功能:

  • 存储键值对
  • 存储状态

值得注意的是,由于线性探测法的特性,删除一个元素之后可能会影响后续查找元素。我们需要给每个节点赋予三种状态表示:空节点、存在值得节点、被删除得节点。有了这三种种状态,我们在查找元素时,就不会因为中间曾经被删除的节点而影响。

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<vector>
using namespace std;
namespace Close_Hash{
	enum Statu{
		EMPTY,
		EXIST,
		DEL
	};


	template<class K,class V>
	struct Node {
		Node():_statu(EMPTY){}
		pair<K, V> _kv;
		Statu _statu;
	};

	template<class K>
	struct HashFunc{
		size_t operator()(const K& key) {
			return (size_t)key;
		}
	};

	template<>
	struct HashFunc<string> {
		size_t operator()(const string& key) {
			size_t res = 0;
			for (auto it : key) {
				int u = it - '0';
				res = res * 131 + u;
			}
			return res;
		}
	};


	template<class K,class V,class Hash=HashFunc<K>>
	class HashTable {
	public:
		HashTable(size_t capacity = 10)
			: _totalSize(0)
			, _ht(capacity)

		{

		}
		V& operator[](const K& key) {

			Node<K, V>* node = Find(key);
			if (node) {
				return (node->_kv).second;
			}
			else {
				this->Insert(make_pair(key,0));

				return Find(key)->_kv.second;
			}
		}
		// 插入
		bool Insert(const pair<K, V>& val) {
			//负载超标
			if (_totalSize * 10 >= 7 * _ht.size()) {
				HashTable<K, V> newtable;
				
				newtable._ht.resize(_ht.size() * 2);
				for (int i = 0; i < _ht.size(); i++) {
					if (_ht[i]._statu == EXIST) {
						newtable.Insert(_ht[i]._kv);
					}
				}
				_ht.swap(newtable._ht);
			 }
			Hash ha;
			size_t pos = ha(val.first) % _ht.size();
			//线性探测

			while (_ht[pos]._statu == EXIST) {
				pos++;
				pos = pos % _ht.size();
			}
			_ht[pos]._kv = val;
			_ht[pos]._statu = EXIST;
			_totalSize++;
			return true;

		}

		// 查找
		Node<K,V>* Find(const K& key) {
			Hash ha;
			size_t pos = ha(key) % _ht.size();
			while (_ht[pos]._statu != EMPTY) {
				if (_ht[pos]._statu == EXIST && _ht[pos]._kv.first == key) {
					return &_ht[pos];
				}
				pos++;
				pos = pos % _ht.size();
			}
			return nullptr;
		}

		// 删除
		bool Erase(const K& key) {
			Node<K, V>* node = Find(key);
			if (!node)return false;
			node->_statu = DEL;
			return true;

		}

		size_t Size() {
			return _ht.size();
		}
		bool Empty() const
		{
			return _ht.empty();
		}


	private:
		vector<Node<K,V>> _ht;
		size_t _totalSize;
	};

}

2. 开散列

开散列法又被叫做链地址法或者拉链法。和闭散列不同的是,具有相同地址的关键码属于同一集合,并用一个单链表维护这个集合。我们将这个集合称为。开散列得到哈希表的每一个元素实际上是一个名为桶的单链表。于是,当我们发生哈希冲突时,不用去哈希表中找空位了,因为当前桶是一个单链表,插入元素时直接头插就好了。
在这里插入图片描述

拉链法的优点

拉链法处理哈希冲突的效率是非常高的。即使多个元素通过哈希函数得到的地址是一样的,也不会显著影响哈希表的性能。此外,拉链法下的负载因子是可以超过1的,因此拉链法下的哈希表可以灵活的应对动态数据集的增长,不需要频繁的调整哈希表的大小。值得一提的是,虽然看上去会使用额外的空间来存放指针,但由于不需要严格维护平衡因子,空间效率并不会比开放定址法低。因为开放定址法需要开大量额外的空间来保证负载因子不溢出。

代码实现:

跟线性探测代码不一样的地方在于,拉链法的节点实际上是一个链表的头节点(桶)。所以对于哈希表的节点类,我们希望有以下功能:

  • 指向下一个元素的指针
  • 存储键值对

在插入元素时,将新节点插入到桶中。

#pragma once
#include<vector>
#include<iostream>
using namespace std;

namespace hash_bucket {
	
	template<class K,class V>
	struct HashNode {
		HashNode(const pair<K, V>& kv)
			: _kv(kv)
			,_next(nullptr)
		{

		}
		HashNode()
		   :_next(nullptr)
		{

		}
		pair<K, V> _kv;
		HashNode<K, V>* _next;
	};

	template<class K>
	struct HashFunc {
		size_t operator()(const K& k) {
			return (size_t)k;
		}
	};
	template<>
	struct HashFunc<string> {
		size_t operator()(const string& str) {
			size_t res = 0;
			for (auto it : str) {
				int u = it;
				res = res * 131 + u;
			}
			return res;
		}
	};

	template<class K,class V,class Hash=HashFunc<K>>
	class HashTable {
	public:
		typedef HashNode<K, V> Node;
		typedef Node* pNode;
		HashTable()
			:_table(10, nullptr)
			, _n(0)
		{

		}
		//随机访问
		V& operator[](const K& k) {
			if (!Find(k)) {
				Insert(make_pair(k,V()));
			}
			return Find(k)->_kv.second;
		}

		//插入
		bool Insert(const pair<K, V>& kv) {
			Hash ha;
			//负载因子为1时扩容
			if (_n == _table.size()) {
				HashTable<K, V> newtable;
				newtable._table.resize(_table.size() * 2);

				for (size_t i = 0; i < _table.size(); i++) {
					if (_table[i]) {
						pNode cur = _table[i];
						while (cur) {
							pNode temp = cur->_next;
							size_t pos = ha(cur->_kv.first) % newtable._table.size();
							if (newtable._table[pos]) {
								cur->_next = newtable._table[pos];
							}
							newtable._table[pos] = cur;
							cur = temp;
						}
					}
				}
				_table.swap(newtable._table);
			}
	
			size_t pos = ha(kv.first) % _table.size();
			pNode newnode = new Node(kv);
	 		if (_table[pos] != nullptr) {
				newnode->_next = _table[pos];
			}
			_table[pos] = newnode;
			_n++;
			return true;
		}

		//查找
		pNode Find(const K& k) {
			Hash ha;
			size_t pos= ha(k) % _table.size();
			pNode cur = _table[pos];
			while (cur) {
				if (cur->_kv.first == k)return cur;
				cur = cur->_next;
			}
			return nullptr;
		}

		//删除
		bool Erase(const K& k) {
			Hash ha;
			if (!Find(k))return false;
			size_t pos = ha(k) % _table.size();
			pNode pre = nullptr;
			pNode cur = _table[pos];
			if (cur->_kv.first == k) {
				delete cur;
				_table[pos] = nullptr;
				return true;
			}
			else {
				while (cur) {
					if (cur->_kv.first == k) {
						pre->_next = cur->_next;
						delete cur;
						cur = nullptr;
						return true;
					}
					pre = cur;
					cur = cur->_next;
				}
				return false;
			}
			
		}

	private:
		vector<pNode> _table;
		int _n;//存储有效桶的个数
	};

}

上述代码大致实现了基于开散列法的哈希表,支持键值对的插入,删除、查询以及修改。

注意开散列法哈希表的扩容方案。当负载因子达到一定值时,我们选择扩大哈希表的大小,对于旧哈希表的元素不用再重新拷贝构造,而是移植到新表中。这样就节省了很多空间。具体实现如下:

//负载因子为1时扩容
if (_n == _table.size()) {
	HashTable<K, V> newtable;//新表
	newtable._table.resize(_table.size() * 2);

	for (size_t i = 0; i < _table.size(); i++) {
		if (_table[i]) {//遍历旧表,移植节点
			pNode cur = _table[i];
			while (cur) {
				pNode temp = cur->_next;
				size_t pos = ha(cur->_kv.first) % newtable._table.size();
				if (newtable._table[pos]) {
					cur->_next = newtable._table[pos];
				}
				newtable._table[pos] = cur;
				cur = temp;
			}
		}
	}
	_table.swap(newtable._table);
}

针对开散列哈希的扩展

如何给上述开散列哈希表类增加迭代器呢?
由于迭代器中需要访问哈希表而不仅仅是桶,而哈希表又属于哈希表类的私有成员。因此,设计迭代器模块时,我们可以将迭代器类设置为哈希表类的内部类。而一个类的内部类是这个类的友元,因此迭代器就能访问到哈希表。给出以迭代器代码,每一个迭代器对象指向哈希表中桶内的一个节点

//迭代器内部类
template<class Ptr, class Ref>
struct _HashIterator {
	typedef _HashIterator<Ptr, Ref> self;
	pNode _node;
   const HashTable* _pht;

	_HashIterator(pNode pnode, const HashTable* pht)
		:_node(pnode)
		, _pht(pht)
	{

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

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

	self& operator=(const self it) {
		_node = it._node;
		_pht = it._pht;
		return *this;
	}

	self& operator++() {
		if (_node->_next) {
			_node = _node->_next;
		}
		else {
			KeyOft kft;
			Hash ha;
			size_t pos = ha(kft(_node->_data)) % _pht->_table.size();
			size_t i = pos + 1;
			for (; i < _pht->_table.size(); i++) {
				if (_pht->_table[i]) {
					break;
				}
			}
			if (i == _pht->_table.size()) {
				_node = nullptr;
			}
			else {
				_node = _pht->_table[i];
			}

		}
		return *this;
	}

	bool operator!=(const self& iterator) {
		return iterator._node != _node;
	}
};

该迭代器主要实现了重载操作符的作用,希望能将迭代器当指针来使用。

基于开散列拉链法封装哈希表

MyHash.h

该文件封装实现了一个哈希表类,哈希函数是除留余数,解决哈希冲突的方法采用拉链法。
代码:

#pragma once
#include<vector>
#include<iostream>
using namespace std;

namespace hash_bucket {
	template<class T>
	struct HashNode {
		HashNode(const T& data)
			: _data(data)
			, _next(nullptr)
		{

		}
		HashNode()
			:_next(nullptr)
		{

		}
		T _data;
		HashNode<T>* _next;
	};

	template<class K>
	struct HashFunc {
		size_t operator()(const K& k) {
			return (size_t)k;
		}
	};


	template<>
	struct HashFunc<string> {
		size_t operator()(const string& str) {
			size_t res = 0;
			for (auto it : str) {
				int u = it;
				res = res * 131 + u;
			}
			return res;
		}
	};


	template<class K, class T, class KeyOft, class Hash = HashFunc<K>>
	class HashTable {
	public:
		typedef HashNode<T> Node;
		typedef Node* pNode;
		HashTable()
			:_table(10, nullptr)
			, _n(0)
		{

		}
		//迭代器内部类
		template<class Ptr, class Ref>
		struct _HashIterator {
			typedef _HashIterator<Ptr, Ref> self;
			pNode _node;
		   const HashTable* _pht;

			_HashIterator(pNode pnode, const HashTable* pht)
				:_node(pnode)
				, _pht(pht)
			{

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

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

			self& operator=(const self it) {
				_node = it._node;
				_pht = it._pht;
				return *this;
			}

			self& operator++() {
				if (_node->_next) {
					_node = _node->_next;
				}
				else {
					KeyOft kft;
					Hash ha;
					size_t pos = ha(kft(_node->_data)) % _pht->_table.size();
					size_t i = pos + 1;
					for (; i < _pht->_table.size(); i++) {
						if (_pht->_table[i]) {
							break;
						}
					}
					if (i == _pht->_table.size()) {
						_node = nullptr;
					}
					else {
						_node = _pht->_table[i];
					}

				}
				return *this;
			}

			bool operator!=(const self& iterator) {
				return iterator._node != _node;
			}
		};

		typedef _HashIterator<T*, T&> Iterator;
		typedef _HashIterator<const T*, const T&> Const_Iterator;

		//迭代器
		Iterator begin() {
			for (size_t i = 0; i < _table.size(); i++) {
				if (_table[i]) {
					return Iterator(_table[i], this);
				}
			}
			return Iterator(nullptr, this);
		}

		Iterator end() {
			return Iterator(nullptr, this);
		}
		Const_Iterator begin()const {
			for (size_t i = 0; i < _table.size(); i++) {
				if (_table[i]) {
					return Iterator(_table[i], this);
				}
			}
			return Iterator(nullptr, this);
		}

		Const_Iterator end() const {
			return Iterator(nullptr, this);
		}



		//随机访问
		/*V& operator[](const K& k) {
			if (!Find(k)) {
				Insert(make_pair(k,V()));
			}
			return Find(k)->_kv.second;
		}*/

		//插入
		pair<Iterator, bool> Insert(const T& val) {

			Hash ha;
			KeyOft kft;
			Iterator it = Find(kft(val));
			if (it._node != nullptr)return make_pair(it, false);
			//负载因子为1时扩容
			if (_n == _table.size()) {
				HashTable<K, T, KeyOft> newtable;
				newtable._table.resize(_table.size() * 2);

				for (size_t i = 0; i < _table.size(); i++) {
					if (_table[i]) {
						pNode cur = _table[i];
						while (cur) {
							pNode temp = cur->_next;
							size_t pos = ha(kft(cur->_data)) % newtable._table.size();
							if (newtable._table[pos]) {
								cur->_next = newtable._table[pos];
							}
							newtable._table[pos] = cur;
							cur = temp;
						}
					}
				}
				_table.swap(newtable._table);
			}

			size_t pos = ha(kft(val)) % _table.size();
			pNode newnode = new Node(val);
			if (_table[pos] != nullptr) {
				newnode->_next = _table[pos];
			}
			_table[pos] = newnode;
			_n++;
			return make_pair(Iterator(newnode, this), true);
		}

		//查找
		Iterator Find(const K& k) {
			Hash ha;
			KeyOft kft;
			size_t pos = ha(k) % _table.size();
			pNode cur = _table[pos];
			while (cur) {
				if (kft(cur->_data) == k)return Iterator(cur, this);
				cur = cur->_next;
			}
			return Iterator(nullptr, this);
		}

		//删除
		bool Erase(const K& k) {
			Hash ha;
			KeyOft kft;
			if (!Find(k))return false;
			size_t pos = ha(k) % _table.size();
			pNode pre = nullptr;
			pNode cur = _table[pos];
			if (kft(cur->_data) == k) {
				delete cur;
				_table[pos] = nullptr;
				return true;
			}
			else {
				while (cur) {
					if (kft(cur->_data) == k) {
						pre->_next = cur->_next;
						delete cur;
						cur = nullptr;
						return true;
					}
					pre = cur;
					cur = cur->_next;
				}
				return false;
			}

		}

	private:
		vector<pNode> _table;
		int _n;
	};

}

基于哈希表实现unordered_map类

基于MyHash.h封装的哈希表类提供的接口,实现一个unordered_map。unordered_map的值是一个键值对。

Myunordered_map.h

该文件基本实现了unordered_map类。
代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include"MyHash.h"

namespace bit {
	template<class K, class V, class Hash =	hash_bucket::HashFunc<K> >
	class unordered_map {
		struct MapKeyOft {
			const K& operator()(const pair<K, V>& kv) {
				return kv.first;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOft, Hash>::Iterator iterator;
		//typedef typename hash_bucket::HashTable<K, pair<const K, V>, KeyOft, Hash>::Const_Iterator const_iterator;
		iterator begin() {
			return _ht.begin();
		}
		iterator end() {
			return _ht.end();
		}

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

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

		bool erase(const K& k) {
			return _ht.Erase(k);
		}
		pair<iterator, bool> find(const K& k) {
			return _ht.Find(k);
		}

	private:
		hash_bucket::HashTable<K, pair<const K, V>, MapKeyOft, Hash> _ht;
	};

}

在这里插入图片描述

基于哈希表实现unordered_set类

基于MyHash.h封装的哈希表类提供的接口,实现一个unordered_set类。

Myunordered_map.h

该文件基本实现了unordered_set类。
代码:

#include"MyHash.h"

namespace bit {
	template<class K,class Hash = hash_bucket::HashFunc<K> >
	class unordered_set {
		struct SetKeyOft {
			const K& operator()(const K& k) {
				return k;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K, const K, SetKeyOft, Hash>::Iterator iterator;
		typedef typename hash_bucket::HashTable<K, const K, SetKeyOft, Hash>::Const_Iterator const_iterator;
		iterator begin() {
			return _ht.begin();
		}
		iterator end() {
			return _ht.end();
		}

		

		pair<iterator, bool> insert(const K& kv) {
			return _ht.Insert(kv);
		}

		bool erase(const K& k) {
			return _ht.Erase(k);
		}
		pair<iterator, bool> find(const K& k) {
			return _ht.Find(k);
		}

	private:
		hash_bucket::HashTable<K, const K, SetKeyOft, Hash> _ht;
	};

}


在这里插入图片描述

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

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

相关文章

Ethercat总线学习:CAN、CANopen、EtherCAT、PDO与SDO

各种XoE CoE是什么 CoE CAN application protocol Over EtherCAT&#xff0c;是EtherCAT应用层协议的一种&#xff0c;根据CiA402协议编写&#xff0c;使用对象和对象字典的功能来实现邮箱通讯。 PDO与SDO是常用的数据传输方式&#xff0c;他们在实现高效数据传输、同步方面发…

html中被忽略的简单标签

1&#xff1a; alt的作用是在图片不能显示时的提示信息 <img src"https://img.xunfei.cn/mall/dev/ifly-mall-vip- service/business/vip/common/202404071019208761.jp" alt"提示信息" width"100px" height"100px" /> 2&#…

Vue2基础及其进阶面试(二)

vue2的生命周期 删除一些没用的 App.vue 删成这个样子就行 <template><router-view/></template><style lang"scss"></style>来到路由把没用的删除 import Vue from vue import VueRouter from vue-router import HomeView from .…

Java进阶学习笔记25——Objects类

为啥比较两个对象是否相等&#xff0c;要用Objects的equals方法&#xff0c;而不是用对象自己的equals方法来解决呢&#xff1f; Objects&#xff1a; Objects类是一个工具类&#xff0c;提供了很多操作对象的静态方法供我们使用。 package cn.ensource.d14_objects;import ja…

无货源抖店怎么起店?教你两种起店方法,记得收藏!

大家好&#xff0c;我是喷火龙。 开通抖音小店之后最重要的一步就是起店了&#xff0c;今天就给大家分享两种起店方法。 大家都知道&#xff0c;产品是做店的核心&#xff0c;品不行&#xff0c;就算平台给你免费的流量&#xff0c;那你也承接不住。 第一个&#xff0c;商品卡…

Steam在连接至服务器发生错误/连接服务器遇到问题解决办法

Steam作为全球最大的数字游戏分发平台&#xff0c;构建了一个活跃的玩家社区&#xff0c;用户可以创建个人资料&#xff0c;添加好友&#xff0c;组建群组&#xff0c;参与讨论&#xff0c;甚至直播自己的游戏过程。通过创意工坊&#xff0c;玩家还能分享自制的游戏模组、地图、…

【古董技术】ms-dos应用程序的结构

序 制定一个MS-DOS应用程序计划需要认真分析程序的大小。这种分析可以帮助程序员确定MS-DOS支持的两种程序风格中哪一种最适合该应用程序。.EXE程序结构为大型程序提供了好处&#xff0c;因为所有.EXE文件之前都有额外的512字节&#xff08;或更多&#xff09;的文件头。另一方…

跨境选品师不是神话:普通人也能轻松掌握,开启全球贸易新篇章!

随着互联网技术的飞速发展&#xff0c;跨境电商行业已成为全球经济的新增长点。在这个背景下&#xff0c;一个新兴的职业——跨境选品师&#xff0c;逐渐走进了人们的视野。那么&#xff0c;跨境选品师究竟是做什么的?普通人又该如何成为优秀的跨境选品师呢? 一、跨境选品师的…

Docker compose 的方式一键部署夜莺

官方安装文档&#xff1a;https://flashcat.cloud/docs/content/flashcat-monitor/nightingale-v7/install/docker-compose/ 介绍&#xff1a;夜莺监控是一款开源云原生观测分析工具&#xff0c;采用 All-in-One 的设计理念&#xff0c;集数据采集、可视化、监控告警、数据分析…

数据结构(四)串

2024年5月26日一稿(王道P127) 定义和实现

日志的介绍及简单实现

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 日志是什么&#xff1f; 为什么需要日志&#xff1f; 实现一个简单日志 时间戳 clock_gettime time & localtime 可变模板参数(使用C语言)&#xff0c;va_start & va_end & vsprintf 宏 __LINE__…

推荐一款媒体影音嗅探神器—Chrome扩展插件(猫抓cat-catch)

目录 1.1、前言1.2、下载地址1.3、github Releases 版本说明1.4、安装步骤1.5、猫抓插件常规设置1.5.1、设置抓取文件的类型1.5.2、设置抓取文件的后缀名 1.1、前言 我们在日常上网的过程中&#xff0c;很多音频、视频网站下载资源都非常不方便&#xff0c;要么需要安装客户端&…

【高阶数据结构】AVL树的旋转与底层(C++实现)

1.AVL树的概念及作用 2.AVL树插入数据的规则 1.按照搜索树的规则插入&#xff0c;然后更新父亲的平衡因子 2.更新父亲的平衡因子后&#xff0c;如果出现一下三种情况需要进行相应处理 3.AVL树的旋转 3.1右单旋 右单旋的所有情况可以抽象为上图&#xff1a;图中&#xff0c;a,…

【识人】感情与交友中,如何判断一个人的性格,以及是否值得交往和相处

【识人】感情与交友中&#xff0c;如何判断一个人的性格&#xff0c;以及是否值得交往和相处 文章目录 序言正文1、学会筛选&#xff0c;贴标签&#xff0c;学会区别对待&#xff0c;2、男生女生一定要在年轻的时候学会对外在祛魅3、培养付出意识&#xff0c;学会顶风相见。4、…

protobuf —— 认识和安装

protobuf —— 认识和安装 什么是序列化和反序列化有哪些常见的什么是序列化和反序列化工具Protobuf安装安装依赖开始安装 连接动态库一些遗留问题 我们今天来看一个序列化和反序列化的工具&#xff1a;protobuf。 什么是序列化和反序列化 序列化&#xff08;Serialization&a…

Yolov9调用COCOAPI生成APs,APm,APl

最近在做小目标检测的东西&#xff0c;因为后期毕业论文需要&#xff0c;所以开始使用Yolov9模型&#xff0c;运行val.py的时候不会自己产生小目标的AP指标&#xff0c;所以研究了一下&#xff0c;步骤非常简单&#xff1a; 第一步&#xff1a; 在数据集中生成json格式的Annota…

【LLM多模态】综述Visual Instruction Tuning towards General-Purpose Multimodal Model

note 文章目录 note论文1. 论文试图解决什么问题2. 这是否是一个新的问题3. 这篇文章要验证一个什么科学假设4. 有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一课题在领域内值得关注的研究员&#xff1f;5. 论文中提到的解决方案之关键是什么&#xff1f;6. 论文中的…

AIGC时代算法工程师的面试秘籍(2024.4.29-5.12第十三式) |【三年面试五年模拟】

写在前面 【三年面试五年模拟】旨在整理&挖掘AI算法工程师在实习/校招/社招时所需的干货知识点与面试方法&#xff0c;力求让读者在获得心仪offer的同时&#xff0c;增强技术基本面。也欢迎大家提出宝贵的优化建议&#xff0c;一起交流学习&#x1f4aa; 欢迎大家关注Rocky…

LVS精益价值管理系统 DownLoad.aspx 任意文件读取漏洞复现

0x01 产品简介 LVS精益价值管理系统是杭州吉拉科技有限公司研发的一款专注于企业精益化管理和价值流优化的解决方案。该系统通过集成先进的数据分析工具、可视化的价值流映射技术和灵活的流程改善机制&#xff0c;帮助企业实现高效、低耗、高质量的生产和服务。 0x02 漏洞概述…

【数据库】基于PyMySQL连接并使用数据库(代码示例)

这里写目录标题 前言1、安装PyMySQL2、打开要连接的数据库3、创建数据库连接4、获取数据库版本5、新建数据库表6、向表中插入数据7、查询表中的相关记录8、更新表中的相关记录9、删除表中的相关记录10、关闭游标和连接完整代码 前言 本文演示了如何基于PyMySQL使用代码来创建数…