C++哈希(散列)与unordered关联式容器封装(Map、Set)

一、unordered系列关联式容器

在C++98中,STL提供了以红黑树为底层数据结构的关联式容器(map、set等),查询时的效率可以达到log_{2}N,最差情况下需要比较红黑树的高度次。因此在C++11中,STL提供了四个unordered系列关联式容器,与红黑树的结构类似,但是底层结构不同。其中unordered意为无序

1.unordered_map

在unordered_map中,键值通常用于唯一标识元素,而映射值是具有与此键关联的内容的对象。键和映射值的类型可能不同。内部不会对键值或映射值进行排序,而是根据哈希值存储在桶内,并且按照键值直接快速访问单个元素(平均时间复杂度恒定)

unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问 value.

unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低

函数名称功能说明
bool empty() const检测unordered_map是否为空
size_t size() const返回unordered_map的有效元素个数
operator[]返回key对应的value值,如果无匹配key则插入该新元素
at返回unordered_map中key对应的value值,如果无匹配key则引发异常
size_t bucket_count() const返回哈希桶中桶的总个数
size_t bucket_size(size_t n) const返回n号桶中有效元素的总个数
size_t bucket(const K& key)返回元素key所在的桶号

2.unordered_set

在unordered_set中,元素的值同时也是键值。键值是不可以改变的。unordered_set中的元素不可以修改,但是可以插入和删除

unordered_set中的元素不按照任何特定顺序排序,根据哈希值存储在桶中

函数名称功能说明
operator=销毁原有unordered_set对象中的元素,并替换
pair<iterator,bool> emplace()构造和插入元素。只有原对象没有该元素才会插入
size_t bucket_count() const返回哈希桶中桶的总个数
size_t bucket_size(size_t n) const返回n号桶中有效元素的总个数
size_t bucket(const K& key)返回元素key所在的桶号
rehash()设置桶的个数

二、性能对比

运行下述测试代码可以看出

对于有序数据来说,set和map的插入性能更优,删除性能也更优

对于随机部分重复数据来说,两者差距不大

对于随机大量重复数据来说,unordered插入性能更优

综合各种场景unordered系列性能更优,尤其是find方面查找非常迅速

#include <iostream>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <map>
#include <set>
#include <vector>
#include <time.h>
using namespace std;void test_unordered_set1()
{unordered_set<int> s;s.insert(1);s.insert(3);s.insert(2);s.insert(7);s.insert(2);unordered_set<int>::iterator it = s.begin();while (it != s.end()){cout << *it << " ";++it;}cout << endl;for (auto e : s){cout << e << " ";}cout << endl;
}void test_unordered_set2()
{const size_t N = 100000;unordered_set<int> us;set<int> s;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; ++i){v.push_back(rand());//v.push_back(rand()+i);//v.push_back(i);}size_t begin1 = clock();for (auto e : v){s.insert(e);}size_t end1 = clock();cout << "set insert:" << end1 - begin1 << endl;size_t begin2 = clock();for (auto e : v){us.insert(e);}size_t end2 = clock();cout << "unordered_set insert:" << end2 - begin2 << endl;size_t begin3 = clock();for (auto e : v){s.find(e);}size_t end3 = clock();cout << "set find:" << end3 - begin3 << endl;size_t begin4 = clock();for (auto e : v){us.find(e);}size_t end4 = clock();cout << "unordered_set find:" << end4 - begin4 << endl << endl;cout << s.size() << endl;cout << us.size() << endl << endl;;size_t begin5 = clock();for (auto e : v){s.erase(e);}size_t end5 = clock();cout << "set erase:" << end5 - begin5 << endl;size_t begin6 = clock();for (auto e : v){us.erase(e);}size_t end6 = clock();cout << "unordered_set erase:" << end6 - begin6 << endl << endl;
}void test_unordered_map()
{string arr[] = { "电脑", "平板", "电脑", "平板", "电脑", "电脑", "平板", "电脑", "手机", "电脑", "手机", "平板" };map<string, int> countMap;for (auto& e : arr){countMap[e]++;}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}
}int main()
{test_unordered_map();//test_unordered_set1();//test_unordered_set2();return 0;
}

三、底层结构

