当前位置: 首页 > news >正文

深入浅出C++ ——哈希

文章目录

    • 前言
  • 一、unordered系列关联式容器
    • 1. unordered_map
    • 2. unordered_set
  • 二、哈希
    • 1. 哈希概念
    • 2. 哈希冲突
    • 3. 哈希函数
    • 4. 哈希冲突解决方法
  • 三、模拟实现unordered系列容器
    • 1. 哈希表的改造
    • 2. 模拟实现 unordered_set
    • 3. 模拟实现 unordered_map

前言

    在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,unordered系列的关联式容器的底层结构采用哈希结构。
    map/set的遍历是有序的,而unordered系列容器遍历是无序的。map/set是双向迭代器,而unordered系列容器只支持单向迭代器。但是当对大量数据进行增删改查时,unordered系列容器的效率更高,尤其是查找的效率。

一、unordered系列关联式容器

1. unordered_map

构造

函数声明功能介绍
unordered_map()构造空的unordered_map对象

容量

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

迭代器

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

元素访问

函数声明功能介绍
operator[]返回与key对应的value,没有一个默认值

查询

函数声明功能介绍
iterator find(const K& key)返回key在哈希桶中的位置
size_t count(const K& key)返回哈希桶中关键码为key的键值对的个数

修改

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

桶操作

函数声明功能介绍
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_map类似,故不在说明。另外,在使用方面,unordered系列的容器与map/set容器也基本类似。可以说除了底层结构和unordered系列操作哈希桶外,两者没有区别。

二、哈希

1. 哈希概念

    顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(log2Nlog_2 Nlog2N),搜索的效率取决于搜索过程中元素的比较次数。理想的搜索方法是不经过任何比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。


哈希方法

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

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


2. 哈希冲突

    对于两个数据元素的关键字kik_ikikjk_jkj(i != j),有kik_iki != kjk_jkj,但有:Hash(kik_iki) ==Hash(kjk_jkj),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。引起哈希冲突的一个原因可能是哈希函数设计不够合理。哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突


3. 哈希函数

(1)直接定址法

     取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B ;该方法优点是简单、均匀,不存在哈希冲突,缺点是需要事先知道关键字的分布情况,适合查找比较小且连续的情况。


(2)除留余数法

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


(3)其他方法

     哈希函数还有采用平方取中法、折叠法、随机数法、数学分析法等等,但是不常用,常用的还是前两种方法。


4. 哈希冲突解决方法

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


(1)闭散列

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


线性探测

     线性探测是从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止

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

    给定一个负载因子为填入表中的元素个数/散列表的长度,负载因子越大,冲突概率越大,效率越低,空间利用率越高;负载因子越小,冲突概率越小,效率越高,空间利用率越低。

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


二次探测

     线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为:HiH_iHi = (H0H_0H0 + i2i^2i2 )% m, 其中:i =1,2,3…, H0H_0H0是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小。


代码实现

namespace CloseHash
{//哈希表每个空间给个标记,EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除enum State{EMPTY,EXIST,DELETE};template<class K, class V>struct HashData{pair<K, V> _kv;State _state = EMPTY;};//使用仿函数,将key转换为无符号的整形,方便插入时取模计算位置template<class K> struct HashFunc{size_t operator()(const K& key){return (size_t)key;  }};// 模板特化  支持string类型转换为无符号整形template<>struct HashFunc<string>{// BKDR算法size_t operator()(const string& key){size_t val = 0;for (auto ch : key){val *= 131;val += ch;}return val;}};template<class K, class V, class Hash = HashFunc<K>>class HashTable{public:bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;// 负载因子到了就扩容if (_tables.size() == 0 || 10 * _size / _tables.size() >= 7) // 扩容{size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;HashTable<K, V, Hash> newHT; //这样可以复用insert函数newHT._tables.resize(newSize);// 旧表的数据映射到新表for (auto e : _tables){if (e._state == EXIST){newHT.Insert(e._kv);}}_tables.swap(newHT._tables);}//使用仿函数,将key转换为无符号的整形Hash hash;size_t hashi = hash(kv.first) % _tables.size();// 线性探测while (_tables[hashi]._state == EXIST){hashi++;hashi %= _tables.size();}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_size; 二次探测//Hash hash;//size_t start = hash(kv.first) % _tables.size();//size_t i = 0;//size_t hashi = start;//while (_tables[hashi]._state == EXIST)//{//	++i;//	hashi = start + i*i;//	hashi %= _tables.size();//}//_tables[hashi]._kv = kv;//_tables[hashi]._state = EXIST;//++_size;return true;}HashData<K, V>* Find(const K& key){if (_tables.size() == 0){return nullptr;}Hash hash;size_t start = hash(key) % _tables.size();size_t hashi = start;while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state != DELETE && _tables[hashi]._kv.first == key){return &_tables[hashi];}hashi++;hashi %= _tables.size();if (hashi == start){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;}}void Print(){for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]._state == EXIST){printf("[%d:%d] ", i, _tables[i]._kv.first);}else{printf("[%d:*] ", i);}}cout << endl;}private:vector<HashData<K, V>> _tables;size_t _size = 0; // 存储多少个有效数据};
}

