C++ 哈希表(unordered_map与unordered_set)

news2024/11/15 13:23:24

文章目录

  • unordered_map 与 unordered_set
  • 哈希表 (Hash Table)
    • 哈希函数
    • 哈希冲突
    • 模拟实现
    • 封装
  • 补充:unordered_map 与 unordered_set 的使用

unordered_map 与 unordered_set

就和名字一样,这是 map、set 的无序版本(数据遍历出来是无序的),其底层不是红黑树,而是哈希表

● 为什么要设计这两个容器?
答案很简单:效率高

//我们用以下代码比较set和unordered_set的效率(测效率编译器用release版本)
#include <iostream>
#include <set>
#include <map>
#include<unordered_map>
#include<unordered_set>
#include<time.h>
#define N 1000000
int main(){
	set<int> s;
	unordered_set<int> us;
	srand(time(0));
	vector<int> v;
	v.reserve(N);
	for (int i = 0; i < N; i++) {
		//v.push_back(rand());//有大量重复数据时
		//v.push_back(rand() + i);//重复数据较少时 -- rand只能生成差不多 30000 个不同的数,通过 +i 减少重复数据
		v.push_back(i);//有序数据时
	}
	int begin1 = clock();
	for (auto e : v) {
		s.insert(e);
	}
	int end1 = clock();
	cout << "set插入用时:" << end1 - begin1 << endl;
	int begin2 = clock();
	for (auto e : v) {
		us.insert(e);
	}
	int end2 = clock();
	cout << "unordered_set插入用时:" << end2 - begin2 << endl << endl;
	int begin3 = clock();
	for (auto e : v) {
		s.find(e);
	}
	int end3 = clock();
	cout << "set查找(所有数据)用时:" << end3 - begin3 << endl;
	int begin4 = clock();
	for (auto e : v) {
		us.find(e);
	}
	int end4 = clock();
	cout << "unordered_set查找(所有数据)用时:" << end4 - begin4 << endl << endl;

	cout << "数据总数:" << N << endl;
	cout << "不同数据个数:" << s.size() << endl << endl;
	
	int begin5 = clock();
	for (auto e : v) {
		s.erase(e);
	}
	int end5 = clock();
	cout << "set删除(所有数据)用时:" << end5 - begin5 << endl;
	int begin6 = clock();
	for (auto e : v) {
		us.erase(e);
	}
	int end6 = clock();
	cout << "unordered_set删除(所有数据)用时:" << end6 - begin6 << endl;
	return 0;	
}

结果:

这是VS2022版本的测试结果
在这里插入图片描述
可以看见,除了有序以及查找所有数据的情况,unordered_set的效率都是比set高的

这是VS2019版本的测试结果:
在这里插入图片描述
nnd,我还以为换了个环境就能得到我想要的结果,按理说根据哈希表的特点无论是2022还是2019应该测出来unordered_set的查找应该比set要快才对,但即便我把数据增多到10000000也仍然是两个0……

也不算是一无所获……至少让我再一次明白了编译器的底层到底是什么是不能用我的常理去理解的……

这是老师的演示的结果(我们就以这个为准,说来老师当时用的应该是VS2013):
请添加图片描述

哈希表 (Hash Table)

也称散列表,其思想为让其中存储的 key 与存储位置(哈希地址)建立映射关系,如此其查找速度将会变得相当快

就比如想要统计某文章中26个字母各出现的次数,C语言期间我们可以通过 int arr[26] 数组去记录次数,下标 0 对应 a,25 对应z,如此建立起映射关系

哈希函数

哈希表建立映射关系所依赖的函数,这种转换函数称为 哈希(散列)函数

  1. 直接定址法
    取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
  2. 除留余数法
    %得余数
  3. 其他(之后如果发现有需要再补充)

哈希冲突

不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突
或哈希碰撞

解决哈希冲突两种常见的方法是:闭散列和开散列

闭散列
也称开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的 “下一个” 空位置中去。
寻找空位置可以用线性探测(挨个或挨几个地往后找,但这样数据容易堆成一片降低效率)、二次探测(方法如其名)

这地方有人了?我给你往后找个最近的空位置坐

开散列
也称链地址法(开链法),哈希表不存储单个元素而是存储一条单链,各链表的头结点存储在哈希表中(显然每条单链上都是发生冲突的元素)

