哈希相关的模拟实现

哈希相关的模拟实现

  • 哈希表的模拟实现
    • 闭散列除留取余法
      • 查找、插入和删除
      • 闭散列参考程序
    • 开散列除留取余法(数组+链表)
      • 迭代器
      • 查找和删除
      • 插入
      • 开散列参考程序
  • unordered_map和unordered_set的模拟实现
    • unordered_map
    • unordered_set

建议先看 哈希的概念及其应用-CSDN博客

哈希表的模拟实现

学习c++,最大的乐趣个人认为是搓天搓地搓空气,包括底层的操作系统也是由和c++很接近的c语言和汇编语言等搓出来的(不排除后期可能有别的)。

闭散列除留取余法

这里选用开放定址法闭散列的线性探测。但需要用枚举或其他变量来表示状态标识,因为线性探测在原来的位置满了的时候会向后探测找空位,需要根据状态来进行判断空位是否真的是否为空。

根据红黑树的模拟实现的经验,这里将通过模板参数来推演 Key 模型和 Key-Value 模型。

template<class Key,class Type,
class KeyOfType = mapKeyOfType<Key, Type>,
class Hash = HashFunc<Key> >
class HashTable {
private:vector<HashData<Type> >tables;size_t size;KeyOfType kot;Hash hashfunc;typedef HashData<Type> HashData;
};

其中的模板参数:

Key:查找哈希地址的键的类型。

Type:存储的数据的类型。

KeyOfType:从Type类型数据中获得Key型键值的仿函数。

Hash:通过Key型键值查找哈希地址的哈希函数。

查找、插入和删除

  1. 查找

查找时根据Key型键值来查找。获取哈希地址后,要查找的数据可能通过开放寻址法放到其他空位,所以需要从获取的哈希地址向后查找。

  1. 插入

首先查找要插入的数据是否存在,不存在就不插入。

然后检测负载因子。若负载因子大于 0.7 (推荐设置为0.7),则扩容。扩容时推荐创建新表,将旧表的数据重新插入新表,再删除旧表。

最后就是通过哈希函数找到哈希地址,从哈希地址开始通过线性探测寻找空位,找到后进行插入。

  1. 删除

首先查找要删除的数据是否存在,不存在直接返回。

找到要删除的数据后,将状态设置为EMPTY即可。

闭散列参考程序

因为闭散列造成数据堆积的情况相对严重,因此只适合数据量小的情况,闭散列也不是重点,这里给 Key 模型实现参考用于复习即可。