(2)开散列

     开散列法又叫链地址法/开链法首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。 如图所示,开散列中每个桶中放的都是发生哈希冲突的元素。
在这里插入图片描述


开散列增容

     开散列最好的情况是每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。


哈希桶实现开散列

//使用仿函数,将key转换为无符号的整形,方便插入时取模计算位置
template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct HashFunc<string>
{// BKDRsize_t operator()(const string& key){size_t val = 0;for (auto ch : key){val *= 131;val += ch;}return val;}
};namespace Bucket
{template<class K, class V>struct HashNode{pair<K, V> _kv;HashNode<K, V>* _next;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(){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;}}// 如果hash的size是素数,可以减少冲突inline size_t __stl_next_prime(size_t n){static const size_t __stl_num_primes = 28;static const size_t __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};for (size_t i = 0; i < __stl_num_primes; ++i){if (__stl_prime_list[i] > n){return __stl_prime_list[i];}}return -1;}bool Insert(const pair<K, V>& kv){// 去重if (Find(kv.first)){return false;}Hash hash;// 负载因子到1就扩容if (_size == _tables.size()){//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;vector<Node*> newTables;//newTables.resize(newSize, nullptr);newTables.resize(__stl_next_prime(_tables.size()), nullptr); //扩容时,resize素数表中数据的大小// 旧表中节点移动映射新表for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];while (cur){Node* next = cur->_next;size_t hashi = hash(cur->_kv.first) % newTables.size();cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}size_t hashi = hash(kv.first) % _tables.size();// 头插Node* newnode = new Node(kv);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_size;return true;}Node* Find(const K& key){if (_tables.size() == 0){return nullptr;}Hash hash;size_t hashi = hash(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){if (_tables.size() == 0){return false;}Hash hash;size_t hashi = hash(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){// 1、头删// 2、中间删if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;--_size;return true;}prev = cur;cur = cur->_next;}return false;}size_t Size(){return _size;}// 表的长度size_t TablesSize(){return _tables.size();}// 桶的个数size_t BucketNum(){size_t num = 0;for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]){++num;}}return num;}size_t MaxBucketLenth(){size_t maxLen = 0;for (size_t i = 0; i < _tables.size(); ++i){size_t len = 0;Node* cur = _tables[i];while (cur){++len;cur = cur->_next;}if (len > maxLen){maxLen = len;}}return maxLen;}private:vector<Node*> _tables;size_t _size = 0; // 存储有效数据个数};
}

开散列与闭散列比较

    由于闭散列法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子,而表项所占空间又比指针大的多,所以使用开散列法反而比闭散列法节省存储空间。


三、模拟实现unordered系列容器

