【C++】开散列实现unordered_map与unordered_set的封装

> 作者:დ旧言~
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:手撕哈希表的闭散列和开散列

> 毒鸡汤:谁不是一边受伤,一边学会坚强。

> 专栏选自:C嘎嘎进阶

> 望小伙伴们点赞👍收藏✨加关注哟💕💕

🌟前言

我们已经模拟实现了哈希表,当然需要用哈希表来封装相关容器,最初的map和set封装是用红黑树,所以我们就不再封装了map和set,来封装它们的同胞兄弟unordered_map与unordered_set。对比前面map和set的封装还是比较简单的,咱们走起吧!!!

⭐主体

学习开散列实现unordered_map与unordered_set的封装咱们按照下面的图解:

🌙unordered_map讲解

概念:

unordered_map其实就是与map相对应的一个容器。学过map就知道,map的底层是一个红黑树,通过中序遍历的方式可以以有序的方式遍历整棵树。而unordered_map,正如它的名字一样,它的数据存储其实是无序的,这也和它底层所使用的哈希结构有关。而在其他功能上,unordered_map和map基本上就是一致的。

特点:

  • unordered_map是用于存储<key, value>键值对的关联式容器,它允许通过key快速的索引到对应的value。
  • 在内部,unorder_map没有对<key, value>按照任何特定的顺序排序,为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的<key, value>键值对放在相同的桶中。
  • unordered_map容器的搜索效率比map快,但它在遍历元素自己的范围迭代方面效率就比较低。
  • 它的迭代器只能向前迭代,不支持反向迭代。

拓展:

从大结构上看,unordered_map和map的模板其实没有太大差距。学习了map和set我们就应该知道,map是通过T来告诉红黑树要构造的树的存储数据类型的,unordered_map也是一样的,但是它的参数中多了Hash和Pred两个参数,这两个参数都传了仿函数,主要和哈希结构有关。

使用:

统计水果个数:

int main()
{string arr[] = { "西瓜","西瓜", "苹果", "梨", "苹果","香蕉", "苹果", "梨", "香蕉", "苹果", "西瓜" };unordered_map<string, int> count;for (auto& e : arr){count[e]++;}for (auto e : count){cout << e.first << ":" << e.second << endl;}return 0;
}

🌙unordered_set讲解

概念:

unordered_set其实也是一样的,从功能上来看和set并没有什么区别,只是由于地层数据结构的不同,导致unordered_set的数据是无序的,但是查找效率非常高。

特点:

  • 无序性:unordered_set中的元素没有特定的顺序,不会根据插入的顺序或者元素的值进行排序。
  • 唯一性:unordered_set中的元素是唯一的,不允许重复的元素。
  • 快速查找:unordered_set使用哈希表实现,可以在平均常数时间内进行查找操作,即使在大型数据集上也能保持高效。
  • 插入和删除效率高:unordered_set的插入和删除操作的平均时间复杂度为常数时间,即O(1)。
  • 高效的空间利用:unordered_set使用哈希表来存储元素,不会浪费额外的空间。
  • 不支持修改操作:由于unordered_set中的元素是唯一的,不支持直接修改元素的值,需要先删除旧值,再插入新值。
  • 迭代器失效:在进行插入和删除

拓展:

unordered_set相较于set,多了Hash和Pred两个参数。这两个参数都是传了仿函数,和unordered_map与map之间的关系都是一样的。

使用:

存储整数:

int main()
{unordered_set<int> v;v.insert(7);v.insert(2);v.insert(3);v.insert(5);v.insert(4);v.insert(8);v.insert(7);unordered_set<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";it++;}cout << endl;return 0;
}

🌙哈希桶的改造

可以我们原先的哈希桶的写法,这里我们只改造开散列:【C++】手撕哈希表的闭散列和开散列

 💫结构修改

步骤:

首先,这里实现的哈希桶需要能够同时支持unordered_map和unordered_set的调用。因此,在传入的数据中就需要有一个T,来标识传入的数据类型。同时,还需要有Hash函数和KeyOfT来分别对传入的数据转换为整形和获取传入数据的key值,主要是提供给使用了KV模型的数据。

其次,哈希桶其实是保存在一个顺序表中的,每个下标对应的位置上都是桶的头节点,每个桶中的数据以单链表的方式链接起来。因此,我们就需要一个vector来存储结构体指针,这个结构体中包含了当前节点存储的数据和下一个节点的位置。当然,还有有一个_n来记录顺序表中数据的个数。

代码:

//开散列
namespace hash_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 KeyOfT, class Hash>class HashTable{template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash >//友元 使 __HashIterator中可以调用HashTable的私有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;private:vector<Node*> _tables;  //指针数组size_t _n = 0;          //存储数据有效个数};
}

注意:

哈希桶hash_bucket中
需要将其内部的 HashNode 的参数进行修改
将原来的模板参数 K,V 改为 T
同样由于不知道传入数据的是K还是K V类型的 ,所以 使用 T 类型的data代替


之前实现的模板参数 K ,V分别代表 key 与value
修改后 , 第一个参数 拿到单独的K类型,是为了 使用 Find erase接口函数的参数为K
第二个参数 T 决定了 拿到的是K类型 还是 K V 类型


上面的代码中有一个迭代器的重命名和迭代器结构体的友元声明。重命名是为了方便后续的使用。友元声明则和迭代器的实现有关,这里先不过多讲解。

💫构造函数

代码如下:

// 析构函数
~HashTable()//析构
{for (auto& cur : _tables){while (cur){Node* next = cur->_next;delete cur;cur = next;}cur = nullptr;}
}

💫插入函数

步骤:

在插入时,首先要先查看哈希桶中是否存在相同键值,存在就直接返回当前位置。第二步就是要查看哈希桶中的元素个数与哈希桶的容量之间的负载因子,如果等于1,就需要进行扩容。第三步则是开始插入节点。先找到映射位置,然后新建一个节点连接到对应的数组下标的空间中即可。

注意:

insert()函数返回的是pair<iterator, bool>,这是为了后续实现 [ ] 重载做准备。

代码:

// 插入元素
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;// 创建 newsize个数据,每个数据都为空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), true);}

💫查找函数

步骤:

查找数据很简单。先通过hash函数计算出要查找的数据键值所对应的位置,如果对应的位置上存储的是空,说明不存在,直接返回。如果不为空,则比对键值,相同返回,不相同向下找直到为空。

代码:

// 查找函数
iterator Find(const K& key)
{Hash hash;KeyOfT kot;//若表刚开始为空if (_tables.size() == 0){return end();}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();
}

💫删除函数

步骤:

在删除时,首先要调用hash函数获取关键码,根据对应位置上找。如果该位置存的数据为空,则表示不存在,返回;如果不为空,就比较键值,相同为找到,删除,不同就继续向下找直到为空。

注意:

哈希桶与哈希表不同,要删除数据时不能使用find()函数搜索。因为哈希桶中的数据是用单链表链接起来的,用find()就无法知道节点的父节点,进而无法链接链表。

代码:

// 删除函数
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;
}

💫迭代器实现

步骤:

迭代器的结构体中首先要有哈希桶的指针。当然,迭代器的结构体中还需要有一个数据的指针,用于初始化迭代器,获取对应位置上的内容。

判断当前节点是否为空,不为空则返回;为空就说明当前下标对应的位置上已经没有数据了,就调用hash函数获取该节点的关键码。++关键码走向下一个下标,不为空则返回;为空则继续走,到找到不为空的位置或结束。

图解:

注意:

哈希桶的begin()要返回的是第一个不为空的桶,而不是第一个节点

代码:

//迭代器
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; //节点的指针HT* _ht;     //哈希表// 初始化列表__HashIterator(Node* node, HT* ht):_node(node), _ht(ht){}// 初始化列表const__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;}};

💫友元声明

解析:

在++迭代器的时候,会使用到哈希表指针,哈希表指针又会使用到HashTable中的_tables。HashTable中的_tables是私有成员,在类外是不能访问的。解决这个问题可以在HashTable中写一个公有的访问函数,也可以采用友元。

图解:

🌙unordered_map封装

代码如下:

#include"HashTable.h"namespace lyk
{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 hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;// 开始位置iterator begin(){return _ht.begin();}// 结束位置iterator end(){return _ht.end();}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret->first->second;}// 插入函数pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.insert(kv);}// 查找函数iterator find(const K& key){return _ht.Find();}// 删除函数bool erase(const K& key){return _ht.Erase(key);}private:hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;};// 测试一void test_map1(){unordered_map<int, int> v;v.insert(make_pair(1, 1));v.insert(make_pair(3, 3));v.insert(make_pair(10, 10));v.insert(make_pair(2, 2));v.insert(make_pair(8, 8));unordered_map<int, int>::iterator it = v.begin();while (it != v.end()){cout << it->first << ":" << it->second << " ";++it;}cout << endl;}// 测试二void test_map2(){unordered_map<string, string> dict;dict.insert(make_pair("sort", ""));dict.insert(make_pair("left", ""));dict.insert(make_pair("right", "ұ"));for (auto& kv : dict){kv.second += 'y';cout << kv.first << ":" << kv.second << endl;}}};

🌙unordered_set封装

代码如下:

#include"HashTable.h"namespace lyk
{template<class K, class Hash = 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 _ht.begin();}// 结束位置iterator end(){return _ht.end();}// 开始位置const修饰const_iterator begin() const{return _ht.begin();}// 结束位置const修饰const_iterator end() const{return _ht.end();}// 插入pair<iterator, bool> insert(const K& key){return _ht.insert(key);}// 查找iterator find(const K& key){return _ht.Find();}// 删除bool erase(const K& key){return _ht.Erase(key);}private:hash_bucket::HashTable<K, K, SetKeyOfT, Hash> _ht;};// 测试代码void test_set1(){unordered_set<int> us;us.insert(3);us.insert(1);us.insert(5);us.insert(15);us.insert(45);us.insert(7);unordered_set<int>::iterator it = us.begin();while (it != us.end()){cout << *it << " ";++it;}cout << endl;for (auto e : us){cout << e << " ";}cout << endl;}}

🌙哈希表完整代码

#include <iostream>
#include <string>
#include <vector>
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& key){size_t hash = 0;for (auto ch : key){hash *= 131;hash += ch;}return hash;}
};//开散列
namespace hash_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 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; //节点的指针HT* _ht;     //哈希表// 初始化列表__HashIterator(Node* node, HT* ht):_node(node), _ht(ht){}// 初始化列表const__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 >//友元 使 __HashIterator中可以调用HashTable的私有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(){Node* cur = nullptr;size_t i = 0;//找到第一个不为空的桶for (i = 0; i < _tables.size(); i++){cur = _tables[i];if (cur){break;}}//迭代器返回 第一个桶 和哈希表//this 作为哈希表本身return iterator(cur, this);}// 最后位置iterator end(){return iterator(nullptr, this);}// const修饰的开始位置const_iterator begin()const{Node* cur = nullptr;size_t i = 0;//找到第一个不为空的桶for (i = 0; i < _tables.size(); i++){cur = _tables[i];if (cur){break;}}//迭代器返回 第一个桶 和哈希表//this 作为哈希表本身return const_iterator(cur, this);}// const修饰的最后位置const_iterator end() const{return const_iterator(nullptr, this);}// 析构函数~HashTable()//析构{for (auto& cur : _tables){while (cur){Node* next = cur->_next;delete cur;cur = next;}cur = nullptr;}}// 查找函数iterator Find(const K& key){Hash hash;KeyOfT kot;//若表刚开始为空if (_tables.size() == 0){return end();}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){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;// 创建 newsize个数据,每个数据都为空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), true);}private:vector<Node*> _tables;  //指针数组size_t _n = 0;          //存储数据有效个数};}