#pragma once
#include<string>
#include<vector>
#include<algorithm>
#include<iostream>
using std::string;
using std::vector;
using std::lower_bound;
using std::cout;
using std::pair;
using std::make_pair;//开放寻址法 + 除留取余法 的哈希表
namespace open_adress {enum State {EMPTY,EXIST,DELETE};template<class Key, class Type>struct mapKeyOfType {const Key& operator()(const Type& aim) {return aim.first;}};template<class Key>struct HashFunc {size_t operator()(const Key& aim) {return size_t(aim);}};template<>struct HashFunc<string> {size_t operator()(const string& aim) {size_t val = 0;for (auto& x : aim)val = val * 131 + x;return val;}};template<class Type>struct HashData {Type data;State state;HashData(const Type& data = Type(), const State& state = EMPTY):data(data), state(state) {}HashData(const HashData& aim):data(aim.data), state(aim.state) {}};inline unsigned long __stl_next_prime(unsigned long n) {// Note: assumes long is at least 32 bits.static const int __stl_num_primes = 28;static const unsigned long __stl_prime_list[__stl_num_primes] = {53, 97, 193, 389, 769,1543, 3079, 6151, 12289, 24593,49157, 98317, 196613, 393241, 786433,1572869, 3145739, 6291469, 12582917, 25165843,50331653, 100663319, 201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;}template<class Key,class Type,class KeyOfType = mapKeyOfType<Key, Type>,class Hash = HashFunc<Key> >class HashTable {private:vector<HashData<Type> >tables;size_t size;KeyOfType kot;Hash hashfunc;typedef HashData<Type> HashData;public:HashTable(const size_t& capacity = __stl_next_prime(0)) {tables.resize(capacity);size = 0;}bool insert(const Type& data) {if (find(kot(data)))return false;// 负载因子0.7就扩容if (size * 10 / tables.size() >= 7) {HashTable<Key, Type, KeyOfType>newht(__stl_next_prime(tables.size() + 1));for (auto& x : tables)if (x.state == EXIST)newht.insert(x.data);tables.swap(newht.tables);}size_t hashi = hashfunc(kot(data)) % tables.size();while (tables[hashi].state == EXIST) {hashi++;hashi %= tables.size();}tables[hashi].data = data;tables[hashi].state = EXIST;++size;return true;}HashData* find(const Key& aim) {size_t hashi = hashfunc(aim) % tables.size();while (tables[hashi].state != EMPTY) {if (kot(tables[hashi].data) == aim)return &tables[hashi];hashi = (hashi + 1) % tables.size();}return nullptr;}bool erase(const Key& aim) {HashData* ret = find(aim);if (ret) {ret->state = DELETE;return true;}return false;}public:void print() {for (auto& x : tables)if (x.state == EXIST)cout << x.data << ',';cout << "\n";}void mapprint() {for (auto& x : tables)if (x.state == EXIST)cout << "[" << x.data.first << ':' << x.data.second << "] ";cout << "\n";}};//测试用的函数,一起打包在了命名空间中template<class Key, class Type>struct setKeyOfData {const Key& operator()(const Type& aim) {return aim;}};void fset() {srand(size_t(time(0)));HashTable<int, int, setKeyOfData<int, int> >ht;vector<int>a = {94,98,2,2,97,5,15,18,19,20,22,27,27,30,33,34,38,38,42,46,48,60,61,66,70,71,73,73,77,85,85,87,88,90,91,91,93,94,95,95 };for (auto& x : a)ht.insert(x);ht.print();cout << "\n";ht.erase(61);ht.print();cout << "\n";ht.insert(61);ht.print();}void fmap() {HashTable<string, pair<string, string> >ht;ht.insert(make_pair("insert", "插入"));ht.insert(make_pair("erase", "删除"));ht.insert(make_pair("hash", "哈希"));ht.mapprint();}
}

开散列除留取余法(数组+链表)

开散列的哈希表实际上是一个支持下标索引的数组,每个元素存储一个或一组容器。当发生哈希冲突时,将同一哈希函数得到的哈希地址相同的数据放在同一容器中。这个容器称哈希桶。

哈系统可以是链表,也可以是红黑树,还可以是链表和红黑树的组合,反正就是哪个数据的查询快就用哪个。

这里的开散列哈希表只用链表实现,这样的哈希表相对容易实现。但当冲突的数据越来越多时,链表会变得很长,遍历的效率会降低。因此不作为实际应用时的参考。

参考原型:

inline unsigned long __stl_next_prime(unsigned long n) {// Note: assumes long is at least 32 bits.static const int __stl_num_primes = 28;static const unsigned long __stl_prime_list[__stl_num_primes] = {53, 97, 193, 389, 769,1543, 3079, 6151, 12289, 24593,49157, 98317, 196613, 393241, 786433,1572869, 3145739, 6291469, 12582917, 25165843,50331653, 100663319, 201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;
}template<class Key, class Type,class KeyOfType, class Hash = HashFunc<Key> >class HashTable {
private:vector<list<Type>>tables;size_t size;KeyOfType kot;Hash hashfunc;
public:HashTable(size_t capacity = __stl_next_prime(0)) {tables.resize(capacity);size = 0;}HashTable(const HashTable<Key, Type, KeyOfType, Hash>& aim):tables(aim.tables), size(aim.size), kot(aim.kot), hashfunc(aim.hashfunc){}};

__stl_next_prime函数是从c++98的stl_hashtable中获得。因为库中是使用素数作为哈希表的容量实现,所以这里选择借鉴。

tables:哈希表本体,vector数组代替。

size:统计元素个数。

kot:用于从Type类型中获取键值。

hashfunc:用于将Key型键值转化为哈希地址。

迭代器

因为决定了使用list作为哈希桶的容器,所以迭代器最好使用list的迭代器。然后迭代器需要访问哈希表以及指定位置的哈希桶。因此迭代器需要包含3个成员:list迭代器、哈希表和哈希桶。

因为哈希表直接拷贝的的话开销可能很大,所以迭代器内包含哈希表的指针,通过指针访问哈希表即可。

在实现过程中我遇到或一个问题:参考c++STL-list的模拟实现-CSDN博客,我将迭代器也设置成常量迭代器和非常量迭代器用同一个迭代器模板类推导得到,但遇到很多常属性迭代器(特别是list的迭代器)和非常属性的迭代器之间互相赋值等越权的情况。

针对这种情况,需要将迭代器类型和哈希表指针类型也作为模板参数,也可以使用条件编译选择类型(需要编译器支持c++11,且操作繁琐,不如给模板参数方便)。

因此迭代器原型参考:

template<class Key, class Type, class KeyOfType,class Ref, class Ptr, class Hash,class listiterator,class vectorptr>struct HashIterator {listiterator it;//list的迭代器vectorptr pt;//哈希表的指针size_t pb;//哈希桶typedef HashIterator<Key, Type, KeyOfType,Ref, Ptr, Hash,listiterator, vectorptr> self;HashIterator(listiterator it, vectorptr pt,size_t pb):it(it), pt(pt), pb(pb) {}HashIterator(const self& aim):it(aim.it), pt(aim.pt), pb(aim.pb) {}
};

由此也就衍生出了一个经验(个人认为):用其他容器的迭代器来实现本容器的迭代器可以上传迭代器和容器的数据类型作为模板参数

之后若迭代器不带常属性,则在容器中这样定义:

typedef HashIterator<Key, Type, KeyOfType, 
Type&, Type*, Hash,
typename list<Type>::iterator,
vector<list<Type>>*>iterator;

typename是让编译器将迭代器识别为类型。

若迭代器带常属性:

typedef HashIterator<Key, Type, KeyOfType,
const Type&, const Type*, Hash,
typename list<Type>::const_iterator, 
const vector<list<Type>>*>const_iterator;

回忆迭代器要求的几个功能,根据哈希表来实现:

  1. 支持对迭代器解引用操作。在这里的实现中直接返回*it即可。
  2. 支持对迭代器的->操作。在这里的实现中直接返回&(*it)即可,即对迭代器解引用获得数据后,再取地址返回。
  3. 支持++。先++it,若it为当前哈希桶的尾迭代器,则查找下一个非空哈希桶,找不到再重置为end()迭代器。
  4. 支持==。判断哈希桶是否为同一个,以及迭代器it是否相同即可。

其他的根据需要自行补充。

最后:若迭代器无法访问哈希表,则哈希表类可能需要将迭代器设置为友元以及前置声明。声明参考c语言的声明,可以不实现,而是放一个模板参数和不带类体的类声明语句。

查找和删除

通过哈希函数的到要查找的目标(aim)的哈希地址,然后遍历哪个位置的哈希桶即可。找不到返回尾迭代器。

删除需要先找到目标是否存在,不存在的话可以报错,也可以返回尾迭代器;存在的话通过链表的erase方法删除迭代器(可能要使用标准库的std::find工具,需展开algorithm头文件)。

成功删除迭代器后,还需检查当前哈希桶内是否还有元素,没有元素的话需要向后查找第1个非空哈希桶,将非空哈希桶的首元素迭代器返回。

否则返回尾迭代器表示删除失败。

插入

首先查找要插入的元素是否在哈希表中。在的话就返回迭代器和false组成的键值对。这样做是因为容器unordered_map需要借助insert方法来实现operator[]的操作。

然后检查负载因子,为1时扩容。开散列不需要和闭散列一样做的太过严格。

最后就是老流程:通过哈希函数获取哈希地址,将数据插入指定地址的链表中。

开散列参考程序

这里只实现了迭代器、插入、查找和删除的基本功能。其他的函数是为了哈希表能做unordered_mapunordered_set顺手实现的。

#pragma once
#include<string>
#include<vector>
#include<algorithm>
#include<iostream>
#include<list>
#include<set>
#include<cassert>
using std::string;
using std::vector;
using std::lower_bound;
using std::cout;
using std::pair;
using std::make_pair;
using std::list;
using std::set;template<class Key>
struct HashFunc {size_t operator()(const Key& aim) {return size_t(aim);}
};template<>
struct HashFunc<string> {size_t operator()(const string& aim) {size_t val = 0;for (auto& x : aim)val = val * 131 + x;return val;}
};inline unsigned long __stl_next_prime(unsigned long n) {// Note: assumes long is at least 32 bits.static const int __stl_num_primes = 28;static const unsigned long __stl_prime_list[__stl_num_primes] = {53, 97, 193, 389, 769,1543, 3079, 6151, 12289, 24593,49157, 98317, 196613, 393241, 786433,1572869, 3145739, 6291469, 12582917, 25165843,50331653, 100663319, 201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;
}//迭代器实现需要将其他容器的迭代器对象作为成员
template<class Key, class Type, class KeyOfType,class Ref, class Ptr, class Hash,class listiterator, class vectorptr>struct HashIterator {listiterator it;//list的迭代器vectorptr pt;//哈希表的指针size_t pb;//哈希桶typedef HashIterator<Key, Type, KeyOfType, Ref, Ptr, Hash,listiterator, vectorptr> self;HashIterator(listiterator it, vectorptr pt,size_t pb):it(it), pt(pt), pb(pb) {}HashIterator(const self& aim):it(aim.it), pt(aim.pt), pb(aim.pb) {}Ref operator*() {return *it;}Ptr operator->() {return &(*it);}self& operator++() {++it;if (it == (*pt)[pb].end()) {do++pb;while (pb < (*pt).size() && (*pt)[pb].empty());if (pb < (*pt).size()) {it = (*pt)[pb].begin();}else {--pb;it = (*pt)[pb].end();}}return *this;}self operator++(int) {self tmp(*this);++(*this);return tmp;}self& operator--() {//若最后一个哈希桶为空链表,则//链表的begin和end迭代器相等if (it == (*pt)[pb].begin()) {auto tmp = pb;//找到第1个非空的迭代器do--pb;while (pb > 0 && (*pt)[pb].empty());if ((*pt)[pb].empty()) {pb = tmp;return *this;}it = --(*pt)[pb].end();}else--it;return *this;}self operator--(int) {self tmp(*this);--(*this);return tmp;}bool operator==(const self& aim)const {return pb == aim.pb && (it == aim.it);}bool operator!=(const self& aim)const {return !(*this == aim);}};template<class Key, class Type,class KeyOfType, class Hash = HashFunc<Key> >class HashTable {private:vector<list<Type>>tables;size_t _size;KeyOfType kot;Hash hashfunc;public:HashTable(size_t capacity = __stl_next_prime(0)) {tables.resize(capacity);_size = 0;}HashTable(const HashTable<Key, Type, KeyOfType, Hash>& aim):tables(aim.tables), _size(aim._size), kot(aim.kot), hashfunc(aim.hashfunc) {}//迭代器模板类的友元声明template<class Key, class Type, class KeyOfType,class Ref, class Ptr, class Hash,class listiterator,class vectorptr>friend struct HashIterator;//迭代器typedef HashIterator<Key, Type, KeyOfType,Type&, Type*, Hash,typename list<Type>::iterator,vector<list<Type>>*>iterator;typedef HashIterator<Key, Type, KeyOfType,const Type&, const Type*, Hash,typename list<Type>::const_iterator,const vector<list<Type>>*> const_iterator;iterator begin() {for (size_t i = 0; i < tables.size(); i++)if (!tables[i].empty())return iterator(tables[i].begin(),(&tables), i);return end();}iterator end() {return iterator(tables[tables.size() - 1].end(),(&tables),tables.size() - 1);}const_iterator begin() const {for (size_t i = 0; i < tables.size(); i++)if (!tables[i].empty())return const_iterator(tables[i].cbegin(),const_cast<const vector<list<Type>>*>(&tables),i);return end();}const_iterator end() const {return const_iterator(tables[tables.size() - 1].cend(),const_cast<const vector<list<Type>>*>(&tables),tables.size() - 1);}//插入pair<iterator, bool> insert(const Type& aim) {auto it = find(kot(aim));if (it != end())return make_pair(it, false);//负载因子等于1时扩容if (_size == tables.size()) {//生成新表vector<list<Type>>nt(__stl_next_prime(_size + 1));for (auto& tb : tables) {for (auto& lt : tb) {size_t hashi = hashfunc(kot(lt)) % nt.size();nt[hashi].push_front(lt);}}tables.swap(nt);}size_t hashi = hashfunc(kot(aim)) % tables.size();tables[hashi].push_front(aim);++_size;return make_pair(iterator(tables[hashi].begin(), &tables, hashi), true);}//查找iterator find(const Key& aim) {size_t hashi = hashfunc(aim) % tables.size();for (auto it = tables[hashi].begin(), ed = tables[hashi].end();it != ed; it++)if (kot(*it) == aim)return iterator(it, &tables, hashi);return end();}//删除iterator erase(const Key& aim) {auto it = find(aim);if (it == end())return it;size_t hashi = hashfunc(aim) % tables.size();auto nit = tables[hashi].erase(std::find(tables[hashi].begin(), tables[hashi].end(), *it));--_size;if (nit != tables[hashi].end()) {return iterator(nit, &tables, hashi);}for (size_t i = hashi + 1; i < tables.size(); i++)if (!tables[i].empty())return iterator(tables[i].begin(), &tables, i);return end();}size_t size()const {return this->_size;}bool empty()const {return _size == 0;}void clear() {for (auto& x : tables)x.clear();_size = 0;}size_t count(const Key& val) {size_t cnt = 0, hashi = hashfunc(val) % tables.size();for (auto& x : tables[hashi])if (kot(x) == val)++cnt;return cnt;}
};//以下为测试用的函数
template<class Key, class Type>
struct setKeyOfType {const Key& operator()(const Type& aim) {return aim;}
};template<class Key, class Type>
struct mapKeyOfType {const Key& operator()(const Type& aim) {return aim.first;}
};//void f() {
//	std::vector<int>arr = { 1,3,4,5,6,7 };
//	auto it = arr.begin();
//	for (const auto ed = arr.end(); it != ed; ++it)
//		cout << *it << " ";
//}void testhash() {srand(size_t(time(0)));HashTable<int, int, setKeyOfType<int, int> >ht;vector<int>arr = { 1,2,55,92,2,3,5,6,7,8,34 };for (auto x : arr)ht.insert(x);//测试find和eraseauto it = ht.find(2);cout << *it << "\n";it = ht.erase(92);//cout << *it << endl;if (it == ht.end())cout << 92 << "\n";cout << "\n";//测试迭代器auto nit = (ht).begin();for (auto ned = ht.end(); nit != ned; nit++)cout << *nit << ' ';cout << "\n";for (const auto& x : ht)cout << x << ' ';cout << "\n";cout << "\n";//带常属性的迭代器实验const HashTable<int, int, setKeyOfType<int, int> >ht2(ht);for (auto& x : ht2)cout << x << ' ';cout << "\n";auto ht2it = ht2.begin();for (auto ht2ed = ht2.end(); ht2it != ht2ed; ++ht2it)cout << *ht2it << ' ';cout << "\n";cout << "\n";//测试Key-Value模型的匹配度HashTable<int, pair<int, int>, mapKeyOfType<int, pair<int, int>> >ht3;ht3.insert(make_pair(1, 2));ht3.insert(make_pair(2, 2));ht3.insert(make_pair(3, 2));ht3.insert(make_pair(4, 2));for (auto& x : ht3)cout << "[" << x.first << ":" << x.second << "]" << " ";cout << "\n";cout << (*ht3.find(3)).second << "\n";ht3.erase(3);for (auto& x : ht3)cout << "[" << x.first << ":" << x.second << "]" << " ";cout << "\n";ht3.insert(make_pair(3, 2));ht3.insert(make_pair(56, 2));ht3.insert(make_pair(109, 2));auto mapit = --ht3.end();for (auto maped = ht3.begin(); (mapit) != maped; --mapit)cout << "[" << (*mapit).first << ":" << (*mapit).second << "]" << " ";cout << "\n";cout << "[" << (*mapit).first << ":" << (*mapit).second << "]" << "\n";--mapit;//这里设置了--begin()不起作用cout << "[" << mapit->first << ":" << mapit->second << "]" << "\n";
}