1. 哈希表的改造

    哈希表的改造时,对模板参数列表的改造,增加迭代器操作

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};// 特化  
template<>
struct HashFunc<string>
{// BKDRsize_t operator()(const string& key){size_t val = 0;for (auto ch : key){val *= 131;val += ch;}return val;}
};namespace Bucket
{template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};// 前置声明template<class K, class T, class Hash, class KeyOfT>class HashTable;//迭代器template<class K, class T, class Hash, class KeyOfT>struct __HashIterator{typedef HashNode<T> Node;typedef HashTable<K, T, Hash, KeyOfT> HT;typedef __HashIterator<K, T, Hash, KeyOfT> Self;Node* _node;HT* _pht;__HashIterator(Node* node, HT* pht):_node(node), _pht(pht){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}Self& operator++(){if (_node->_next){// 当前桶中迭代_node = _node->_next;}else{// 找下一个桶Hash hash;KeyOfT kot;size_t i = hash(kot(_node->_data)) % _pht->_tables.size();++i;for (; i < _pht->_tables.size(); ++i){if (_pht->_tables[i]){_node = _pht->_tables[i];break;}}// 说明后面没有有数据的桶了if (i == _pht->_tables.size()){_node = nullptr;}}return *this;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}};template<class K, class T, class Hash, class KeyOfT>class HashTable{typedef HashNode<T> Node;template<class K, class T, class Hash, class KeyOfT>friend struct __HashIterator;public:typedef __HashIterator<K, T, Hash, KeyOfT> iterator;iterator begin(){for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]){return iterator(_tables[i], this);}}return end();}iterator end(){return iterator(nullptr, this);}~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;}}inline size_t __stl_next_prime(size_t n){static const size_t __stl_num_primes = 28;static const size_t __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};for (size_t i = 0; i < __stl_num_primes; ++i){if (__stl_prime_list[i] > n){return __stl_prime_list[i];}}return -1;}pair<iterator, bool> Insert(const T& data){Hash hash;KeyOfT kot;// 去重iterator ret = Find(kot(data));if (ret != end()){return make_pair(ret, false);}// 负载因子到1就扩容if (_size == _tables.size()){//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;vector<Node*> newTables;//newTables.resize(newSize, nullptr);newTables.resize(__stl_next_prime(_tables.size()), nullptr);// 旧表中节点移动映射新表for (size_t i = 0; i < _tables.size(); ++i){Node* cur = _tables[i];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[i] = nullptr;}_tables.swap(newTables);}size_t hashi = hash(kot(data)) % _tables.size();// 头插Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_size;return make_pair(iterator(newnode, this), true);}iterator Find(const K& key){if (_tables.size() == 0){return end();}Hash hash;KeyOfT kot;size_t hashi = hash(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return end();}bool Erase(const K& key){if (_tables.size() == 0){return false;}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){// 1、头删// 2、中间删if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;--_size;return true;}prev = cur;cur = cur->_next;}return false;}size_t Size(){return _size;}// 表的长度size_t TablesSize(){return _tables.size();}// 桶的个数size_t BucketNum(){size_t num = 0;for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]){++num;}}return num;}size_t MaxBucketLenth(){size_t maxLen = 0;for (size_t i = 0; i < _tables.size(); ++i){size_t len = 0;Node* cur = _tables[i];while (cur){++len;cur = cur->_next;}//if (len > 0)//printf("[%d]号桶长度:%d\n", i, len);if (len > maxLen){maxLen = len;}}return maxLen;}private:vector<Node*> _tables;size_t _size = 0; // 存储有效数据个数};
}

2. 模拟实现 unordered_set

namespace Jared
{template<class K, class Hash = HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename Bucket::HashTable<K, K, Hash, SetKeyOfT>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}pair<iterator, bool> insert(const K& key){return _ht.Insert(key);}private:Bucket::HashTable<K, K, Hash, SetKeyOfT> _ht;};
}

3. 模拟实现 unordered_map

namespace Jared
{template<class K, class V, class Hash = HashFunc<K>>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename Bucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){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 = _ht.Insert(make_pair(key, V()));return ret.first->second;}private:Bucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT> _ht;};
}

相关文章:

深入浅出C++ ——哈希

文章目录前言一、unordered系列关联式容器1. unordered_map2. unordered_set二、哈希1. 哈希概念2. 哈希冲突3. 哈希函数4. 哈希冲突解决方法三、模拟实现unordered系列容器1. 哈希表的改造2. 模拟实现 unordered_set3. 模拟实现 unordered_map前言 在C11中&#xff0c;STL又提…...

Tina_Linux_系统裁剪_开发指南

