哈希表与unordered_map

1.哈希概念

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

而最理想的搜索方法是,可以不经过任何比较,一次直接从表中得到要搜索的元素,即查找的时间复杂度为O(1)。

如果构造一种存储结构,该结构能够通过某种函数使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时就能通过该函数很快找到该元素

向该结构当中插入和搜索元素的过程如下:

插入元素: 根据待插入元素的关键码,用此函数计算出该元素的存储位置,并将元素存放到此位置。
搜索元素: 对元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按此位置取元素进行比较,若关键码相等,则搜索成功。

例如:数据集合{1,7,6,4,5,9};

哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。


该方式即为哈希(散列)方法, 哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(散列表)。

2.哈希冲突

不同关键字通过相同哈希函数计算出相同的哈希地址,这种现象称为哈希冲突或哈希碰撞。我们把关键码不同而具有相同哈希地址的数据元素称为“同义词”。那么发生哈希冲突该如何处理呢?

3.哈希函数

引起哈希冲突的一个原因可能是:哈希函数设计不够合理。

哈希函数设计原则:

哈希函数的定义域必须包括需要存储的全部关键码

如果散列表允许有m个地址时,其值域必须在0到m-1之间,哈希函数计算出来的地址能均匀分布在整个空间中.

哈希函数应该比较简单

4.常见哈希函数

1. 直接定址法

取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B优点:简单、均匀

缺点:需要事先知道关键字的分布情况使用场景:适合查找比较小且连续的情况

2. 除留余数法

设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数(也可以直接取m),按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址

5.哈希冲突解决

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

5.1 闭散列

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢?

5.1.1方法一:线性探测

当发生哈希冲突时,从发生冲突的位置开始,依次向后探测,直到找到下一个空位置为止。

Hi=(H0+i)%m ( i = 1 , 2 , 3 , . . . ) (i=1,2,3,...)(i=1,2,3,...)

H0:通过哈希函数对元素的关键码进行计算得到的位置。
Hi:冲突元素通过线性探测后得到的存放位置。
m:表的大小。

比如2.1中的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,hashAddr为4,因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

插入:通过哈希函数获取待插入元素在哈希表中的位置如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。

删除:采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素

思考:哈希表什么情况下进行扩容?如何扩容?

一般使用0.7作为负载因子,如下是线性探测的实现:

pragma once#include<iostream>
#include<vector>
#include<string>using namespace std;template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};// 特化
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash += e;hash *= 131;}return hash;}
};namespace openaddresss {enum State{EMPTY,EXIST,DELETE   //查找过程中遇到空,或者找到了才结束};template<class K, class V>struct HashData{pair<K, V> _data;State _state = EMPTY;  //HashData需要存在默认构造函数 HashData(pair<K, V> data = pair<K, V>()):_data(data){}};template<class K, class V, class Hash = HashFunc<K>>class  HashTable{typedef HashData<K, V> Node;public:HashTable(){_hsTable.resize(10);_n = 0;}//查找HashData<K, V>* find(const K& key){Hash hs;int len = _hsTable.size();int hashi = hs(key) % len;while (_hsTable[hashi]._state != EMPTY)  //查找的时候,如果这个值刚刚被删除,待查找的值可能在后面,此时依旧循环{if (_hsTable[hashi]._state == EXIST && key == _hsTable[hashi]._data.first){return &(_hsTable[hashi]);}hashi++;hashi %= len;}return nullptr;}//插入 如果映射位置冲突, ++hashi.hashi%lenbool insert(pair<K, V> data){Hash hs;K key = data.first;//先查找是否存在,存在即返回falseif (find(key) != nullptr){return false;}//扩容if ((double)_n / (double)_hsTable.size() > 0.7){HashTable<K, V, Hash> newTable;newTable._hsTable.resize(2 * _hsTable.size());//重新映射for (size_t i = 0; i < _hsTable.size(); i++){if (_hsTable[i]._state == EXIST){newTable.insert(_hsTable[i]._data);}}swap(newTable._hsTable, _hsTable);}int len = _hsTable.size();//插入int hashi = hs(key) % len;while (_hsTable[hashi]._state == EXIST){hashi++;hashi %= len;}// 已经在vector中 resize了,直接设置即可,无需new元素插入_hsTable[hashi]._data = data;_hsTable[hashi]._state = EXIST;_n++;return true;}bool Erase(const K& key){HashData<K, V>* ret = Find(key);if (ret){_n--;ret->_state = DELETE;return true;}else{return false;}}private:vector<Node> _hsTable;  //hash表size_t _n;				//hash表的有效数据个数};
}

线性探测优点:实现非常简单,

线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。

5.1.2 二次探测

线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题找下一个空位置的方法为:

Hi=(H0+i²)%m ( i = 1 , 2 , 3 , . . . ) (i=1,2,3,...)(i=1,2,3,...)

采用二次探测为产生哈希冲突的数据寻找下一个位置,相比线性探测而言,采用二次探测的哈希表中元素的分布会相对稀疏一些,不容易导致数据堆积。和线性探测一样,采用二次探测也需要关注哈希表的负载因子。

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

因此,闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷。

5.2 开散列

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。

下面是开散列的实现:

#pragma once#include<iostream>
#include<vector>
#include<string>using namespace std;template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};// 特化
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash += e;hash *= 131;}return hash;}
};//哈希表的开散列   插入中直接头插
namespace HashBucket
{template<class K, class V>struct HashNode{HashNode<K, V>* _next;  pair<K, V> _kv;			HashNode(const pair<K, V>& kv):_kv(kv), _next(nullptr){}};template<class K, class V, class Hash = HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public:     //增删查改HashTable(){_table.resize(5);}~HashTable(){for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}}Node* find(const K& key){Hash hs;int n = _table.size();//将key 映射进去int hashi = hs(key) % n;Node* cur = _table[hashi];while (cur){if (key == cur->_kv.first){return cur;}cur = cur->_next;}return nullptr;}bool insert(const pair<K, V>& kv){if (find(kv.first))return false;Hash hs;// 负载因子到1就扩容if (_n == _table.size()){//增容时,可以在素数数组中找到下一个素数作为哈希表增容后的大小,使得增容后的值的分布更均匀vector<Node*> newTables(_table.size() * 2, nullptr);for (size_t i = 0; i < _table.size(); i++){// 取出旧表中节点,重新计算挂到新表桶中Node* cur = _table[i];while (cur){Node* next = cur->_next;// 头插到新表size_t hashi = hs(cur->_kv.first) % newTables.size();cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newTables);}size_t hashi = hs(kv.first) % _table.size();Node* newnode = new Node(kv);// 头插newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return true;}bool Erase(const K& key){Hash hs;size_t hashi = hs(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hashi];while (cur){if (cur->_kv.first == key){// 删除if (prev){prev->_next = cur->_next;}else{_table[hashi] = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}void Some(){size_t bucketSize = 0;size_t maxBucketLen = 0;size_t sum = 0;double averageBucketLen = 0;for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur){++bucketSize;}size_t bucketLen = 0;while (cur){++bucketLen;cur = cur->_next;}sum += bucketLen;if (bucketLen > maxBucketLen){maxBucketLen = bucketLen;}}averageBucketLen = (double)sum / (double)bucketSize;printf("load factor:%lf\n", (double)_n / _table.size());printf("all bucketSize:%d\n", _table.size());printf("bucketSize:%d\n", bucketSize);printf("maxBucketLen:%d\n", maxBucketLen);printf("averageBucketLen:%lf\n\n", averageBucketLen);}private:size_t _n = 0;   //默认负载因子为1;vector<Node*> _table;};
}

6.用一个哈希表同时封装出unordered_map和unordered_set

哈希表模板参数的控制

unordered_set是K模型的容器,而unordered_map是KV模型的容器。