用红黑树和链表实现的哈希表,以后有机会会实现。

unordered_map和unordered_set的模拟实现

unordered_mapunordered_set完全可以将红黑树的map和set的模拟实现的原型搬过来,将底层容器更改为开散列哈希表,再修改一些细节即可。

unordered_map

#pragma once
#include"hash_table.h"
#include<functional>
using std::less;namespace mystd {template<class Key, class Type, class Compare = less<Type> >class unordered_map {public:typedef pair<const Key, Type> val_type;public://获取键值的仿函数struct mapKeyOfType {const Key& operator()(const val_type& key) const {return key.first;}};private:HashTable<Key, pair<const Key, Type>, mapKeyOfType> ht;public://构造函数unordered_map() {}unordered_map(const unordered_map& st):ht(st.ht) {}template<class InputIterator>unordered_map(InputIterator first, InputIterator last) {for (auto it = first; it != last; ++it)insert(*it);}//迭代器typedef typenameHashTable<Key, pair<const Key, Type>,mapKeyOfType>::iteratoriterator;typedef typenameHashTable<Key, pair<const Key, Type>,mapKeyOfType>::const_iteratorconst_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 val_type& val) {return pair<iterator, bool>(ht.insert(val));}//[]访问Type& operator[](const Key& key) {//照搬库里的写法return (*(insert(make_pair(key, Type())).first)).second;}//删除bool erase(const Key& val) {size_t tmp = ht.size();auto it = ht.erase(val);if (tmp == ht.size() + 1)return true;return false;}//返回存储的元素个数size_t size()const {return ht.size();}//判空bool empty() const {return ht.empty();}//清空void clear() {ht.clear();}//查找iterator find(const Key& val) {return ht.find(val);}//计数size_t count(const Key& val) {return ht.count(val);}};void test_map() {mystd::unordered_map<string, int>mp;if (mp.empty())cout << "mp.empty()" << "\n";mp.insert(make_pair("insert", 1));if (!mp.empty())cout << mp.size() << "\n";mp["insert"] = 2;mp["erase"] = 3;mp["hash"] = 4;cout << mp.find("erase")->second << "\n";for (auto& x : mp)cout << "[" << x.first << ":" << x.second << "] ";cout << "\n";mp.erase("erase");for(auto&x:mp)cout << "[" << x.first << ":" << x.second << "] ";cout << "\n";}
}

unordered_set

#pragma once
#include"hash_table.h"
#include<functional>
using std::less;namespace mystd {template<class Type, class Compare = less<Type> >class unordered_set {public:typedef Type Key;//获取键值的仿函数struct setKeyOfType {const Type& operator()(const Type& key) const {return key;}};//构造函数unordered_set() {}unordered_set(const unordered_set<Type, Compare>& st):ht(st.ht) {}template<class InputIterator>unordered_set(InputIterator first, InputIterator last) {for (auto it = first; it != last; ++it)insert(*it);}//迭代器typedef typenameHashTable<const Key, Key,setKeyOfType>::iteratoriterator;typedef typenameHashTable<const Key, Key,setKeyOfType>::const_iteratorconst_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 Type& val) {return pair<iterator, bool>(ht.insert(val));}//删除bool erase(const Type& val) {size_t tmp = ht.size();auto it = ht.erase(val);if (tmp == ht.size() + 1)return true;return false;}//返回存储的元素个数size_t size()const {return ht.size();}//判空bool empty() const {return ht.empty();}//清空void clear() {ht.clear();}//查找iterator find(const Type& val) {return ht.find(val);}//计数size_t count(const Key& val) {return ht.count(val);}private://底层哈希表//STL容器禁止上传带const的数据类型作为容器存储的数据HashTable<const Key, Key, setKeyOfType> ht;};void fset() {mystd::unordered_set<int>st;if (st.empty())cout << "st.empty()" << "\n";st.insert(1);if (!st.empty())cout << st.size() << "\n";st.insert(2);st.insert(3);st.insert(4);cout << *st.find(3) << "\n";for (auto& x : st)cout << x << " ";cout << "\n";st.erase(3);for (auto& x : st)cout << x << " ";cout << "\n";}
}

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

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

相关文章

Vue3+Vite项目如何简单使用tsx

安装必要的依赖npm install vitejs/plugin-vue-jsx -D在 vite.config.ts 中添加以下内容import vueJsx from vitejs/plugin-vue-jsx export default {plugins: [vueJsx()] }在Vue页面使用<script lang"ts"> import { defineComponent } from vue export defaul…

05百融云策略引擎项目交付-laravel实战完整交付定义常量分文件配置-独立建立lib类处理-成功导出pdf-优雅草卓伊凡

05百融云策略引擎项目交付-laravel实战完整交付定义常量分文件配置-独立建立lib类处理-成功导出pdf-优雅草卓伊凡引言此前只是把关于如何把查询内容导出pdf库的代码实现了&#xff0c;但是我们并没有完成整个项目&#xff0c;这最后一个步骤就是安装composer再安装tcpdf库&…

模型训练速度慢排查

一、nvidia-smi 查看 GPU 的利用率与显存。若 GPU 利用率低或波动&#xff0c;说明 CPU 处理数据的速度跟不上 GPU 计算的速度&#xff0c;需要检查数据传输并调整 num_workers&#xff1b;若 GPU 显存充足&#xff0c;可以逐步增加 batch_size_per_card 直至显存占满&#xff…

STM32学习记录--Day4

今天了解了一下SPI总线&#xff1a;1.SPI内部结构​​&#x1f50c; SPI 四大核心引脚功能详解​​1. ​​MOSI (Master Output Slave Input)​​​​功能​​&#xff1a;​​主机输出数据线​​​​工作流程​​&#xff1a;主机内部发送数据寄存器 (TxDR) 的数据 → 移位寄存…

【网络安全】等级保护2.0解决方案

等保2.0&#xff08;网络安全等级保护2.0&#xff09;是我国网络安全领域的基础性制度&#xff0c;在1.0版本基础上扩展了云计算、大数据、物联网等新兴领域&#xff0c;形成覆盖全场景的安全防护框架。其核心是按信息系统重要程度划分等级&#xff08;1-5级&#xff09;&#…

TypeScript 基础介绍(二)

引言&#xff1a;从基础到结构化类型 在《TypeScript 基础介绍&#xff08;一&#xff09;》TypeScript基础介绍&#xff08;一&#xff09;-CSDN博客中&#xff0c;我们探讨了 TypeScript 的类型系统基础、联合类型、类型断言和类型守卫等核心特性。这些内容解决了 JavaScript…

【科研绘图系列】R语言绘制线性相关性

文章目录 介绍 加载R包 数据下载 导入数据 数据预处理 画图 系统信息 参考 介绍 【科研绘图系列】R语言绘制线性相关性 加载R包 library(tidyverse) library(ggplot2) library(ggsignif) library(RColorBrewer) library(dplyr) library(reshape2

FastAPI的请求-响应周期为何需要后台任务分离?

url: /posts/c7b54d6b3b6b5041654e69e5610bf3b9/ title: FastAPI的请求-响应周期为何需要后台任务分离? date: 2025-07-31T06:11:25+08:00 lastmod: 2025-07-31T06:11:25+08:00 author: cmdragon summary: FastAPI 的请求-响应周期遵循 ASGI 协议,类似于餐厅点餐流程。同步处…

多种录音笔录音芯片方案推荐

多种录音笔录音芯片方案推荐一、引言随着信息技术的飞速发展&#xff0c;录音笔作为一种重要的音频记录设备&#xff0c;在会议记录、采访、学习等众多场景中得到广泛应用。其核心的录音芯片方案直接影响录音质量、功能特性以及产品成本。唯创知音作为音频芯片领域的知名厂商&a…

Linux系统编程Day2-- Linux常用操作

一、Linux 基本命令概览以下是一些常用的Linux命令操作&#xff0c;后续我们会对其每个单独如何使用进行讲解。操作类型常用命令示例文件/目录操作ls, cd, cp, mv, rm, mkdir, rmdir查看文件内容cat, less, more, head, tail查找操作find, grep, locate, which权限管理chmod, c…

cs336 assignment1 作业环境配置

代码结构 所有的代码写到cs336_basics/* 下面&#xff0c;在adapters.py里调用自己的.py&#xff0c;通过所有的test。 作业资料参考 karpathy视频仓库&#xff1a; 视频 github仓库 测试项目运行环境 下载uv uv官网仓库 使用命令&#xff1a; powershell -ExecutionPoli…

YOLOv11来了,使用YOLOv11训练自己的数据集和推理(附YOLOv11网络结构图)

文章目录前言一、YOLOv11代码下载地址1.YOLOv11模型结构图二、数据集准备1.数据集标注软件2.voc数据集格式转换3.数据集划分4.修改yolo的训练配置文件三、YOLO环境配置教程1.pytorch环境安装2.其他依赖安装四、YOLOv11训练五、YOLOv11推理六、解决训练过程中断怎么继续上次训练…

20250731在荣品的PRO-RK3566开发板的Android13下跑通敦泰的FT8206触控芯片

20250731在荣品的PRO-RK3566开发板的Android13下跑通敦泰的FT8206触控芯片 2025/7/31 17:48缘起&#xff1a;本文前置条件&#xff1a;已经解决FT8206和PRO-RK3566的硬件连接。 通过i2cdect可以扫描到i2c从机地址&#xff1a;0x38。【8位地址为0x70】缘起&#xff1a;本文只分析…

异常检测:算法分类及经典模型概览

第一部分&#xff1a;异常检测的核心概念 在深入算法细节之前&#xff0c;理解异常检测的“语境”至关重要。 1. 什么是异常检测&#xff1f; 异常检测&#xff08;Anomaly Detection 或 Outlier Detection&#xff09;旨在通过数据挖掘技术&#xff0c;识别出数据集中与大多数…

技术干货 | 矢网DTF测量技术:透视线缆、天线与波导内部缺陷的“射频X光”(二)

无线通信、雷达等领域中&#xff0c;射频组件与传输系统的性能至关重要&#xff0c;其内部微小损伤易导致信号问题甚至系统失效。传统测试无法精确定位故障点&#xff0c;排查困难。DTF测量&#xff0c;矢网赋予的“透视眼”&#xff01;它能穿透“黑箱”&#xff0c;精确定位线…

【[CSP-J 2022] 上升点列】

题目 [CSP-J 2022] 上升点列 题目描述 在一个二维平面内&#xff0c;给定 n 个整数点 (x i ,y i​ )&#xff0c;此外你还可以自由添加 k 个整数点。 你在自由添加 k 个点后&#xff0c;还需要从 nk 个点中选出若干个整数点并组成一个序列&#xff0c;使得序列中任意相邻两点间…

Kong API Gateway的十年进化史

一、技术基因的诞生&#xff08;2007-2015&#xff09; 2007年&#xff0c;三位意大利开发者Augusto Marietti、Marco Palladino和Michele Orru在博洛尼亚的一个小车库中创立了Mashape公司。 最初他们开发了一个名为Mashup的API聚合平台&#xff0c;试图通过整合第三方API为开发…

蓝牙设备配对:从机发现主机全过程

在蓝牙 paging 过程中&#xff0c;从设备&#xff08;Slave&#xff09;是通过特定的扫描机制和跳频方式来发现主设备发送的 ID 包的&#xff0c;具体过程如下&#xff1a;从设备处于特定扫描模式&#xff1a;从设备需要处于 Page Scan 模式&#xff0c;才能够接收主设备发送的…

聚观早报 | 三星获特斯拉AI芯片订单;小米16首发成安卓最强SOC;iPhone 17 Pro支持8倍光学变焦

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。整理丨肖羽7月29日消息三星获特斯拉AI芯片订单小米16首发成安卓最强SOCiPhone 17 Pro支持8倍光学变焦宁德时代滑板底盘公司启动首轮融…

Gemini Fullstack LangGraph Quickstart(DeepSeek+Tavily版本)

文章目录参考资料说明Gemini Fullstack LangGraph QuickstartDeepSeek Fullstack LangGraph Quickstart项目部署完整源码地址后端部署前端部署参考资料 DeepResearch应用开发实战网盘课件资料 说明 本文仅供学习和交流使用&#xff0c;感谢赋范社区相关老师的辛苦付出&#…