文章目录Tina_Linux_系统裁剪_开发指南1 概述2 Tina系统裁剪简介2.1 boot0裁剪2.2 uboot裁剪2.3 内核裁剪2.3.1 删除不使用的功能2.3.2 删除不使用的驱动2.3.3 修改内核源代码2.3.3.1 size工具.2.3.3.2 ksize.py脚本2.3.3.3 nm命令2.3.3.4 kernel压缩方式.2.4 文件系统裁剪.2.4…...

算法刷题打卡第99天:至少在两个数组中出现的值

至少在两个数组中出现的值 难度&#xff1a;简单 给你三个整数数组 nums1、nums2 和 nums3 &#xff0c;请你构造并返回一个 元素各不相同的 数组&#xff0c;且由 至少 在 两个 数组中出现的所有值组成。数组中的元素可以按 任意 顺序排列。 示例 1&#xff1a; 输入&…...

线程池面试题

1. 什么是线程池&#xff1f;为什么要使用线程池&#xff1f; 线程池是一种用于管理线程的技术&#xff0c;它可以在应用程序中重复使用一组线程来执行多个任务。线程池的优点包括提高应用程序的性能和可伸缩性、避免线程创建和销毁的开销、避免线程过多导致系统负担过重等。线…...

【学习笔记】NOIP爆零赛5

说实话是不想补题的。因为每一道题都贼难写&#xff0c;题解又通篇写着显然&#xff0c;然后自己天天搞竞赛又把注意力搞差了&#xff0c;调一道题又调半天&#xff0c;考试的题又难的要死 不会正解 &#xff0c;部分分又写挂了 可能心态崩了就是从那场t1t1t1签到题考高精度数位…...

【数据结构】时间复杂度

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a;初阶数据结构 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对…...

vector的基本使用

目录 介绍&#xff1a; vector iterator 的使用 增删查改 增&#xff08;push_back insert&#xff09;&#xff1a; 删(pop_back erase)&#xff1a; swap&#xff1a; vector的容量和扩容&#xff1a; 排序&#xff08;sort&#xff09;&#xff1a; 介绍&#xff…...

剑指 Offer 55 - I. 二叉树的深度

摘要 剑指 Offer 55 - I. 二叉树的深度 一、深度优先搜索 如果我们知道了左子树和右子树的最大深度l和r&#xff0c;那么该二叉树的最大深度即为&#xff1a;max(l,r)1。 而左子树和右子树的最大深度又可以以同样的方式进行计算。因此我们可以用「深度优先搜索」的方法来计…...

Bean的生命周期和作用域

Bean的生命周期Bean的执行流程&#xff1a;Bean 执行流程&#xff1a;启动Spring 容器 -> 实例化 Bean&#xff08;分配内存空间&#xff0c;从无到有&#xff09;-> Bean 注册到 Spring 中&#xff08;存操作&#xff09; -> 将 Bean 装配到需要的类中&#xff08;取…...

TestNG和Junit的区别,测试框架该如何选择?

要想知道两个框架的区别&#xff0c;首先分别介绍一下两个框架。 TestNG是一个java中的开源自动化测试框架&#xff0c;其灵感来自JUnit和NUnit&#xff0c;TestNG还涵盖了JUnit4整个核心的功能&#xff0c;但引入了一些新的功能&#xff0c;使其功能更强大&#xff0c;使用更…...

MySQL安全登录策略

MySQL密码复杂度策略设置 MySQL 系统自带有 validate_password 插件&#xff0c;此插件可以验证密码强度&#xff0c;未达到规定强度的密码则不允许被设置。MySQL 5.7 及 8.0 版本默认情况下貌似都不启用该插件&#xff0c;这也使得我们可以随意设置密码&#xff0c;比如设置为…...

优化模型验证23:带无人机停靠站的卡车无人机协同配送车辆路径问题、模型、gurobipy验证及结果可视化

带中转hub的卡车无人机车辆路径问题 模型来源为:Wang Z , Sheu J B . Vehicle routing problem with drones[J]. Transportation Research Part B: Methodological, 2019, 122(APR.):350-364. 问题描述: 这篇问题研究了一个带停靠站的卡车无人机路径问题,无人机仅能从起点…...

mongoTemplate Aggregation 多表联查 排序失效问题解决