这根链子上有人挂着了?那你也挂这吧

模拟实现

简单功能的实现倒没什么难的

//实现方式一:闭散列 / 开放地址法 + 除留余数法
//开放定址法(闭散列)
namespace OpenAddress {
	enum State
	{
		EMPTY,
		EXIST,
		DELETE
	};

	template<class K, class V>
	struct HashData {
		pair<K, V> _kv;//一步步来,先认为是给map用的
		State _state = EMPTY;
	};

	template<class K, class V>
	class HashTable {
	public:
		bool Insert(pair<K, V> kv) {
			//●判断是否需要扩容 -- 依据负载因子(当前数据量/最大数据量 一般 < 0.7 或 0.8)
			//因为我们一开始没有给_table开空间,所以这里要加个判断
			if (_size == 0 || _size * 10 - _table.size() * 10 >= 7) {
				扩容方法一:创建新表(代码有些冗余)
				//int newsize = (_size == 0 ? 10 : _size * 2);
				//vector<HashDate<K, V>> newtable;
				//newtable.resize(newsize);
				转移旧数据(遍历旧表,按新的映射关系插入到新表中)
				//for (auto& e : _table) {
				//	if (e._state == EXIST) {
				//		int index = (e._kv).first % newsize;
				//		while (newtable[index]._state == EXIST) {
				//			index = (index + 1) % newtable.size();
				//		}
				//		newtable[index]._kv = e._kv;
				//	}
				//}
				//_table.swap(newtable);

				//扩容方法二:创建新哈希表,复用Insert
				int newsize = (_size == 0 ? 10 : _size * 2);
				HashTable<K, V> newhashtable;
				newhashtable._table.resize(newsize);
				for (auto e : _table) {
					if (e._state == EXIST)
						newhashtable.Insert(e._kv);
				}
				_table.swap(newhashtable._table);
			}
			//●插入
			int index = kv.first % _table.size();
			//有负载因子在不怕没空间
			while (_table[index]._state == EXIST) {
				index = (index + 1) % _table.size();//线性探测(往后一个一个找)
			}
			_table[index]._kv = kv;
			_table[index]._state = EXIST;
			_size++;
			return true;
		}

		HashData<K, V>* Find(const K& key) {
			if (_table.size() == 0)
				return nullptr;
			int hashi = key % _table.size();
			int index = hashi;
			//线性查找:从hashi下标开始往后一个一个找,遇到EMPTY状态结束,如果都找一圈了(全是删除状态)也返回
			while (_table[index]._state != EMPTY)
			{
				if (_table[index]._state == EXIST && _table[index]._kv.first == key)
					return &_table[index];

				index = (index + 1) % _table.size();

				// 如果已经查找一圈,那么说明全是存在+删除
				if (index == hashi)
					break;
			}
			return nullptr;

		}

		bool Erase(const K& key){
			HashData<K, V>* ret = Find(key);
			if (ret){
				ret->_state = DELETE;
				--_size;
				return true;
			}
			else
				return false;
		}
	private:
		vector<HashData<K, V>> _table;//哈希表(.size()表示)
		int _size = 0;				   //哈希表中实际存在的元素个数
	public:
	};
	void test_insert() {
		HashTable<int, int> ht;
		ht.Insert(make_pair(1, 5));
		ht.Insert(make_pair(2, 6));
		ht.Insert(make_pair(3, 7));
		cout << ht.Find(1)->_kv.second << endl;
		ht.Erase(1);
		if (ht.Find(1))
			cout << "1在" << endl;
		else
			cout << "1不在" << endl;
	}
}
//哈西桶、链地址法(开散列)(不想手写了,所以借用了老师写的代码)
namespace HashBucket {
	template<class K,class V>
	struct HashNode{
		HashNode<K, V>* _next;
		pair<K, V> _kv;

		HashNode(const pair<K, V>& kv)
			:_next(nullptr)
			, _kv(kv)
		{}
	};
	template<class K, class V>
	class HashTable {
		typedef HashNode<K, V> Node;
	public:
		//析构函数,闭散列不写是因为vector自己就释放了,而这里不行
		~HashTable()
		{
			for (auto& cur : _tables)
			{
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}

				cur = nullptr;
			}
		}
		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
				return nullptr;