🌟结束语

       今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

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

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

相关文章

c语言例题,实现一个整型有序数组的二分查找

c语言中&#xff0c;有很多可以实现效果的方法&#xff0c;而在一个整型有序的数组中&#xff0c;我们可以使用二分查找的方法来实现对于一个数组中的元素查找。二分查找的优点在于本身需要的计算是比较少的&#xff0c;一次计算查找排除掉数组中一半的元素&#xff0c;尤其对于…

Topaz Photo AI for Mac v2.4.2 智能AI降噪软件

Topaz Photo AI是一款适用于Mac的图像处理软件&#xff0c;使用人工智能技术对照片进行编辑和优化。该软件提供了多种强大的功能&#xff0c;包括降噪、锐化、消除噪点、提高分辨率等&#xff0c;可以帮助用户改善图像质量&#xff0c;并实现自定义的效果。 软件下载&#xff1…

探究贪心算法:特点与实际应用

探究贪心算法&#xff1a;特点与实际应用 探究贪心算法&#xff1a;特点与实际应用&#x1f4dd; 摘要&#x1f680; 引言&#x1f4cb; 正文内容&#xff08;详细介绍&#xff09;&#x1f4cc; 小结&#x1f4ca; 表格总结&#x1f3af; 总结&#x1f52e; 未来展望&#x1f…

C#.net8创建webapi,使用SqlSugar,仓储模式,DTO,服务层,控制层的综合应用(企业级)

本文源码地址: https://download.csdn.net/download/u012563853/89036104 源码中,也有详细的注释说明。 代码总览: 这是一个综合性比较强的文章,需要有一定的基础,没有基础的人,看了后,会全面的了解一下,有基础的人,看了后会加强认识,更加的巩固,直接在项目中去应…

详解 Java 中的 volatile 关键字

引言&#xff1a; 在 Java 编程中&#xff0c;volatile 是一个非常重要的关键字&#xff0c;用于修饰变量。它主要用于确保多个线程之间对共享变量的修改能够被及时地更新到主内存&#xff0c;并且能够保证可见性。然而&#xff0c;需要注意的是&#xff0c;volatile 关键字并不…

PS之更换背景色

1.更换背景色 导入图片后, 选中快速选择工具, 依次点击 选择主体 -> 选择并遮住 其后右边会出现一栏 “属性”, 拉到最下面的 “输出设置”, 选择净化颜色, 然后确认 此时应该得到抠出来的物体. 右下角点击新建图层 此时对图层2打开拾色器, 将图层2 设为白色 然后CTRL…

二维码门楼牌管理应用平台:创新管理与服务的全新方式

文章目录 前言一、二维码门楼牌管理应用平台的建设背景二、二维码门楼牌管理应用平台的功能特点三、切换功能在城市管理中的应用四、二维码门楼牌管理应用平台的未来展望 前言 在数字化时代的浪潮中&#xff0c;二维码技术以其便捷、高效的特点&#xff0c;正逐渐渗透到我们生…

ArrayList和LinkedList的区别【大白话Java面试题】

ArrayList和LinkedList的区别【大白话Java面试题】 大白话回答 arraylist和linkedlist的区别是&#xff1a;数据结构不同&#xff0c;效率不同&#xff0c;自由性不同&#xff0c;主要控件开销不同 数据结构不同 ArrayList是Array(动态数组)的数据结构&#xff0c;LinkedList…

leetcode 周赛 391场

2. 换水问题 给你两个整数 numBottles 和 numExchange 。 numBottles 代表你最初拥有的满水瓶数量。在一次操作中&#xff0c;你可以执行以下操作之一&#xff1a; 喝掉任意数量的满水瓶&#xff0c;使它们变成空水瓶。用 numExchange 个空水瓶交换一个满水瓶。然后&#xf…

JAM计数法C++