目录说明说明 接着上一个文章的例子来说&#xff1a;mongoTemplate支持多表联查 排序 条件筛选 分页 去重分组 在按照上一个demo的代码执行后&#xff0c;可能会发生排序失效的问题&#xff0c;为什么说可能呢&#xff1f;每个人负责业务不同&#xff0c;不可能是最简单的dem…...

什么是智慧实验室?

智慧实验室是利用现代信息技术和先进设备将实验室实现智能化和智慧化的概念。通过将各种数据、信息和资源整合在一起&#xff0c;实现实验室设备的互联互通&#xff0c;数据的实时采集、传输、处理和分析&#xff0c;从而提高实验室的效率、精度和可靠性。一、智慧实验室包含多…...

Python abs() 函数

Python abs() 函数Python 数字描述abs() 函数返回数字的绝对值。语法以下是 abs() 方法的语法:abs( x )参数x -- 数值表达式。返回值函数返回x&#xff08;数字&#xff09;的绝对值。实例以下展示了使用 abs() 方法的实例&#xff1a;#!/usr/bin/python print "abs(-45) …...

裸辞了,面试了几十家软件测试公司,终于找到想要的工作

上半年裁员&#xff0c;下半年裸辞&#xff0c;有不少人高呼裸辞后躺平真的好快乐&#xff01;但也有很多人&#xff0c;裸辞后的生活五味杂陈。 面试了几十家终于找到心仪工作 因为工作压力大、领导PUA等各种原因&#xff0c;今年2月下旬我从一家互联网小厂裸辞&#xff0c;没…...

ShardingSphere

1.简介 1.开源的分布式数据生态项目 ShardingSphere-JDBC&#xff1a;轻量级Java框架ShardingSphere-Proxy&#xff1a;数据库代理ShardingSphere-Sidecar(规划中)&#xff1a;Kubernetes的云原生数据库代理 2.使用版本&#xff1a;ShardingSphere5.1.1 1.数据库集群架构 1.出现…...

配置Maven

对于刚开始认识的Maven的初学者超级有用的哦&#xff01;项目统一共享使用一套jar包&#xff0c;由maven统一管理。节省了jar空间&#xff0c;统一jar包版本首先将maven安装完毕测试有没有配置完成&#xff0c;在命令框里面打 mvn -version进行测试maven安装完&#xff0c;第一…...

赛宁网安“网络安全卓越中心”:立足科技创新 推动网安产业高质量发展

​​2月22日上午&#xff0c;网络安全卓越中心CPCOE——圆桌论坛活动在南京召开。本次论坛由南京未来科技城主办&#xff0c;南京赛宁信息技术有限公司承办。论坛上&#xff0c;江苏省科协副主席、南京理工大学教授李千目&#xff0c;江苏省互联网协会副理事长兼秘书长刘湘生&a…...

操作系统题目收录(十四)

1、 有些操作系统中将文件描述信息从目录项中分离出来&#xff0c;这样做的好处是&#xff08;&#xff09;。 A&#xff1a;减少读文件时的I/O信息量B&#xff1a;减少写文件时的I/O信息量C&#xff1a;减少查找文件时的I/O信息量D&#xff1a;减少复制文件时的I/O信息量 解…...

vscode里如何用git

打开vs终端执行如下&#xff1a; 1 初始化 Git 仓库&#xff08;如果尚未初始化&#xff09; git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

visual studio 2022更改主题为深色

visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中&#xff0c;选择 环境 -> 常规 &#xff0c;将其中的颜色主题改成深色 点击确定&#xff0c;更改完成...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

大数据学习(132)-HIve数据分析

​​​​&#x1f34b;&#x1f34b;大数据学习&#x1f34b;&#x1f34b; &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4…...

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…...

Typeerror: cannot read properties of undefined (reading ‘XXX‘)

最近需要在离线机器上运行软件&#xff0c;所以得把软件用docker打包起来&#xff0c;大部分功能都没问题&#xff0c;出了一个奇怪的事情。同样的代码&#xff0c;在本机上用vscode可以运行起来&#xff0c;但是打包之后在docker里出现了问题。使用的是dialog组件&#xff0c;…...