要想只用一份哈希表代码同时封装出K模型和KV模型的容器,我们必定要对哈希表的模板参数进行控制。为了与原哈希表的模板参数进行区分,这里将哈希表的第二个模板参数的名字改为T。

template<class K, class T>
class HashTable

如果上层使用的是unordered_set容器,那么传入哈希表的模板参数就是key和key。

template<class K>
class unordered_set
{
public://...
private:HashTable<K, K> _ht; //传入底层哈希表的是K和K
};

但如果上层使用的是unordered_map容器,那么传入哈希表的模板参数就是key以及key和value构成的键值对。

template<class K, class V>
class unordered_map
{
public://...
private:HashTable<K, pair<K, V>> _ht; //传入底层哈希表的是K以及K和V构成的键值对
};

更改模板参数后,哈希结点的定义如下:

//哈希结点的定义
template<class T>
struct HashNode
{T _data;HashNode<T>* _next;//构造函数HashNode(const T& data):_data(data), _next(nullptr){}
};

仿函数获取key值

由于我们在哈希结点当中存储的数据类型是T,这个T可能就是一个键值,也可能是一个键值对,对于底层的哈希表来说,它并不知道哈希结点当中存储的数据究竟是什么类型,因此需要由上层容器提供一个仿函数,用于获取T类型数据当中的键值。

因此,unordered_set和unordered_map容器都需要向底层哈希表提供一个仿函数,该仿函数返回键值对当中的键值。

string类型无法取模

template<class K>
struct Hash
{size_t operator()(const K& key) //返回键值key{return key;}
};
//string类型的特化
template<>
struct Hash<string>
{size_t operator()(const string& s) //BKDRHash算法{size_t value = 0;for (auto ch : s){value = value * 131 + ch;}return value;}
};

迭代器的封装

迭代器通常是对Node*的封装,然而我们在迭代器++时候,需要查找下一个桶的地址,因此我们还需要有hash表的初始地址。

