【C++ —— 哈希】学习笔记 | 模拟实现封装unordered_map和unordered_set

文章目录

  • 前言
  • 一、unordered系列关联式容器
        • 1.1 unordered_map
        • 1.2 unordered_set
  • 二、底层结构
        • 2.1哈希概念(哈希是一种算法思想)
        • 2.2哈希冲突
        • 2.3 解决哈希冲突方法:
          • 1.直接定址法(值和位置关系是唯一关系,每个人都有唯一位置,值很分散,直接定址法会导致空间开很大,资源的浪费)
          • 2.闭散列
          • 2.1 开放地址法/线性探测
          • 3.开散列
            • 1. 哈希桶/拉链法
          • 2.字符串映射问题
  • 三、unordered_map和unordered_set封装哈希表模拟实现
      • 1.UnOrdered_map.h
      • 2.UnOrdered_set.h
      • 3.HashTable.h


前言

本文参考文档:https://legacy.cplusplus.com/reference/unordered_map/


一、unordered系列关联式容器

1.1 unordered_map

1.unordered_map接口说明

函数声明功能介绍
unordered_map构造不同格式的unordered_map对象

在这里插入图片描述
2. unordered_map的容量

函数声明功能介绍
bool empty() const检测unordered_map是否为空
size_t size() const获取unordered_map的有效元素个数

3.unordered_map的迭代器(无序set和map的迭代器都是单向迭代器)

函数功能函数介绍
begin ()返回unordered_map中第一个元素的迭代器
end()返回unordered_map中最后一个元素后一个位置的迭代器
cbegin()返回unordered_map中第一个元素的const迭代器
cend()返回unordered_map中最后一个元素后一个位置的const迭代器

4.unordered_map的元素访问

函数功能函数介绍
operator[]返回key值对应的val值(没有默认值)

注意:该函数中实际调用哈希桶的插入操作,用参数key与V()构造一个默认值往底层哈希桶
中插入,如果key不在哈希桶中,插入成功,返回V(),插入失败,说明key已经在哈希桶中,
将key对应的value返回。

5.unordered_map查询

函数功能函数介绍
find()查询key值是否存在,存在返回key值对应的迭代器的位置,返回key在哈希桶中的位置
count返回哈希桶中关键码为key的键值对的个数

注意:unordered_map中key是不能重复的,因此count函数的返回值最大为1

在这里插入图片描述
6. unordered_map的修改操作

函数功能函数介绍
insert向容器中插入键值对
erase删除容器中的键值对
void clear()清空容器中有效元素个数
void swap()交换两个容器中的元素

7.unordered_map的桶操作

函数功能函数介绍
size_t bucket_count()const返回哈希桶中桶的总个数
size_t bucket_size(size_t n)const返回n号桶中有效元素的总个数
size_t bucket(const K& key)返回元素key所在的桶号
1.2 unordered_set

unordered_set接口根map差不多一样,不过set只存key值且不能修改
这里就不过多赘述了,详情可自行观看文档
set文档资料

二、底层结构

2.1哈希概念(哈希是一种算法思想)

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即
O( l o g 2 N log_2 N log2N)
,搜索的效率取决于搜索过程中元素的比较次数。

理想的搜索方法:可以 不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立
一一映射的关系,那么在查找时通过该函数可以很快找到该元素

哈希/散列:映射 一个值和另一个值建立关系。
哈希表/散列表: 映射 关键字和存储位置建立一个关系。

2.2哈希冲突

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

把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

2.3 解决哈希冲突方法:
1.直接定址法(值和位置关系是唯一关系,每个人都有唯一位置,值很分散,直接定址法会导致空间开很大,资源的浪费)

在这里插入图片描述

2.闭散列
2.1 开放地址法/线性探测