题目&#xff1a; 代码&#xff1a; #include <iostream> using namespace std; int main() {int s,t,w;cin>>s>>t>>w;char a[26];cin>>a; //输入给出的jam数字for(int i1;i<5;i) //循环找哪个字母递增{for(int jw-1;j>0;j--){if(a[j]1&…

【41-60】计算机网络基础知识(非常详细)从零基础入门到精通,看完这一篇就够了

【41-60】计算机网络基础知识&#xff08;非常详细&#xff09;从零基础入门到精通&#xff0c;看完这一篇就够了 以下是本文参考的资料 欢迎大家查收原版 本版本仅作个人笔记使用41、使用 Session 的过程是怎样的&#xff1f;42、Session和cookie应该如何去选择&#xff08;适…

知识蒸馏详解及pytorch官网demo案例

知识蒸馏Knowledge Distillation(KD) 1、简介 一种模型压缩方法 知识蒸馏的一般框架&#xff08;如下图&#xff09; 三部分&#xff1a;知识、蒸馏算法、师生架构。 知识 将知识分为三种形式&#xff1a;基于响应的&#xff08;response-based&#xff09;、基于特征的&…

数字乡村发展蓝图:科技赋能农村实现全面振兴

目录 一、数字乡村发展蓝图的内涵与目标 二、科技赋能农村&#xff1a;数字乡村发展的动力与路径 &#xff08;一&#xff09;加强农业科技创新&#xff0c;提升农业生产效率 &#xff08;二&#xff09;推进农村电商发展&#xff0c;拓宽农民增收渠道 &#xff08;三&…

MHA高可用配置与故障切换

前言&#xff1a; MHA高可用故障就是单点故障&#xff0c;那么我们如何解决单点故障MHA中Master如何将故障的机器停止&#xff0c;使用备用的Slave服务器 一 MHA定义 MHA&#xff08;MasterHigh Availablity&#xff09;是一套优秀的Mysql高可用环境下故障切换和主从复制的…

【Linux】进程程序替换 做一个简易的shell

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 文章目录 前言 进程程序替换 替换原理 先看代码和现象 替换函数 第一个execl()&#xff1a; 第二个execv()&#xff1a; 第三个execvp()&#xff1a; 第四个execvpe()&a…

编程语言|C语言——C语言操作符的详细解释

这篇文章主要详细介绍了C语言的操作符&#xff0c;文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值&#xff0c;需要的朋友们下面随着小编来一起学习学习吧 一、基础 1.1 算数操作符 - * / % - * / 这些操作符是我们…

QT初识(1)

QT初识 桌面开发什么是QT下载QT安装好之后的工具AssisantDesignerQT Creator 创建一个简单的项目 我们今天来认识一下QT。 桌面开发 在了解QT&#xff0c;我们得了解一下桌面开发&#xff1a; 桌面开发指的是编写和构建在个人计算机或其他桌面操作系统&#xff08;如Windows、…

关系网络c++

题目&#xff1a; 代码&#xff1a; #include<bits/stdc.h>using namespace std;int n,x,y;struct node{int num;//编号 int t;//步数 node(){}node(int sum,int tt){numsum;ttt;} }; int mp[101][101];//图 bool flag[101];//标记 queue<node> q; void bfs() {q…

【Docker】Windows中打包dockerfile镜像导入到Linux

【Docker】Windows中打包dockerfile镜像导入到Linux 大家好 我是寸铁&#x1f44a; 总结了一篇【Docker】Windows中打包dockerfile镜像导入到Linux✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 今天遇到一个新需求&#xff0c;如何将Windows中打包好的dockerfile镜像给迁移…

Autodesk Maya 2025---智能建模与动画创新,重塑创意工作流程

Autodesk Maya 2025是一款顶尖的三维动画软件&#xff0c;广泛应用于影视广告、角色动画、电影特技等领域。新版本在功能上进行了全面升级&#xff0c;新增了对Apple芯片的支持&#xff0c;建模、绑定和角色动画等方面的功能也更加出色。 在功能特色方面&#xff0c;Maya 2025…