由于树形搜索最优时间复杂度为log_{2}N,因此理想的搜索方法是不经过任何比较,直接一次从表中获取搜索的元素。由此寻找一种函数使得元素的存储位置与其关键字值能够建立一一映射的关系。这种方法就叫做哈希方法,使用的函数称为哈希函数,构造的结构叫做哈希表(散列表)

1.哈希函数 

对于不同的关键字值使用同一个哈希函数,可能计算出相同的哈希地址,这种现象称为冲突。引起哈希冲突可能是由于哈希函数设置的不合理,因此有以下哈希函数设计规则

哈希函数设计原则:

·哈希函数的定义域必须包括需要存储的全部关键字值。假设哈希表有m个地址,则值域必须在0到m-1之间

·哈希函数计算后关键字值能尽量均匀的分布在整个空间内

·哈希函数需要简单明了

常见的哈希函数包括除留余数法、直接定址法等

①直接定址法:取关键字的某个线性函数为散列函数,比如 h(key)=A*key+B。这种方法简单,分布均匀。但是需要事先知道关键字的分布情况,适合查找比较小且连续的情况

②除留余数法:h(key)=key mod M。M一般为散列表的长度,M的取值十分重要,M选取不当可能造成严重冲突。如果key是十进制数,则M应当避免取10的幂。一般而言,选择一个不超过M的最大的素数P

③平方取中法:这个方法是先取关键字的平方,然后根据可使用空间的大小,选取平方数是中间几位为哈希地址。假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址; 再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址。比较适合不知道关键字的分布,而位数又不是很大的情况

④折叠法:折叠法是将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位),并按照散列表的长度,取最后级位作为散列地址。适合不知道关键字分布,二关键字位数比较多的情况

⑤随机数法:选择一个随机函数,取关键字的随机函数值作为哈希地址。通常适用关键字长度不等的情况

⑥数学分析法:设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定 相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只 有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散 列地址。只适合处理关键字位数比较大,知道关键字分布并且若干位分布均匀的情况

哈希函数设计的越精妙,产生哈希冲突的可能性越低,但是哈希冲突不可被避免

2.哈希冲突解决

首先引入概念负载因子:负载因子=表中存储元素个数 / 哈希表长度。负载因子越接近一,说明元素越多,冲突的可能性越大

闭散列

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,因此可以将key存放在冲突位置的下一个位置中

寻找下一个位置的方法包括:线性探测、二次探测

 线性探测程序实现

#pragma once
//线性探测 哈希表
#include<vector>
#include<iostream>
using std::make_pair;
using std::endl;
using std::cout;
using std::pair;
using std::vector;namespace my_hashtable
{enum State{EMPTY,EXIST,DELETE};template<class K, class V>struct HashData{pair<K, V> _kv;State _state = EMPTY;};template<class K,class V>class HashTable{public:bool Insert(const pair<K, V>& kv){if (Find(kv.first)){return false;}//负载因子超过0.7就扩容if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7){size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;HashTable<K, V> newht;newht._tables.resize(newsize);//新表大小等于扩容后大小//遍历旧表,映射到新表for (auto& data : _tables){if (data._state == EXIST){newht.Insert(data._kv);}}this->_tables.swap(newht._tables);}//线性探测size_t hashi = kv.first % _tables.size();//如果是capacity可能会越界size_t i = 1;size_t index = hashi;while (_tables[index]._state == EXIST){index = hashi + i;index %= _tables.size();//取模防止越界i++;}_tables[index]._kv = kv;_tables[index]._state = EXIST;_n++;return true;}HashData<K,V>* Find(const K& key){if (_tables.size() == 0){return nullptr;}size_t hashi = key % _tables.size();//线性探测size_t i = 1;size_t index = hashi;while (_tables[index]._state != EMPTY){if (_tables[index]._state == EXIST && _tables[index]._kv.first == key){return &_tables[index];}index = hashi + i;index %= _tables.size();i++;if (index == hashi)//已经寻找一圈说明所有位置都是存在或删除状态{break;}}return nullptr;}bool Erase(const K& key){HashData<K, V>* ret = Find(key);if (ret){ret->_state = DELETE;_n--;return true;}else{return false;}}private:vector<HashData<K, V>> _tables;size_t _n = 0;//存储的数据个数};void testHashTable1(){int a[] = { 3, 33, 2, 13, 5, 12, 1002 };HashTable<int, int> ht;for (auto e : a){ht.Insert(make_pair(e, e));}ht.Insert(make_pair(15, 15));if (ht.Find(13)){cout << "13在" << endl;}else{cout << "13不在" << endl;}ht.Erase(13);if (ht.Find(13)){cout << "13在" << endl;}else{cout << "13不在" << endl;}}
}

开散列

开散列:也叫拉链法,对key值通过散列函数计算散列地址,具有相同地址的key属于同一个子集,每个子集被称为一个桶。每个桶中的元素通过一个单链表链接,各个链表的头节点存储在哈希表中

拉链法程序实现

#pragma once
//拉链法 哈希表
#include<iostream>
#include<vector>
using std::cout;
using std::endl;
using std::pair;
using std::make_pair;
using std::vector;namespace my_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:~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];//在位置上的vector中查找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()){size_t newsize = _tables.size() == 0?10 : _tables.size() * 2;vector<Node*> newtables(newsize, nullptr);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;//存储有效数据个数};void TestHashBucket1(){int a[] = { 3, 33, 2, 13, 5, 12, 1002 };HashTable<int, int> ht;for (auto e : a){ht.Insert(make_pair(e, e));}ht.Insert(make_pair(15, 15));ht.Insert(make_pair(25, 25));ht.Insert(make_pair(35, 35));ht.Insert(make_pair(45, 45));}void TestHashBucket2(){int a[] = { 3, 33, 2, 13, 5, 12, 1002 };HashTable<int, int> ht;for (auto e : a){ht.Insert(make_pair(e, e));}ht.Erase(12);ht.Erase(3);ht.Erase(33);}
}