			size_t hashi = key % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}

				cur = cur->_next;
			}

			return nullptr;
		}

		bool Erase(const K& key)
		{
			size_t hashi = key % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;

					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}

			return false;
		}

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;

			// 负载因因子==1时扩容
			if (_n == _tables.size()){
				//扩容方法一:按开放定址法的方法二一样Insert(但这里问题在于每次Insert都会开空间)
				/*size_t newsize = _tables.size() == 0 ? 10 : _tables.size()*2;
				HashTable<K, V> newht;
				newht.resize(newsize);
				for (auto cur : _tables)
				{
					while (cur)
					{
						newht.Insert(cur->_kv);
						cur = cur->_next;
					}
				}

				_tables.swap(newht._tables);*/

				//扩容方法二:改变旧表数据指向,转移到新表中
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<Node*> newtables(newsize, nullptr);
				//for (Node*& cur : _tables)
				for (auto& cur : _tables){
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = cur->_kv.first % newtables.size();

						// 头插到新表
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;

						cur = next;
					}
				}
				_tables.swap(newtables);
			}

			size_t hashi = kv.first % _tables.size();
			// 头插
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;

			++_n;
			return true;
		}
	private:
		vector<Node*> _tables; // 指针数组
		size_t _n = 0; // 存储有效数据个数
	};
}

封装

我们选择用哈希桶去进行封装

封装步骤

  1. 改进哈希表:修改、增加模板参数

首先,我们知道map、set 肯定离不开key-value键值对

● 像Find这样的函数需要根据 key 去进行查找,所以Key的类型是必须知道的
——模板参数 K

● 以上是针对存储pair数据实现的哈希表,而现在你要存储不同数据
—— 模板参数 T 表示

● unordered_map存储pair<int ,char>这种 key-value 键值对,想要获取key则需要从存储的T中取出first;unordered_set存储int之类的类型,存储的T就是key,能直接取到
——获取key的方法不同,需要仿函数 KeyOfT 去指导获取key

● 哈希表里存储数据和位置之间的关系是计算来的,上面存储pair,我们就其中的first去计算,但我们这里默认了first就是整形,如果这是string呢?
——模板参数 HashiFunc ,用于将key转换为整形,用于计算数据在哈希表中映射的位置

  1. 增添哈希表迭代器
    模板参数:const迭代器模板参数三兄弟 T Ref Ptr,此外哈希表的迭代器++操作时是需要用 key 去计算数据当前所处位置的
    —— K、KeyOfT、HashiFunc

还要注意迭代器的成员变量

  1. 其他注意事项
    函数返回值、unordered_set与unordered_map默认支持整形和string类型转换去计算哈希表映射位置、unordered_set与unordered_map的普通迭代器与const迭代器……

Hash.h

#include<vector>
#include<iostream>
using namespace std;
template<class T>
struct HashNode{
	HashNode<T>* _next;
	T _data;

	HashNode(const T& data)
		:_next(nullptr)
		, _data(data)
	{}
};
//因为迭代器里需要哈希表的指针,所以需要前置声明哈希表
template<class K, class T, class KeyOfT, class Hash>
class HashTable;

//老师似乎并没有给哈希表设置const迭代器,是因为这么写模板参数太多了吗?
template<class K, class Ref, class Ptr, class T, class KeyOfT, class HashiFunc>
// ++时需要计算当前哈希位置,故需要得知哈希表的大小
//可以整一个函数去访问哈希表的里的_tables,但这里我们试试用友元解决这个问题
struct __HashIterator {
	typedef __HashIterator<K, Ref, Ptr, T, KeyOfT, HashiFunc> Self;
	typedef HashNode<T> Node;
	//! ++计算时肯定需要_table里的元素,因此你需要让迭代器能访问到所处的哈希表
	typedef HashTable<K, T, KeyOfT, HashiFunc> HT;

	Node* _pnode;
	HT* _pht;

	__HashIterator(Node* pnode, HT* pht)//迭代器的初始化一般就是begin这些函数了,到时候传参数构造就行
		:_pnode(pnode)
		, _pht(pht)
	{}