当前位置被占用,在开放空间里按某种规则,找一个没有被占用的位置存储

  1. 线性探测 hashi + i(i >= 0)
  2. 二次探测 hashi + i^2(i >= 0)
    在这里插入图片描述
    多定义一个负载因子,存储有效关键字个数
    当有效关键字个数超过表大小的6~8成就再次扩容,用此种方法可以有效的减少哈希冲突的次数。
    以空间换效率。

    注意:
    负载因子太多:会影响效率。
    负载因子太少:会浪费空间。
    代码实现:
	enum Stutas{//删除状态的意义://1.再插入,这个位置可以覆盖//2.防止后面冲突的值,出现找不到的情况,遇到删除状态还要继续向后寻找DELETE,EMPTY,EXIST};template<class K,class V>struct HashData{pair<K,V> _kv;Stutas _s = EMPTY;};//开放地址法//线性查找,插入template<class K>struct Hashfunc{size_t operator()(const K& key){return (size_t)key;}};//struct HashfuncString//{//	//BKDR算法//	size_t operator()(const string& key)//	{//		size_t hash = 0;//		//把他们的ascll码值加起来//		for (auto e : key)//		{//			hash += e;//		}//		return hash;//	}//};template<>struct Hashfunc<string>{size_t operator()(const string& key){size_t hash = 0;//把他们的ascll码值加起来for (auto e : key){hash += e;}return hash;}};template<class K,class V, class Hash = Hashfunc<K>>class HashTable{public:HashTable(){_tables.resize(10);//resize也直接会开初始化size的大小。reserve之会开capecity的大小}//提供find解决insert能够插入相同key的问题HashData<K, V>* Find(const K& key){Hash ht;size_t hashi = ht(key) % _tables.size();while (_tables[hashi]._s != EMPTY){if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key){//找到return找到位置的地址//没找到return空return &_tables[hashi];}hashi++;//hashi超出table的size大小就给他%回来hashi = hashi % _tables.size();}return nullptr;}bool Insert(const pair<K, V>& kv){Hash ht;if (Find(kv.first)){return false;}if ((double)_n / _tables.size() >= 0.7){//不能直接扩容size的二倍,还要转移数据,重新映射关系(扩容之后值和空间的位置关系已经乱了)//释放旧空间size_t newsize = _tables.size() * 2;HashTable<K,V,Hash> newHT;newHT._tables.resize(newsize);//遍历旧表,插入新表for ( size_t i= 0; i < _tables.size(); i++){if (_tables[i]._s == EXIST){newHT.Insert(_tables[i]._kv);}}_tables.swap(newHT._tables);}//size_t hashi = kv.first % _tables.size();//key不一定是整形,如果是string呢?用仿函数把kv.first的值转化整形值//先用字符串映射一个整形size_t hashi = ht(kv.first) % _tables.size();while (_tables[hashi]._s == EXIST){hashi++;//hashi超出table的size大小就给他%回来hashi = hashi % _tables.size();}_tables[hashi]._kv = kv;_tables[hashi]._s = EXIST;++_n;}bool Erase(const K& key){HashData<K,V>* ret =  Find(key);//find找到会返回找到位置的地址if (ret){ret->_s = DELETE;--_n;return true;}return false;}void Print(){for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._s == EXIST){//cout << i << "->";cout <<"["<<i<<"]->"<< _tables[i]._kv.first << ":"<< _tables[i]._kv.second << endl;/*printf("[%d]->%d\n", i, _tables[i]._kv.first);printf("")*/}else if (_tables[i]._s == EMPTY){printf("[%d]->\n", i);}else {printf("[%d]->DELETE\n", i);}}cout << endl;}private:vector<HashData<K,V>> _tables;size_t _n = 0;//存储的关键字个数};
3.开散列
1. 哈希桶/拉链法

哈希桶的平均时间复杂度为O(1)

思路概念:
我们把每个hashi映射的位置,替换成list,链接每次插入的冲突的新key

可以参考下图:如4位置都是冲突的关键字。
在这里插入图片描述

2.字符串映射问题

整形还是可以用除留余数法计算key映射的位置,但是例如string这种字符串呢
我们可以使用仿函数,实现类型的转换。
例如把字符串的每个字符的ascll码值加起来。
代码实现:

template<class K>struct Hashfunc{size_t operator()(const K& key){return (size_t)key;}};struct HashfuncString{size_t operator()(const string& key){size_t hash = 0;//把他们的ascll码值加起来for (auto e : key){hash += e;}return hash;}};

但是这种转化函数会有一些缺陷,例如abcacb这两个不同的字符串ascll码值加起来都是相同的,导致了映射位置冲突。
有没有什么办法能解决这类的问题呢?
有大佬专门研究出了很多种算法我们可以参考:
字符串哈希算法
例如:
在这里插入图片描述
这类算法会减少字符串转哈希算法的冲突。但是肯定不能完全避免。
我们也可以模仿大佬算法完善我们的代码:

struct HashfuncString
{size_t operator()(const string& key){size_t hash = 0;//把他们的ascll码值加起来for (auto e : key){hash *= 31;//在加ascll码值前*31/131hash += e;}return hash;}
};

这种方式也是类似库里面unordered_map的实现方式(要传一个仿函数)
在这里插入图片描述
但是库里面其实比我们实现的更好一些,
库里面不用传第三个模板参数直接取模,
库里面的unordered_map用string做key可以直接转化为整型。

std::unordered_map<string,string> dict;

我们可以用特化模板参数来模拟实现这总功能。

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 hash = 0;//把他们的ascll码值加起来for (auto e : key){hash += e;}return hash;}
};

利用哈希桶实现哈希表代码:

template<class K,class V>struct HashNode{HashNode* _next;pair<K, V> _kv;HashNode(const pair<K, V>& kv):_kv(kv),_next(nullptr){}};template<class K>struct Hashfunc{size_t operator()(const K& key){return (size_t)key;}};template<>struct Hashfunc<string>{//BKDR算法size_t operator()(const string& key){size_t hash = 0;//把他们的ascll码值加起来for (auto e : key){hash *= 31;//在加ascll码值之前*31/131...hash += e;}return hash;}};template<class K,class V ,class Hash = Hashfunc<K>>class HashTable{typedef HashNode<K, V> Node;public:HashTable(){_tables.resize(10);}~HashTable(){for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;Hash ht;//扩容用开放地址法的扩容方法会产生大量的资源浪费//可以考虑直接把旧表的数据挪动下来if (_n == _tables.size()){vector<Node*> newtable;newtable.resize(_tables.size() * 2, nullptr);//遍历旧表for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){//挪动旧表的数据映射到新表Node* next = cur->_next;size_t hashi = ht(cur->_kv.first) % newtable.size();cur->_next = newtable[i];newtable[i] = cur;//利用cur把节点挪动到新表//cur利用next返回到旧表以此循环cur = next;}_tables[i] = nullptr;//旧表这个桶数据已经被挪动完,记得制空。//以防后面析构有问题}_tables.swap(newtable);}//先算出数据要存入哪个桶size_t hashi = ht(kv.first) % _tables.size();//头插Node* newnode = new Node(kv);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}Node* Find(const K& key){Hash ht;size_t hashi = ht(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return NULL;}bool Erase(const K& key){Hash ht;size_t hashi = ht(key) % _tables.size();Node* cur = _tables[hashi];Node* prev = nullptr;while (cur){if (cur->_kv.first == key){if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}prev = cur;cur = cur->_next;}return false;}void Some(){size_t bucketsize = 0;size_t maxbucketlen = 0;size_t sumbucketlen = 0;double averagebucketlen = 0;for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur == nullptr){++bucketsize;}size_t bucketlen = 0;while (cur){++bucketlen;cur = cur->_next;}sumbucketlen += bucketlen;if (bucketlen > maxbucketlen){maxbucketlen = bucketlen;}}averagebucketlen = (double)sumbucketlen / (double)bucketsize;printf("bucketsize:%d\n", bucketsize);printf("maxbucketlen:%d\n", maxbucketlen);printf("averagebucketlen:%lf\n", averagebucketlen);}private:vector<Node*> _tables;size_t _n = 0;};

三、unordered_map和unordered_set封装哈希表模拟实现

1.UnOrdered_map.h

#include"HashTable.h"namespace goat
{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;pair<iterator,bool> insert(const pair<K,V>& kv){return _mp.Insert(kv);}iterator begin(){return _mp.begin();}iterator end(){return _mp.end();}V& operator[](const K& key){pair<iterator, bool>ret = _mp.Insert(make_pair(key,V()));return ret.first->second;}const V& operator[](const K& key)const{pair<iterator, bool>ret = _mp.Insert(make_pair(key, V()));return ret.first->second;}private:Hash_Bucket::HashTable<K, pair<const K,V> ,MapKeyOfT ,Hash> _mp;};
}

2.UnOrdered_set.h

#include"HashTable.h"namespace goat
{template<class K ,class Hash = Hash_Bucket::Hashfunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename Hash_Bucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator;typedef typename Hash_Bucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator;/*iterator begin() {return _st.begin();}iterator end() {return _st.end();}*/const_iterator begin() const{return _st.begin();}const_iterator end() const{return _st.end();}iterator find(const K& key){return _st.Find(key);}bool erase(const K& key){_st.Erase(key);}//返回值pair要实现operator[]再去修改pair<iterator, bool> insert(const K& key){//return _st.Insert(key);//返回普通迭代器,但是这里insert接受的是const迭代器//正常解决方法1:支持const迭代器转化成普通迭代器,方法二:下一层Insert使用节点的指针返回(指针会产生隐式类型转换为迭代器//但是这里unordered迭代器有三个值不能使用方法二//iterator(cur , this ,hashi)//这里用另外一种简单的方法auto ret = _st.Insert(key);return pair<iterator, bool>(iterator(ret.first._node, ret.first._pht, ret.first._hashi), ret.second);}private:Hash_Bucket::HashTable<K, K , SetKeyOfT, Hash> _st;};}

3.HashTable.h

#include<iostream>
#include<vector>
#include<utility>
#include<list>
#include<set>using namespace std;namespace Hash_Bucket
{template<class T>struct HashNode{HashNode<T>* _next;T _data;//库里面的unordered是实现插入顺序遍历的//要单独维护一个链表//HashNode<T>* _linknext;//HashNode<T>* _linkprev;HashNode(const T& data):_data(data),_next(nullptr){}};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 hash = 0;for (auto e : key){hash *= 31;hash += e;}return hash;}};//解决互相依赖方法一//前置声明(不用给缺省参数)//因为hashiterator和hashtable是互相依赖的关系template<class K, class T, class KeyOfT, class Hash>class HashTable;template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>struct __HashIterator{typedef HashNode<T> Node;typedef __HashIterator<K, T,Ref,Ptr, KeyOfT, Hash> Self;Node* _node;const HashTable<K, T, KeyOfT, Hash>* _pht;//解决互相依赖方法二//实际上哈希迭代器需要的是://vector<Node*>* _ptb;//1.记录当前桶的所在位置size_t _hashi;__HashIterator(Node* node,const HashTable<K, T, KeyOfT, Hash>* pht, size_t hashi): _node(node), _pht(pht), _hashi(hashi){}Self& operator++(){if (_node->_next){//桶还有节点,就走下一个节点_node = _node->_next;}else{//当前桶已经走完了,需要下一个桶的开始//传个哈希表过来2.直接算出当前所在桶位置//KeyOfT kot;//Hash hf;//size_t hashi = hf(kot(_node->_data)) % _pht._tables.size();++_hashi;//_tables是私有成员变量//可以考虑友元while (_hashi < _pht->_tables.size()){if (_pht->_tables[_hashi]){_node = _pht->_tables[_hashi];break;}else{_hashi++;}}if (_hashi == _pht->_tables.size()){_node = nullptr;}}return *this;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}};//unordered_set -> HashTable<K,K>//unordered_map -> HashTable<K,pair<K,V>>template<class K,class T , class KeyOfT,class Hash = Hashfunc<K>>class HashTable{template<class K, class T,class Ref ,class Ptr ,class KeyOfT, class Hash>friend struct __HashIterator;typedef HashNode<T> Node;public:typedef __HashIterator<K, T, T&,T* , KeyOfT, Hash> iterator;typedef __HashIterator<K, T, const T&,const T* , KeyOfT, Hash> const_iterator;iterator begin(){for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]){return iterator(_tables[i] ,this , i);}}return end();}iterator end(){return iterator(nullptr, this, -1);}const_iterator begin() const //const修饰this-> HashTable<K, T, KeyOfT, Hash>* {for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]){return const_iterator(_tables[i], this, i);}}return end();}const_iterator end() const{return const_iterator(nullptr, this, -1);}HashTable(){_tables.resize(10);}~HashTable(){for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}pair<iterator,bool> Insert(const T& data){KeyOfT kot;/*if (Find(kot(data))){return false;}*/iterator it = Find(kot(data));if(it != end()){return make_pair(it, false);}Hash ht;//扩容用开放地址法的扩容方法会产生大量的资源浪费//可以考虑直接把旧表的数据挪动下来if (_n == _tables.size()){vector<Node*> newtable;newtable.resize(_tables.size() * 2, nullptr);//遍历旧表for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){//挪动旧表的数据映射到新表Node* next = cur->_next;size_t hashi = ht(kot(cur->_data)) % newtable.size();cur->_next = newtable[i];newtable[i] = cur;//利用cur把节点挪动到新表//cur利用next返回到旧表以此循环cur = next;}_tables[i] = nullptr;//旧表这个桶数据已经被挪动完,记得制空。//以防后面析构有问题}_tables.swap(newtable);}//先算出数据要存入哪个桶size_t hashi = ht(kot(data)) % _tables.size();//头插Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(iterator(newnode,this,hashi),true);}iterator Find(const K& key){Hash ht;KeyOfT kot;size_t hashi = ht(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur , this ,hashi);}cur = cur->_next;}return end();}bool Erase(const K& key){Hash ht;KeyOfT kot;size_t hashi = ht(key) % _tables.size();Node* cur = _tables[hashi];Node* prev = nullptr;while (cur){if (kot(cur->_data) == key){if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}prev = cur;cur = cur->_next;}return false;}void Some(){size_t bucketsize = 0;size_t maxbucketlen = 0;size_t sumbucketlen = 0;double averagebucketlen = 0;for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur == nullptr){++bucketsize;}size_t bucketlen = 0;while (cur){++bucketlen;cur = cur->_next;}sumbucketlen += bucketlen;if (bucketlen > maxbucketlen){maxbucketlen = bucketlen;}}averagebucketlen = (double)sumbucketlen / (double)bucketsize;printf("bucketsize:%d\n", bucketsize);printf("maxbucketlen:%d\n", maxbucketlen);printf("averagebucketlen:%lf\n", averagebucketlen);}private:vector<Node*> _tables;size_t _n = 0;};}

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

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

相关文章

NextJs 初级篇 - 安装 | 路由 | 中间件

NextJs 初级篇 - 安装 | 路由 | 中间件 一. NextJs 的安装二. 路由2.1 路由和页面的定义2.2 布局的定义和使用2.3 模板的定义和使用① 模板 VS 布局② 什么是 use client 2.4 路由跳转的方式2.5 动态路由2.6 路由处理程序① GET 请求的默认缓存机制② 控制缓存或者退出缓存的手…

大模型效能工具之智能CommitMessage

01 背景 随着大型语言模型的迅猛增长&#xff0c;各种模型在各个领域的应用如雨后春笋般迅速涌现。在研发全流程的效能方面&#xff0c;也出现了一系列贯穿全流程的提效和质量工具&#xff0c;比如针对成本较高的Oncall&#xff0c;首先出现了高质量的RAG助手&#xff1b;在开…

山东大学软件学院数据库实验1-9(全部)

目录 前言 实验代码 实验一 1-1 1-2 1-3 1-4 1-5 1-6 实验二 2-1 2-2 2-3 2-4 2-5 2-6 2-7 2-8 2-9 2-10 实验三 3-1 3-2 3-3 3-4 3-5 3-6 3-7 3-8 3-9 3-10 实验四 4-1 4-2 4-3 4-4 4-5 4-6 4-7 4-8 4-9 4-10 实验五 5-1…

鹏特资本进入中国市场具有以下一些优势

1. 带来资金&#xff1a;补充国内资金缺口&#xff0c;为企业发展和项目建设提供重要的资金支持。 2. 先进技术和管理经验&#xff1a;有助于推动技术创新和管理水平提升&#xff0c;促进产业升级和优化。 3. 促进竞争&#xff1a;激发国内市场活力&#xff0c;促使本土企业不…

解决 Failed to parse remote port from server output【Remote-SSH】【VSCode】

描述 一早起来&#xff0c;发现remote-ssh无法进入服务器容器&#xff0c;本地使用git bash进行ssh可正常连接服务器&#xff0c;基本确定是vscode工具本身的问题。重装本地用户的.vscode相关目录清空&#xff0c;vscode重装均无果&#xff0c;不建议尝试。弹窗信息为Could no…

【课程作业】嵌入式系统与设计上机作业(作业三)

个人名片&#xff1a; &#x1f393;作者简介&#xff1a;嵌入式领域优质创作者&#x1f310;个人主页&#xff1a;妄北y &#x1f4de;个人QQ&#xff1a;2061314755 &#x1f48c;个人邮箱&#xff1a;[mailto:2061314755qq.com] &#x1f4f1;个人微信&#xff1a;Vir2025WB…

Ant Design pro 6.0.0 搭建使用以及相关配置

一、背景 在选择一款比较合适的中台的情况下&#xff0c;挑选了有arco design、ant design pro、soybean、vue-pure-admin等中台系统&#xff0c;经过筛选就选择了ant design pro。之前使用过arco design 搭建通过组件库拼装过后台管理界面&#xff0c;官方文档也比较全&#…

2024GDCPC广东省赛记录

比赛流程体验&#xff0c;依托&#xff0c;开赛几分钟了&#xff0c;选手还卡在门外无法入场&#xff0c;也没给延时&#xff0c;说好的桌上会发三支笔&#xff0c;于是我们就没准备&#xff0c;要了三次笔&#xff0c;终于在一小时后拿到了&#x1f605; 比赛题目体验&#xf…

Java基础22(JSON解析 注解)

目录 一、JSON解析 1. JSON语法 2. JSON的用途 3. Java解析JSON 4. 使用Fastjson 4.1 Fastjson 的优点 4.2 Fastjson 导包 4.3 Fastjson的主要对象 4.4 常用方法 将Java对象 "序列化"&#xff08;转换&#xff09; 为JSON字符串&#xff1a; 将JSON字符串…

YOLOv5改进策略:Focaler-IoU损失函数改进

文章目录 1、前言2、摘要3、Focaler-IoU&#xff1a;4、代码实现5、目标检测系列文章 1、前言 ​ 目标检测是计算机视觉的基本任务之一&#xff0c;旨在识别图像中的目标并定位其位置。目标检测算法可分为基于锚点和无锚点的方法。基于锚点的方法包括Faster R-CNN、YOLO系列、…

详细分析Element Plus中的ElMessageBox弹窗用法(附Demo及模版)

目录 前言1. 基本知识2. Demo3. 实战4. 模版 前言 由于需要在登录时&#xff0c;附上一些用户说明书的弹窗 对于ElMessageBox的基本知识详细了解 可通过官网了解基本的语法知识ElMessageBox官网基本知识 1. 基本知识 Element Plus 是一个基于 Vue 3 的组件库&#xff0c;其中…

20240523每日运维--------聊聊docker简介(一)

dotCloud 说Docker&#xff0c;必不可免不得不说dotCloud&#xff0c;Docker本来只是dotCloud公司的内部项目&#xff0c;其公司创始人 Solomon Hykes 发了一个内部项目&#xff0c;而这个项目就是Docker&#xff0c;自从2013年docker开源以后&#xff0c;在世界范围引起相当轰…

对于高速信号完整性,一块聊聊啊(12)

常见的无源电子器件 电子系统中的无源器件可以按照所担当的电路功能分为电路类器件、连接类器件。 A、电路类器件&#xff1a; &#xff08;1&#xff09;二极管&#xff08;diode&#xff09; &#xff08;2&#xff09;电阻器&#xff08;resistor&#xff09; &#xf…

浅谈对称加密非对称加密

对称加密&#xff1a;加密和解密使用的密钥是同一个 常见算法&#xff1a;DES、3DES、Blowfish、IDEA、RC4、RC5、RC6 和 AES非对称加密&#xff1a;需要两个密钥&#xff0c;一个公开密钥、一个私有密钥 常见算法&#xff1a;RSA、ECC&#xff08;移动设备用&#xff09;、Dif…

归并排序算法(经典、常见)

今天我们不刷力扣了&#xff0c;我们来复习&#xff08;手撕&#xff09;一下数据结构中的八大排序算法之一&#xff0c;归并排序 基本概念&#xff1a; 归并排序&#xff08;Merge sort&#xff09;是建立在归并操作上的一种有效的排序算法&#xff0c;该算法是采用分治法&am…

【网络技术】【Kali Linux】Wireshark嗅探(十五)SSDP(简单服务发现协议)报文捕获及分析

往期 Kali Linux 上的 Wireshark 嗅探实验见博客&#xff1a; 【网络技术】【Kali Linux】Wireshark嗅探&#xff08;一&#xff09;ping 和 ICMP 【网络技术】【Kali Linux】Wireshark嗅探&#xff08;二&#xff09;TCP 协议 【网络技术】【Kali Linux】Wireshark嗅探&…

与MySQL DDL 对比分析OceanBase DDL的实现

本文将简要介绍OceanBase的DDL实现方式&#xff0c;并通过与MySQL DDL实现的对比&#xff0c;帮助大家更加容易理解。 MySQL DDL 的算法 MySQL 的DDL实现算法主要有 copy、inplace和instant。 copy copy算法的实现相对简单&#xff0c;MySQL首先会创建一个临时表&#xff0…

C++:STL

STL 文章目录 STLSTL 绪论迭代器&#xff08;iterators&#xff09;容器&#xff08;Containers&#xff09;vectorset,multisetmap,multimapstackqueuedequepriority_queuebitset 算法&#xff08;Algorithms&#xff09;sort,count,find,lower_bound,upper_bound,binary_sear…

(2024,attention,可并行计算的 RNN,并行前缀扫描)将注意力当作 RNN

Attention as an RNN 公众号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 3. 方法 3.1 注意力作为一种&#xff08;多对一的&#xff09;RNN 3.2 注意力作为&#xff08;多对多&…

多语言印度红绿灯系统源码带三级分销代理功能

前端为2套UI&#xff0c;一套是html写的&#xff0c;一套是编译后的前端 后台功能很完善&#xff0c;带预设、首充返佣、三级分销机制、代理功能。 东西很简单&#xff0c;首页就是红绿灯的下注页面&#xff0c;玩法虽然单一&#xff0c;好在不残缺可以正常跑。