问题分析

上面的两种程序实现都采用int类型进行测试,但是实际上哈希表中也可以存储字符串类型。当带入字符串类型进行测试时,会发现

size_t hashi = kv.first % _tables.size();

这样的取余定位方法会报错。如果一直只插入string类型数据,则可以将代码改成如下形式

size_t hashi = kv.first[1] % _tables.size();

但这样就达不到泛型编程的要求,因此需要增加模板参数使其应用场景更加广泛

因此需要修改成如下所示的代码

四、哈希表程序实现

 KV模型

#pragma once
//拉链法 哈希表
#include<iostream>
#include<vector>
using std::cout;
using std::endl;
using std::pair;
using std::make_pair;
using std::vector;
using std::string;namespace my_HashBucket
{template<class K>struct HashFunc//将double之类的可以直接转换成int类型的数据进行转换{size_t operator()(const K& key){return key;}};//特化 用于处理字符串转整形template<>struct HashFunc<string>{//如果传入空字符串就会报错//size_t operator()(const string& s)//{//	return s[0];//}size_t operator()(const string& s){size_t hash = 0;for (auto ch : s){//计算方式通过数学统计效率得知hash += ch;hash *= 31;}return hash;}};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 Hash=HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public:~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;}//定位哈希表中位置Hash hash;size_t hashi = hash(key) % _tables.size();Node* cur = _tables[hashi];//在位置上的vector中查找while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return nullptr;}bool Erase(const K& key){Hash hash;size_t hashi = hash(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()){size_t newsize = _tables.size() == 0?10 : _tables.size() * 2;vector<Node*> newtables(newsize, nullptr);for (auto& cur : _tables){while (cur){Node* next = cur->_next;Hash hash;size_t hashi = hash(cur->_kv.first) % newtables.size();//头插进新表cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}}_tables.swap(newtables);}Hash hash;size_t hashi = hash(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.仿函数 用于实现将其他类型参数转换为整型参数

2.HashNode结构体 节点保存数据与下一个节点位置

3._HashIterator结构体 迭代器结构体包括哈希表节点与哈希表

4.HashTable结构体 包括哈希表的增删查改功能,迭代器操作。其中Map式对KV模型进行操作 因此返回的都是键值对

#pragma once
//拉链法 哈希表
#include<iostream>
#include<vector>
using std::cout;
using std::endl;
using std::pair;
using std::make_pair;
using std::vector;
using std::string;template<class K>
struct HashFunc
{size_t operator()(const K& key){return key;}
};// 特化
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto ch : s){hash += ch;hash *= 31;}return hash;}
};namespace my_HashBucket
{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;//哈希表迭代器template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash >struct _HashIterator{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef _HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;typedef _HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;Node* _node;const HT* _ht;_HashIterator(Node* node, const HT* ht):_node(node), _ht(ht){}_HashIterator(const Iterator& it):_node(it._node), _ht(it._ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}Self& operator++(){if (_node->_next != nullptr){_node = _node->_next;}else//寻找不为空的桶{KeyOfT kot;Hash hash;//定位当前桶的位置size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();hashi++;while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){_node = _ht->_tables[hashi];break;}else{hashi++;}}//没找到if (hashi == _ht->_tables.size()){_node = nullptr;}}return *this;}};template<class K, class T, class KeyOfT, class Hash>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;~HashTable(){for (auto& cur : _tables){while (cur){Node* next = cur->_next;delete cur;cur = next;}cur = nullptr;}}//迭代器iterator begin(){Node* cur = nullptr;for (size_t i = 0; i < _tables.size(); i++){cur = _tables[i];if (cur){break;}}return iterator(cur, this);}iterator end(){return iterator(nullptr, this);}const_iterator begin()const{Node* cur = nullptr;for (size_t i = 0; i < _tables.size(); i++){cur = _tables[i];if (cur){break;}}return const_iterator(cur, this);}const_iterator end()const{return const_iterator(nullptr, this);}//接口函数iterator Find(const K& key){if (_tables.size() == 0){return end();}//定位哈希表中位置KeyOfT kot;Hash hash;size_t hashi = hash(key) % _tables.size();Node* cur = _tables[hashi];//在位置上的vector中查找while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return end();}bool Erase(const K& key){Hash hash;KeyOfT kot;size_t hashi = hash(key) % _tables.size();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;return true;}else{prev = cur;cur = cur->_next;}}return false;}pair<iterator, bool> Insert(const T& data){KeyOfT kot;iterator it = Find(kot(data));if (it != end()){return make_pair(it, false);//找到了}Hash hash;//负载因子为1时 扩容if (_n == _tables.size()){//size_t newsize = _tables.size() == 0?10 : _tables.size() * 2;size_t newsize = GetNextPrime(_tables.size());vector<Node*> newtables(newsize, nullptr);for (auto& cur : _tables){while (cur){Node* next = cur->_next;size_t hashi = hash(kot(cur->_data)) % newtables.size();//头插进新表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), false);}size_t GetNextPrime(size_t prime)//每扩容一次就更新模值{// SGIstatic 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};size_t i = 0;for (; i < __stl_num_primes; ++i){if (__stl_prime_list[i] > prime)return __stl_prime_list[i];}return __stl_prime_list[i];}size_t MaxBucketSize(){size_t max = 0;for (size_t i = 0; i < _tables.size(); ++i){auto cur = _tables[i];size_t size = 0;while (cur){++size;cur = cur->_next;}//printf("[%d]->%d\n", i, size);if (size > max){max = size;}}return max;}private:vector<Node*> _tables;//指针数组size_t _n = 0;//存储有效数据个数};
}