	typedef __HashIterator<K, T&, T*, T, KeyOfT, HashiFunc> Iterator;
	__HashIterator(const Iterator& it)
		:_pnode(it._pnode)
		,_pht(it._pht)
	{}

	bool operator!=(const Self& s) {
		return _pnode != s._pnode;
	}
	T& operator*() {
		return _pnode->_data;
	}
	T* operator->() {
		return &_pnode->_data;//自己的碎碎念:记得之前学过这么写是因为编译器会优化,使用时看似一个->实则两个,但这里写的……真的是两个->而不是一个->加一个*吗
	}
	
	Self& operator++() {
		if (_pnode->_next != nullptr) {
			_pnode = _pnode->next;
		}
		//去寻找下一个有数据的哈希桶
		else {
			KeyOfT kot;
			size_t hashi = kot(_pnode->data) % _pht->_tables.size();
			while (hashi < _pht->_tables.size()) {
				if (_pht->_tables[hashi] == nullptr) {
					hashi++;
				}
				else {
					_pnode = _pht->_tables[hashi];
					return *this;
				}
			}
			//后面没数据了
			_pnode = nullptr;
			return *this;
		}
	}
};
template<class K>
struct hashifunc {
	size_t operator()(const K& key) {
		return key;
	}
};
//类模板特化(string)
template<>
struct hashifunc<string> {
	size_t operator()(const string& s) {
		size_t hashi = 0;
		for (auto ch : s) {
			hashi += ch;
			hashi *= 31;//只是将每个字符的ASCII码值相加岂不是换个顺序就哈希冲突了?通过乘一个数解决这个问题(为什么是31?我不到啊)
		}
		return hashi;
	}
};


template<class K, class T,class KeyOfT,class HashiFunc>//HashiFunc为仿函数,用于将传入的K转换为整形,以供hashi的计算
class HashTable {
	template<class K, class Ref, class Ptr, class T, class KeyOfT, class HashiFunc>
	friend struct __HashIterator;

	typedef HashNode<T> Node;
public:
	//析构函数,闭散列不写是因为vector自己就释放了,而这里不行
	~HashTable()
	{
		for (auto& cur : _tables)
		{
			while (cur)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}

			cur = nullptr;
		}
	}

	bool Erase(const K& key)
	{
		HashiFunc hash;//一定记住仿函数得先实例化才能用啊淦
		size_t hashi = hash(key) % _tables.size();
		KeyOfT kot;
		Node* prev = nullptr;
		Node* cur = _tables[hashi];
		while (cur)
		{
			if (kot(cur->_data) == key)
			{
				if (prev == nullptr)
				{
					_tables[hashi] = cur->_next;
				}
				else
				{
					prev->_next = cur->_next;
				}
				delete cur;
				_n--;
				return true;
			}
			else
			{
				prev = cur;
				cur = cur->_next;
			}
		}

		return false;
	}
	typedef __HashIterator<K, K&, K*, T, KeyOfT, HashiFunc> iterator;
	typedef __HashIterator<K, const K&, const K*, T, KeyOfT, HashiFunc> const_iterator;

	iterator begin() {
		//先找到有数据的哈希桶
		for (auto e : _tables) {
			if (e) {
				return iterator(e, this);
			}
		}
		return iterator(nullptr, this);
	}
	iterator end() {
		return iterator(nullptr, this);
	}

	const_iterator begin() const{
		//先找到有数据的哈希桶
		for (auto e : _tables) {
			if (e) {
				return iterator(e, this);
			}
		}
		return const_iterator(nullptr, this);
	}
	const_iterator end() const{
		return const_iterator(nullptr, this);
	}

	iterator Find(const K& key)
	{
		if (_tables.size() == 0)
			return end();
		HashiFunc hash;
		size_t hashi = hash(key) % _tables.size();
		KeyOfT kot;
		Node* cur = _tables[hashi];
		while (cur)
		{
			if (kot(cur->_data) == key) {
				return iterator(cur, this);
			}
			cur = cur->_next;
		}
		return end();
	}
	
	pair<iterator, bool> Insert(const T& data)
	{
		HashiFunc hash;
		KeyOfT kot;
		iterator it = Find(kot(data));
		if (it != end())
		{
			return make_pair(it, false);
		}

		// 负载因因子==1时扩容
		if (_n == _tables.size()){
			//扩容方法一:按开放定址法的方法二一样Insert(但这里问题在于每次Insert都会开空间)
			/*size_t newsize = _tables.size() == 0 ? 10 : _tables.size()*2;
			HashTable<K, V> newht;
			newht.resize(newsize);
			for (auto cur : _tables)
			{
				while (cur)
				{
					newht.Insert(cur->_kv);
					cur = cur->_next;
				}
			}

			_tables.swap(newht._tables);*/

			//扩容方法二:改变旧表数据指向,转移到新表中
			size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
			vector<Node*> newtables(newsize, nullptr);
			//for (Node*& cur : _tables)
			for (auto& cur : _tables){
				while (cur)
				{
					Node* next = cur->_next;
					size_t hashi = hash(kot(cur->_data)) % newtables.size();//HashFunc用于这种求数据在哈希表映射位置的情况
					// 头插到新表
					cur->_next = newtables[hashi];
					newtables[hashi] = cur;

					cur = next;
				}
			}
			_tables.swap(newtables);
		}

		size_t hashi = hash(kot(data)) % _tables.size();
		// 头插
		Node* newnode = new Node(data);
		newnode->_next = _tables[hashi];
		_tables[hashi] = newnode;

		++_n;
		return make_pair(iterator(newnode, this), true);
	}