template<class K, class T, class KeyOfValue, class Hash >// HashFunc<K>>  默认参数最好在声明中给出,这里map和set给出
class HashTable;// 迭代器			
template <class K, class T, class KeyOfValue, class HF>
struct HBIterator
{// hash的迭代器是什么 想想给什么成员可以实现operator* ++  !=//  一个节点的指针,还需要表的初始地址,这样我们就可以算出它是第一个桶typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfValue, HF> HT; //相互引用typedef HBIterator<K, T, KeyOfValue, HF> self;Node* _cur;		    //链表和树的迭代器都只包含了一个Node *HT* _ht;		//hash表需要++,故而还需要当前表的地址  HBIterator(Node* cur, HT* ht):_cur(cur),_ht(ht){}// 运算符的重载T& operator*(){return _cur->_kv;}self& operator++(){if (_cur->_next){_cur = _cur->_next;}KeyOfValue kov;HF hf;//找到下一个桶size_t i = hf(kov(_cur->_kv)) % _ht->_table.size();   //分清楚是表的大小,还是表中数组大小i++;  //i要加到下一个,从下一个桶开始查找size_t j = 0;for (j = i; j < _ht->_table.size(); j++){if (_ht->_table[j]){_cur = _ht->_table[j];break;}}if (j == _ht->_table.size()){_cur = nullptr;}return *this;}bool operator!=(const self& s){return s._cur != this->_cur;}};

解决以上几个问题,就可以实现unordered_map和unordered_set.

hashTable.h:

#pragma once// 封装改成可以实现underoredmap 和underoredset的hash表
#include<iostream>
#include<vector>
#include<string>using namespace std;template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};// 特化
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash += e;hash *= 131;}return hash;}
};//哈希表的开散列
namespace HashBucket
{//  HashNode存储T, 哈希表存的是key还是map是未知的template<class T>struct HashNode{HashNode<T>* _next =nullptr;   //需要指向下一个节点的指针T _kv;			//需要存储值HashNode(const T & kv):_kv(kv){}};// 在迭代器中需要用到HashTable本身,相互引用加前置声明 template<class K, class T, class KeyOfValue, class Hash >// HashFunc<K>>  默认参数最好在声明中给出,这里map和set给出class HashTable;// 迭代器			template <class K, class T, class KeyOfValue, class HF>struct HBIterator{// hash的迭代器是什么 想想给什么成员可以实现operator* ++  !=//  一个节点的指针,还需要表的初始地址,这样我们就可以算出它是第一个桶typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfValue, HF> HT; //相互引用typedef HBIterator<K, T, KeyOfValue, HF> self;Node* _cur;		    //链表和树的迭代器都只包含了一个Node *HT* _ht;		//hash表需要++,故而还需要当前表的地址  HBIterator(Node* cur, HT* ht):_cur(cur),_ht(ht){}// 运算符的重载T& operator*(){return _cur->_kv;}self& operator++(){if (_cur->_next){_cur = _cur->_next;}KeyOfValue kov;HF hf;//找到下一个桶size_t i = hf(kov(_cur->_kv)) % _ht->_table.size();   //分清楚是表的大小,还是表中数组大小i++;  //i要加到下一个size_t j = 0;for (j = i; j < _ht->_table.size(); j++){if (_ht->_table[j]){_cur = _ht->_table[j];break;}}if (j == _ht->_table.size()){_cur = nullptr;}return *this;}bool operator!=(const self& s){return s._cur != this->_cur;}};//template<class K,class T, class KeyOfValue, class Hash>class HashTable{public:  		//定义友元类   记住它的类型和定义方式template <class K, class T, class KeyOfValue, class HF>friend struct HBIterator;typedef HBIterator<K, T, KeyOfValue, Hash> iterator;private:typedef HashNode<T> Node;public:     //增删查改HashTable(){_table.resize(5);}~HashTable(){for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}}iterator begin() {for (size_t i = 0; i < _table.size(); i++){if (_table[i]){return iterator(_table[i], this);  //当前节点的地址和这个哈希表对象的地址构造迭代器}}return end();}iterator end() { return iterator(nullptr, this);  //用空指针和this对象来构造迭代器}size_t size() const{return _n;  //返回hash表中多少元素,与vector中不同}iterator find(const K& key){KeyOfValue kov;Hash hs;int n = _table.size();//将key 映射进去int hashi = hs(key) % n;Node* cur = _table[hashi];while (cur){if (key == kov(cur->_kv)){return iterator(cur, this);}cur = cur->_next;}return iterator(nullptr, this);}pair<iterator, bool> insert(const T& kv){KeyOfValue kov;Node* tmp = find(kov(kv))._cur;if (tmp){return make_pair(iterator(tmp,this),false);}Hash hs;// 负载因子到1就扩容if (_n == _table.size()){vector<Node*> newTables(_table.size() * 2, nullptr);for (size_t i = 0; i < _table.size(); i++){// 取出旧表中节点,重新计算挂到新表桶中Node* cur = _table[i];  //旧表中每个节点的头while (cur){Node* next = cur->_next;// 头插到新表size_t hashi = hs(kov(cur->_kv)) % newTables.size();cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newTables);}size_t hashi = hs(kov(kv)) % _table.size();Node* newnode = new Node(kv);// 头插newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return  make_pair(iterator(newnode, this), true);}iterator Erase(const K& key){Hash hs;KeyOfValue kov;size_t hashi = hs(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hashi];while (cur){if (kov(cur->_kv) == key){iterator ret = iterator(cur->_next, this);// 删除if (prev){prev->_next = cur->_next;}else{_table[hashi] = cur->_next;}delete cur;--_n;return ret;}prev = cur;cur = cur->_next;}return end();}void Some(){size_t bucketSize = 0;size_t maxBucketLen = 0;size_t sum = 0;double averageBucketLen = 0;for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur){++bucketSize;}size_t bucketLen = 0;while (cur){++bucketLen;cur = cur->_next;}sum += bucketLen;if (bucketLen > maxBucketLen){maxBucketLen = bucketLen;}}averageBucketLen = (double)sum / (double)bucketSize;printf("load factor:%lf\n", (double)_n / _table.size());printf("all bucketSize:%d\n", _table.size());printf("bucketSize:%d\n", bucketSize);printf("maxBucketLen:%d\n", maxBucketLen);printf("averageBucketLen:%lf\n\n", averageBucketLen);}private:size_t _n = 0;   //默认负载因子为1;vector<Node*> _table;};}

unordered_map.h:

#pragma once#include"HashTable.h"namespace xwy
{// unordered_map中存储的是pair<K, V>的键值对,K为key的类型,V为value的类型,HF哈希函数类型// unordered_map在实现时,只需将hashtable中的接口重新封装即可template<class K, class V, class HF = HashFunc<K>>  //缺省函数 此处给出最好class unordered_map{// 通过key获取value的操作struct KeyOfValue{const K& operator()(const pair<K, V>& data){return data.first;}};public:typedef typename HashBucket::HashTable<K,pair<K,V>,KeyOfValue,HF>::iterator iterator;iterator begin() { return _ht.begin(); }iterator end() { return _ht.end(); }// capacitysize_t size()const { return _ht.size(); }bool empty()const { return _ht.size() == 0; }// AcessV& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));iterator it = ret.first;return  (*it).second;}const V& operator[](const K& key)const{pair<iterator, bool> ret= insert(make_pair(key, V()));iterator it = ret.first;return  (*it).second;}iterator find(const K& key) { return _ht.find(key); }size_t count(const K& key) { if (find(key))return 1;return 0; }  pair<iterator, bool> insert(const pair<const K, V>& value){return _ht.insert(value);}iterator erase(iterator position){K key = (*position).first;return _ht.Erase(key);}private:HashBucket::HashTable<K, pair<K, V>, KeyOfValue, HF> _ht; //关键是成员变量是什么,怎么写,std命令空间已经展开};void test(){unordered_map<int, int> m;m.insert({ 1,1 });m.insert({ 3,3 });m.insert({ 2,2 });auto it = m.begin();cout << endl;cout << "迭代器打印:";while (it != m.end()){cout << (*it).second << " ";++it;}cout << endl;cout << "[]打印:";for (size_t i = 1; i <=m.size(); i++){cout << m[i] << endl;}}
}

unorderedset.h:

#pragma once#include"HashTable.h"namespace xwy
{template<class K, class HF = HashFunc<K>>  //缺省函数 此处给出最好class unordered_set{// 通过key获取value的操作struct KeyOfValue{const K& operator()(const K& data){return data;}};public:typedef typename HashBucket::HashTable<K,K, KeyOfValue, HF>::iterator iterator;iterator begin() { return _ht.begin(); }iterator end() { return _ht.end(); }// capacitysize_t size()const { return _ht.size(); }bool empty()const { return _ht.size() == 0; }iterator find(const K& key) { return _ht.find(key); }size_t count(const K& key){if (find(key))return 1;return 0;}pair<iterator, bool> insert(const K& value){return _ht.insert(value);}iterator erase(iterator position){K key = *position;return _ht.Erase(key);}private:HashBucket::HashTable<K, K, KeyOfValue, HF> _ht; //关键是成员变量是什么,怎么写,std命令空间已经展开};void test2(){unordered_set<int> m;m.insert(1);m.insert(3);m.insert(2);auto it = m.begin();cout << endl;while (it != m.end()){cout << *it << " ";++it;}cout << endl;//cout << "删除第一个元素后:";auto eit = m.find(1);m.erase(eit);it = m.begin();while (it != m.end()){cout << *it << " ";++it;}}
}

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

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

相关文章

Python | Leetcode Python题解之第540题有序数组中的单一元素

题目&#xff1a; 题解&#xff1a; class Solution:def singleNonDuplicate(self, nums: List[int]) -> int:low, high 0, len(nums) - 1while low < high:mid (low high) // 2mid - mid & 1if nums[mid] nums[mid 1]:low mid 2else:high midreturn nums[l…

MongoDB笔记02-MongoDB基本常用命令

文章目录 一、前言二、数据库操作2.1 选择和创建数据库2.2 数据库的删除 3 集合操作3.1 集合的显式创建3.2 集合的隐式创建3.3 集合的删除 四、文档基本CRUD4.1 文档的插入4.1.1 单个文档插入4.1.2 批量插入 4.2 文档的基本查询4.2.1 查询所有4.2.2 投影查询&#xff08;Projec…

期权懂|股指期权开户门槛不够该怎么办?

期权小懂每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 股指期权开户门槛不够该怎么办&#xff1f; 股指期权开户门槛不够&#xff0c;可以考虑利用资管分仓账户‌&#xff0c;选择已开通期权交易资格的主账户进行分仓&#xff0c;账户…

数据库SQL学习笔记

第 1 章 绪论 1.1 数据库系统概述 1.1.1 四个基本概念 数据库系统(DBS) 定义&#xff1a;是指在计算机系统中引入数据库后的系统构成 构成&#xff1a;数据库&#xff0c;数据库管理系统&#xff08;及其开发工具&#xff09;&#xff0c;应用系统&#xff0c;数据库管理员…

嵌入式常用功能之通讯协议1--串口

嵌入式常用功能之通讯协议1--串口&#xff08;本文&#xff09; 嵌入式常用功能之通讯协议1--IIC 嵌入式常用功能之通讯协议1--SPI&#xff08;待定&#xff09; ...... 一、串口协议简介 1&#xff0c;简介 UART(异步串行通信)&#xff1a;时钟基准不是同一个&#xff08…

云渲染与汽车CGI图像技术优势和劣势

在数字时代&#xff0c;云渲染技术以其独特的优势在汽车CGI图像制作中占据了重要地位。云渲染通过利用云计算的分布式处理能力&#xff0c;将渲染任务分配给云端的服务器集群进行计算&#xff0c;从而实现高效、高质量的渲染效果。 这种技术的优势主要体现在以下几个方面&#…

Anaconda超详细下载安装教程(附安装包)

文章目录 一、下载二、安装Anaconda1.解压下载的安装包2.开始安装3.测试配置是否成功4.其他问题1.查看Anaconda版本2.查看当前是否可以使用python 一、下载 Anaconda安装包下载&#xff1a;https://pan.quark.cn/s/ae29fb506730 &#xff08;直接下载&#xff0c;解压安装即可…

Go-性能优化、优化分析、调优实战pprof

使用官方自带benchmark进行基准性能测试 第一个是函数名-核数 第二个是执行次数 第三个是一次执行时间 第四个是一次执行的多大的内存 第五个是一次执行申请几次内存 slice用的时候在make&#xff08;&#xff09;初始化切片时提供容量信息 data:make([]int,0) data:make([]in…

docker安装低版本的jenkins-2.346.3,在线安装对应版本插件失败的解决方法

提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、网上最多的默认解决方法1、jenkins界面配置清华源2、替换default.json文件 二、解决低版本Jenkins在线安装插件问题1.手动下载插件并导入2.低版本jenkins在…

SIwave:释放信号网络分析仪的强大功能

SIwave 是一种电源完整性和信号完整性工具。信号网络分析器求解器是 SIwave 中的工具之一。 Signal Net Analyzer 是 SIwave 信号完整性包的一部分。它可以快速计算走线的信号完整性。它计算阻抗、延迟、损耗和许多其他东西。它允许用户研究迹线损耗和任何缺陷对注入信号的影响…

stm32 踩坑笔记

串口问题&#xff1a; 问题&#xff1a;会改变接收缓冲的下一个字节 串口的初始化如下&#xff0c;位长度选择了9位。因为要奇偶校验&#xff0c;要选择9位。但是接收有用数据只用到1个字节。 问题原因&#xff1a; 所以串口接收时会把下一个数据更改

在 CSS 中,gap 是 布局容器(flex 或 grid)的属性。它用于设置容器内子元素之间的间距。

在 CSS 中&#xff0c;gap 是 布局容器&#xff08;flex 或 grid&#xff09;的属性。它用于设置容器内子元素之间的间距。以下是 gap 属性在不同布局中的应用&#xff1a; 1. 在 CSS Grid 布局中 gap 定义了网格行和列之间的间距。可以分别使用 row-gap 和 column-gap 设置行…

Linux权限解析:用户、组和权限的协同

​​​​​​​在Linux系统中&#xff0c;权限决定了谁能做什么。本文将指导你如何掌握这些权限&#xff0c;以确保你的系统既安全又高效&#xff01; 目录 1.shell命令及其运行原理 2.Linu权限的概念 (1) 用户 (2) 切换用户命令su (3) 指令提权命令sudo (4) 什么是权限…

神经网络基础--什么是神经网络?? 常用激活函数是什么???

前言 本专栏更新神经网络的一些基础知识&#xff1b;案例代码基于pytorch&#xff1b;欢迎收藏 关注&#xff0c; 本人将会持续更新。 神经网络 1、什么是神经网络 人工神经网络&#xff08; Artificial Neural Network&#xff0c; 简写为ANN&#xff09;也简称为神经网络…

猫头虎分享: AI设计利器 Recraft详解与基础使用教程

&#x1f981;猫头虎分享&#xff1a;AI设计利器 Recraft——全面解析与教程 大家好&#xff0c;我是猫头虎&#xff01;今天为大家带来一款非常炙手可热的 AI 设计工具 —— Recraft 的深度介绍与详细教程。这款工具自推出以来&#xff0c;就迅速获得了全球设计师的青睐。那么…

Python进阶之IO操作

文章目录 一、文件的读取二、文件内容的写入三、之操作文件夹四、StringIO与BytesIO 一、文件的读取 在python里面&#xff0c;可以使用open函数来打开文件&#xff0c;具体语法如下&#xff1a; open(filename, mode)filename&#xff1a;文件名&#xff0c;一般包括该文件所…

《安富莱嵌入式周报》第345期:开源蓝牙游戏手柄,USB3.0 HUB带电压电流测量,LCR电桥前端模拟,开源微型赛车,RF信号扫描仪,开源无线电收发器

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 本周更新一期视频教程 第5期&#xff1a;RTX5/FreeRTOS全家桶源码工程综合实战模板集成CANopen组件&#xff08;2024-1…

「Mac畅玩鸿蒙与硬件20」鸿蒙UI组件篇10 - Canvas 组件自定义绘图

Canvas 组件在鸿蒙应用中用于绘制自定义图形&#xff0c;提供丰富的绘制功能和灵活的定制能力。通过 Canvas&#xff0c;可以创建矩形、圆形、路径、文本等基础图形&#xff0c;为鸿蒙应用增添个性化的视觉效果。本篇将介绍 Canvas 组件的基础操作&#xff0c;涵盖绘制矩形、圆…

用流量策略做多出口实验

一、拓扑&#xff1a; 二、配置过程&#xff1a; 1、配置 IP 地址&#xff0c;配置动态路由协议 OSPF 2、AR2 上&#xff0c;配置高级 ACL&#xff0c;允许 ospf 流量、1 到 6、2 到 8、deny 所有 3、写流分类&#xff0c;抓取流量特征 4、写流行为&#xff0c;配置流量动作 5、…

【Golang】sql.Null* 类型使用(处理空值和零值)

sql.NullString 和 sql.NullInt64 类型&#xff08;以及其他类似的 sql.Null* 类型&#xff09;在处理数据库操作时非常有用&#xff0c;尤其是在 Go 语言的 database/sql 包中。它们的主要用途包括&#xff1a; 表示 NULL 值&#xff1a; 在数据库中&#xff0c;NULL 表示“没…