五、unorderedMap封装

#pragma once#include"HashBucket.h"namespace my_unorderedMap
{template<class K,class V,class Hash=HashFunc<K>>class unordered_map{public:struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename my_HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;typedef typename my_HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::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<K, V>& kv){return _ht.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}private:my_HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;};void test_unordered_map1(){unordered_map<int, int> m;m.insert(make_pair(1, 1));m.insert(make_pair(3, 3));m.insert(make_pair(2, 2));unordered_map<int, int>::iterator it = m.begin();while (it != m.end()){cout << it->first << ":" << it->second << endl;++it;}cout << endl;}void test_unordered_map2(){string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨" };unordered_map<string, int> countMap;for (auto& e : arr){countMap[e]++;}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}}
}

六、unorderedSet封装

#pragma once
#include"HashBucket.h"namespace my_unorderedSet
{template<class K,class Hash=HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename my_HashBucket::HashTable<K, K, SetKeyOfT, Hash>::iterator iterator;typedef typename my_HashBucket::HashTable<K, K, SetKeyOfT, Hash>::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 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:my_HashBucket::HashTable<K, K, SetKeyOfT, Hash> _ht;};void print(const unordered_set<int>& s){unordered_set<int>::const_iterator it = s.begin();while (it != s.end()){//*it = 1;cout << *it << " ";++it;}cout << endl;}void test_unordered_set1(){int a[] = { 3, 33, 2, 13, 5, 12, 1002 };unordered_set<int> s;for (auto e : a){s.insert(e);}s.insert(54);s.insert(107);unordered_set<int>::iterator it = s.begin();while (it != s.end()){//*it = 1;cout << *it << " ";++it;}cout << endl;for (auto e : s){cout << e << " ";}cout << endl;print(s);}
}

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

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

相关文章

Star History 月度开源精选|Llama 2 及周边生态特辑

7 月 18 日&#xff0c;Meta 发布了 Llama&#xff0c;大语言模型 Llama 1 的进阶版&#xff0c;可以自由免费用于研究和商业&#xff0c;支持私有化部署。 所以本期 Star History 的主题是&#xff1a;帮助你快速把 Llama 2 在自己机器上跑起来的开源工具&#xff0c;无论你的…

LeetCode 面试题 02.04. 分割链表

文章目录 一、题目二、C# 题解 一、题目 给你一个链表的头节点 head 和一个特定值 x&#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你不需要 保留 每个分区中各节点的初始相对位置。 点击此处跳转题目。 示例 1&#…

【JS案例】JS实现手风琴效果

JS案例手风琴 &#x1f31f;效果展示 &#x1f31f;HTML结构 &#x1f31f;CSS样式 &#x1f31f;实现思路 &#x1f31f;具体实现 1.绑定事件 2.自定义元素属性 3.切换菜单 &#x1f31f;完整JS代码 &#x1f31f;写在最后 &#x1f31f;效果展示 &#x1f31f;HTML…

【⑬MySQL | 数据类型(一)】简介 | 整数 | 浮点 | 定点 | 时间/日期类型

前言 ✨欢迎来到小K的MySQL专栏&#xff0c;本节将为大家带来MySQL数据类型简介 | 整数 | 浮点 | 定点 | 时间/日期类型的分享✨ 目录 前言0.数据类型简介1 整数类型2 浮点类型3 定点类型4 日期/时间类型总结 0.数据类型简介 数据类型&#xff08;data_type&#xff09;是指系…

链表(详解)

一、链表 1.1、什么是链表 1、链表是物理存储单元上非连续的、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表的指针地址实现&#xff0c;有一系列结点&#xff08;地址&#xff09;组成&#xff0c;结点可动态的生成。 2、结点包括两个部分&#xff1a;&#x…

经典问题解析四

关于动态内存分配 new 和 malloc 的区别是什么&#xff1f; delete 和 free 的区别是什么&#xff1f; new 关键字与 malloc 函数的区别 new 关键字是 C 的一部分 malloc 是由 C 库函数提供的函数 new 是以具体类型为单位进行内存分配 malloc 以字节为单位进行内存分配 …

【1267. 统计参与通信的服务器】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 这里有一幅服务器分布图&#xff0c;服务器的位置标识在 m * n 的整数矩阵网格 grid 中&#xff0c;1 表示单元格上有服务器&#xff0c;0 表示没有。 如果两台服务器位于同一行或者同一列&#xff…

【Terraform学习】使用 Terraform 从 EC2 实例访问 S3 存储桶(Terraform-AWS最佳实战学习)

使用 Terraform 从 EC2 实例访问 S3 存储桶 实验步骤 前提条件 安装 Terraform&#xff1a; 地址 下载仓库代码模版 本实验代码位于 task_ec2_s3connet 文件夹中。 变量文件 variables.tf 在上面的代码中&#xff0c;您将声明&#xff0c;aws_access_key&#xff0c;aws_…

百亿数据查询秒级响应,观测体系之日志中心该如何玩转?

日志是处理生产故障、性能优化、业务分析的重要参考依据&#xff0c;是系统稳定运行不可或缺的一部分。随着业务系统规模急剧膨胀增大&#xff0c;尤其是是微服务架构逐渐普及&#xff0c;一个系统可能涉及多个应用模块与服务实例&#xff0c;传统模式下运维人员去定位问题显得…

一文速学-让神经网络不再神秘,一天速学神经网络基础-输出层(四)

前言 思索了很久到底要不要出深度学习内容&#xff0c;毕竟在数学建模专栏里边的机器学习内容还有一大半算法没有更新&#xff0c;很多坑都没有填满&#xff0c;而且现在深度学习的文章和学习课程都十分的多&#xff0c;我考虑了很久决定还是得出神经网络系列文章&#xff0c;不…

HTML5-1-标签及属性

文章目录 语法规范标签规范标签列表通用属性基本布局 页面的组成&#xff1a; HTML&#xff08;HyperText Markup Language&#xff0c;超文本标记语言&#xff09;是用来描述网页的一种语言&#xff0c;它不是一种编程语言&#xff0c;而是一种标记语言。 HTML5 是下一代 HTM…

vue3+antdesign table实现表格行颜色

实现效果&#xff1a; 代码&#xff1a; html: <a-table:columns"stockColumns":data-source"stockData"class"ant-table-striped":rowClassName"rowClassName"></table> js: const rowClassName computed(() > {re…

Windows下MATLAB调用Python函数操作说明

MATLAB与Python版本的兼容 具体可参看MATLAB与Python版本的兼容 操作说明 操作说明请参看下面两个链接&#xff1a; 操作指南 简单说明&#xff1a; 我安装的是MATLAB2022a和Python3.8.6&#xff08;安装时请勾选所有可以勾选的&#xff0c;包括路径&#xff09;。对应版本安…

归一化的作用,sklearn 安装

目录 归一化的作用&#xff1a; 应用场景说明 sklearn 准备工作 sklearn 安装 sklearn 上手 线性回归实战 归一化的作用&#xff1a; 归一化后加快了梯度下降求最优解的速度; 归一化有可能提高精度(如KNN) 应用场景说明 1&#xff09;概率模型不需要归一化&#xff…

ArrayList与顺序表

文章目录 一. 顺序表是什么二. ArrayList是什么三. ArrayList的构造方法四. ArrayList的常见方法4.1 add()4.2 size()4.3 remove()4.4 get()4.5 set()4.6 contains()4.7 lastIndexOf()和 indexOf(&#xff09;4.8 subList()4.9 clear() 以上就是ArrayList的常见方法&#xff01…

RHCE——九、SELinux

SELinux 一、概念1、作用2、SELinux与传统的权限区别 二、SELinux工作原理1、名词解释主体&#xff08;Subject&#xff09;目标&#xff08;Object&#xff09;策略&#xff08;Policy&#xff09;安全上下文&#xff08;Security Context&#xff09; 2、文件安全上下文查看1…

element——switch接口成功后赋值打开开关

应用场景 基本用法使用v-model双向绑定值&#xff0c;进行开关控制 例子1:需求&#xff1a; **点击switch&#xff0c;出弹窗&#xff0c;点击弹窗保存按钮调接口成功后再赋值&#xff08;row.orderButtonValue“1”&#xff09;打开switch开的状态变颜色。 在vue 中使用 :va…

安全开发-JS应用NodeJS指南原型链污染Express框架功能实现审计WebPack打包器第三方库JQuery安装使用安全检测

文章内容 环境搭建-NodeJS-解析安装&库安装安全问题-NodeJS-注入&RCE&原型链案例分析-NodeJS-CTF题目&源码审计打包器-WebPack-使用&安全第三方库-JQuery-使用&安全 环境搭建-NodeJS-解析安装&库安装 Node.js是运行在服务端的JavaScript 文档参考…

分布式事务篇-2.4 Spring-Boot整合Seata

文章目录 前言一、pom jar导入:二、项目配置&#xff1a;2.1 配置 说明&#xff1a;2.1 .1 seata server 端:2.1 .2 seata client 端: 2.2 开启seata 对于数据源的代理:2.3 seata-client 的注册中心&#xff1a;2.4 seata-client 的配置中心&#xff1a;2.5 去掉手写的数据源代…

【第四阶段】kotlin语言的mutator函数学习

1.mutator特性1&#xff1a;使用list可以直接 - fun main() {val list mutableListOf(123,456,789)//特性1 可是直接使用list -list 111list-123println(list) }执行结果 2.mutator特性2&#xff1a;removeIF() 如果实现是true 会自动遍历整个集合&#xff0c;一个一个的移除…