private:
	vector<Node*> _tables; // 指针数组
	size_t _n = 0; // 存储有效数据个数
};

unordered_map.h

#pragma once
template<class K, class V, class HashiFunc = hashifunc<K>>
class unordered_map {
public:
	struct MapKeyOfT {
		const K& operator()(const pair<const K,V>& kv) {
			return kv.first;
		}
	};
	typedef typename HashTable<K, pair<const K, V>, MapKeyOfT, HashiFunc>::iterator iterator;
	typedef typename HashTable<K, pair<const K, V>, MapKeyOfT, HashiFunc>::const_iterator const_iterator;
	iterator begin() {
		return _ht.begin();
	}
	iterator end() {
		return _ht.end();
	}
	const_iterator begin() const
	{
		return _ht.begin();
	}
	const_iterator end() const
	{
		return _ht.end();
	}
	pair<iterator, bool> Insert(const pair<const K,V>& kv) {
		return _ht.Insert(kv);
	}
	bool Erase(const K& key) {
		return _ht.Erase(key);
	}
	iterator Find(const K& key) {
		return _ht.Find(key);
	}
	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = insert(make_pair(key, V()));
		return ret.first->second;
	}
private:
	HashTable<K, pair<const K, V>, MapKeyOfT, HashiFunc> _ht;
};
void test_unordered_map() {
	unordered_map<string, int> um;
	um.Insert(make_pair("好耶", 4));
	um.Insert(make_pair("不好", 5));
	um.Insert(make_pair("我去", 6));
	um.Erase("好耶");
	if (um.Find("好耶") != um.end()) cout << "存在" << endl;
	else cout << "不存在" << endl;
	cout << (*um.Find("我去")).second << endl;
}

unordered_set.h

#pragma once
template<class K, class HashiFunc = hashifunc<K>>
class unordered_set {
public:
	struct MapKeyOfT {
		const K& operator()(const K& key) {
			return key;
		}
	};
	//typedef typename HashTable<K, const K, MapKeyOfT, HashiFunc>::iterator iterator;//这么写要搭配HashTable<K, const K, MapKeyOfT, HashiFunc> _ht;食用
	typedef typename HashTable<K, K, MapKeyOfT, HashiFunc>::const_iterator iterator;
	typedef typename HashTable<K, K, MapKeyOfT, HashiFunc>::const_iterator const_iterator;
	iterator begin() {
		return _ht.begin();//和封装set时遇到的问题一样
	}
	iterator end() {
		return _ht.end();
	}
	const_iterator begin() const
	{
		return _ht.begin();
	}
	const_iterator end() const
	{
		return _ht.end();
	}

	pair<iterator, bool> Insert(const K& key) {
		return _ht.Insert(key);
	}
	bool Erase(const K& key) {
		return _ht.Erase(key);
	}
	iterator Find(const K& key) {
		return _ht.Find(key);
	}
private:
	HashTable<K, K, MapKeyOfT, HashiFunc> _ht;
};
void test_unordered_set() {
	unordered_set<int> um;
	um.Insert(1);
	um.Insert(3);
	um.Insert(4);
	um.Erase(1);
	if (um.Find(1) != um.end()) cout << "存在" << endl;
	else cout << "不存在" << endl;
	cout << *um.Find(3) << endl;
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS
#include"Hash.h"
#include"unordered_map.h"
#include"unordered_set.h"
#include<string>
int main() {
	test_unordered_map();
	test_unordered_set();
	return 0;
}

补充:unordered_map 与 unordered_set 的使用

● 查找或是使用[]向unordered_map 中插入 key,如果成功了返回相应位置的迭代器,那失败了该如何确定呢?
就像我们上面实现的一样,失败了返回end()

● 想要查看某数据是否存在且不用 find 函数?

size_type count ( const key_type& k ) const;//有则返回 1,无则返回 0

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

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

相关文章

如何选择性能测试工具?ab和其它工具的对比分析!

性能测试是保证应用程序高效可靠的重要手段之一&#xff0c;在进行性能测试时&#xff0c;选择合适的性能测试工具非常重要。应当根据测试需求来选择适合的测试工具&#xff0c;本文将会详细介绍ApacheBench&#xff08;简称ab&#xff09;和其他性能测试工具的区别以及如何选择…

RFID手持终端_智能pda手持终端设备定制方案

手持终端是一款多功能、适用范围广泛的安卓产品&#xff0c;具有高性能、大容量存储、高端扫描头和全网通数据连接能力。它能够快速平稳地运行&#xff0c;并提供稳定的连接表现和快速的响应时&#xff0c;适用于医院、物流运输、零售配送、资产盘点等苛刻的环境。通过快速采集…

ROS笔记一:工作空间和功能包

目录 工作空间 如何创建工作空间&#xff1a; 编译工作空间 设置环境变量 功能包 创建功能包 CMakeLists.txt package.xml 工作空间 ROS的工作空间是用来存放工程文件代码的文件夹 ROS的开发依赖于工作空间&#xff0c;包括编写代码、编译等都是在工作空间下进行的 工作空…

ACK One Argo工作流:实现动态 Fan-out/Fan-in 任务编排

作者&#xff1a;庄宇 什么是 Fan-out Fan-in 在工作流编排过程中&#xff0c;为了加快大任务处理的效率&#xff0c;可以使用 Fan-out Fan-in 任务编排&#xff0c;将大任务分解成小任务&#xff0c;然后并行运行小任务&#xff0c;最后聚合结果。 由上图&#xff0c;可以使…

【Vitis】基于C++函数开发组件的步骤

目录 基本步骤 关键领域 • 硬件接口&#xff1a; 任务级并行度&#xff1a; 存储器架构&#xff1a; 微观级别的最优化&#xff1a; 基本步骤 1. 基于 设计原则 建立算法架构。 2. &#xff08;C 语言仿真&#xff09; 利用 C/C 语言测试激励文件验证 C/C 代码的逻辑。…

2024/2/6学习记录

ts 因为已经学习过了 js &#xff0c;下面的都是挑了一些 ts 与 js 不同的地方来记录。 安装 npm install -g typescript 安装好之后&#xff0c;可以看看自己的版本 ts基础语法 模块 函数 变量 语法和表达式 注释 编译 ts 文件需要用 tsc xxx.ts &#xff0c;js 文件…

Dockerfile文件参数配置和使用

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

动态扩缩容下的全局流水号设计

关于全局流水号&#xff0c;业内用的比较多的就是雪花算法&#xff0c;一直没理解在动态扩缩容下其中的workId和 datacenterId如何设置&#xff0c;查到了几个方法&#xff1a;reidis中取&#xff0c;待后期实践下。 先简单的介绍一下雪花算法&#xff0c;雪花算法生成的Id由…

【Zookeeper】what is Zookeeper?

官网地址&#xff1a;https://zookeeper.apache.org/https://zookeeper.apache.org/ 以下来自官网的介绍 ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. A…

AI专题:AI应用落地的商业模式探索

今天分享的是AI 系列深度研究报告&#xff1a;《AI专题&#xff1a;AI应用落地的商业模式探索》。 &#xff08;报告出品方&#xff1a;国金证券&#xff09; 报告共计&#xff1a;27页 AI基座模型提供按量收费服务 以 ChatGPT 为代表的大模型能力涌现,为基座模型厂商带来增…

“小手艺”有“大情怀”, 《青春手艺人》赋能乡村振兴,传承新时代文化

文化传承发展要坚持“守正创新”&#xff0c;以守正创新的正气和锐气&#xff0c;赓续历史文脉、谱写当代华章。中央广播电视总台农业农村节目中心推出的聚焦年轻手艺人故事的微纪录片《青春手艺人》&#xff0c;为守正创新的文化传承增添了新的鲜活的青春故事。节目积极响应二…

MATLAB Fundamentals>>>(2/2) Project - Analyze Vehicle Data

#创作灵感# MATLAB基础知识官方课程学习笔记 MATLAB Fundamentals>Common Data Analysis Techniques>Summary of Common Data Analysis Techniques>(2/2) Project - Analyze Vehicle Data 任务名称&#xff1a;Fuel Economy Analysis 任务1&#xff1a; The variabl…

华为机考入门python3--(10)牛客10-字符个数统计

分类&#xff1a;字符 知识点&#xff1a; 字符的ASCII码 ord(char) 题目来自【牛客】 def count_unique_chars(s): # 创建一个空集合来保存不同的字符 unique_chars set() # 遍历字符串中的每个字符 for char in s: # 将字符转换为 ASCII 码并检查是否在范围内 #…

android retrofit上传List集合数据

由于接口需要&#xff0c;retrofit上传不能用POST,因为FormUrlEncoded注解跟Body不能共存&#xff0c;所以更改成了QueryMap 因为需要传参&#xff0c;所先将图片集合转成了Hashmap集合&#xff0c;再使用Gson 将集合转成Json 字符串 &#xff0c;再转成RequestBody 下面介绍一…

2024年【上海市安全员C3证】考试题库及上海市安全员C3证报名考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【上海市安全员C3证】考试题库及上海市安全员C3证报名考试&#xff0c;包含上海市安全员C3证考试题库答案和解析及上海市安全员C3证报名考试练习。安全生产模拟考试一点通结合国家上海市安全员C3证考试最新大纲…

微信支付服务商,商户快速进件,减少工作量

大家好&#xff0c;我是小悟 服务商拓展特约商户&#xff0c;人工录入大量商户资料&#xff0c;耗时耗力。商户对标准费率不满意&#xff0c;无法说服商户先签约再帮其调整费率。 为了减少服务商工作量&#xff0c;服务商快速进件工具来了&#xff0c;分为移动端和管理端。用好…

MyBatis多数据源以及动态切换实现(基于SpringBoot 2.7.x)

MyBatis多数据源以及动态切换实现可以实现不同功能模块可以对应到不同的数据库&#xff0c;现在就让我们来讲解一下。 目录 一、引入Maven二、配置文件三、实现多数据源四、动态切换数据源 一、引入Maven 注意&#xff1a;博主这边使用的springboot版本是2.7.14的 <!-- htt…

用 Delphi 程序调用 Python 代码画曲线图

用 Python 的库画图 Python 代码如下&#xff1a; import matplotlib.pyplot as pltsquares [1, 4, 9, 16, 25]; plt.plot(squares); plt.grid(True) # 网格线 plt.show(); # 这句话会弹出个窗口出来&#xff0c;里面是上述数据的曲线。 把以上代码&#xff0c;放进 PyS…

Node.js(五)-跨域(了解)

一 、CORS相关 1. 接口的跨域问题 html: server: 访问结果&#xff1a; 刚才编写的 GET 和 POST接口&#xff0c;存在一个很严重的问题&#xff1a;不支持跨域请求。 解决接口跨域问题的方案主要有两种&#xff1a; ① CORS&#xff08;主流的解决方案&#xff0c;推荐使…

基础面试题整理7之Redis

1.redis持久化RDB、AOF RDB(Redis database) 在当前redis目录下生成一个dump.rdb文件&#xff0c;对redis数据进行备份 常用save、bgsave命令进行数据备份&#xff1a; save命令会阻塞其他redis命令&#xff0c;不会消耗额外的内存&#xff0c;与IO线程同步&#xff1b;bgsav…