[C++]STL---unordered_set与unordered_map的模拟实现

目录

前言

哈希桶的改造

哈希桶的初步改造

迭代器的模拟实现

operator++()

类互相typedef时的前置声明

友元声明

迭代器的出口

插入Insert()

查找Find()

哈希表的最终改造

unordered_set的模拟实现

unordered_map的模拟实现


前言

unordered_set与set的区别:

  1. set是基于红黑树实现的有序集合,而unordered_set是基于哈希表实现的无序集合
  2. set中的元素是按照特定的排序规则进行排序的,而unordered_set中的元素是无序的;
  3. unordered_set通过哈希表实现,查找元素的效率较高,平均时间复杂度为O(1),而set通过红黑树实现,查找元素的效率较低,平均时间复杂度为O(logN);
  4. unordered_set通常占用更多的内存,因为需要维护哈希表的结构,而set通常占用较少的内存;
  5. unordered_set的迭代器为单向迭代器,只能向前迭代,不支持反向迭代,而set为双向迭代器;

unordered_map与map的区别:

  1. map是基于红黑树实现的有序键值对容器,而unordered_map是基于哈希表实现的无序键值对容器;
  2. map中的键值对是按照键的特定排序规则进行排序的,而unordered_map中的键值对是无序的;
  3. unordered_map通过哈希表实现,查找键值对的效率较高,平均时间复杂度为O(1),而map通过红黑树实现,查找键值对的效率较低,平均时间复杂度为O(logN);
  4. unordered_map通常占用更多的内存,因为需要维护哈希表的结构,而map通常占用较少的内存;
  5. unordered_map的迭代器为单向迭代器,只能向前迭代,不支持反向迭代,而map为双向迭代器;

哈希桶的改造

unordered_map与unordered_set底层皆为哈希桶,因此底层采用一个哈希桶并且让它能够满足unordered_map和unordered_set的基本需求 ( 即采用一个哈希桶适配出unordered_set与unordered_map ),因此需要对哈希桶进行改造;

unordered_map为Key-Value模型,unordered_set为Key模型,因此首先修改链表节点中的数据类型,数据类型修改为T,如此T既可以接收键值对,也可以只接收键值;

template<class T>
struct HashNode
{HashNode<T>* _next;T _data;HashNode(const T& data):_data(data), _next(nullptr){}
};

哈希表中 插入Insert() 需要根据键值key判断待插入的数据是否存在,查找Find() 需要根据键值key判断待查找的数据是否存在,删除Erase() 需要根据键值Key确定待删除数据的位置,但是unordered_map数据类型为键值对pair<K,V>,需要将键值Key从键值对pair<K,V>取出,因此HashTable需要增加一个模板参数KeyOfT,这个模板参数接收分别由unordered_set、unordered_map传递的仿函数,此仿函数的作用为取出键值key(由于unordered_set与unorder_map类中传递数据类型是确定的,因此unordered_set与unorder_map类中实现仿函数取出各自的键值Key);

思考:HashTable的模板参数Hash是否应该存在缺省值?

当数据类型为自定义类型(日期类、string类、......)当使用者使用unordered_set与unordered_map时,若HashTable(unordered_map与unordered_set的底层结构)中没有提供将某种数据类型转换为整型仿函数,需要使用者手动实现将此类型的数据转换为整型的仿函数,使用者显然不可以动手去修改底层代码;因此HashTable种Hash不应该存在缺省值,应该由哈希表的上层unordered_map和unordered_set传递,如此使用者也可以对于自定义类型的数据自定义Hash仿函数实现类型转换为整型

unordered_map和unordered_set框架

//HashBucket.h文件
template<class T>
struct HashNode
{HashNode<T>* _next;T _data;HashNode(const T& data):_data(data), _next(nullptr){}
};template<class K, class T,class KeyOfT, class Hash>
class HashTable
{typedef HashNode<T> Node;
public://...
private:vector<Node*> _tables;size_t _n;//记录哈希表中实际存放的数据个数
};
//Unordered_Set.h文件
template<class K, class Hash = HashFunc<K>>
class Unordered_Set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public:private:HashTable<K, K, SetKeyOfT, Hash> _ht;
};
//Unordered_Map文件
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:private:HashTable<K,pair<K,V>,MapKeyOfT,Hash>
};

哈希桶的初步改造

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 *= 31;}return hash;}
};template<class T>struct HashNode{HashNode<T>* _next;T _data;HashNode(const T& data):_data(data), _next(nullptr){}};template<class K, class T,class KeyOfT, class Hash>
class HashTable
{typedef HashNode<T> Node;
public://构造函数HashTable(size_t n = 10){_tables.resize(n, nullptr);_n = 0;}//析构函数~HashTable(){for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur != nullptr){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}//拷贝构造函数  ht2(ht1)HashTable(const HashTable& ht){Hash hs;KeyOfT kot;//开辟相同大小的空间_tables.resize(ht._tables.size());//遍历旧表,头插到新表for (size_t i = 0; i < ht._tables.size(); i++){Node* cur = ht._tables[i];while (cur != nullptr){Node* next = cur->_next;//计算新表的插入位置size_t hashi = hs(kot(cur->_data)) % _tables.size();cur->_next = _tables[hashi];_tables[hashi] = cur;cur = next;}}_n = ht._n;}//赋值运算符重载 ht2=ht1HashTable& operator=(HashTable ht){_tables.swap(ht._tables);swap(_n, ht._n);return *this;}Node* Find(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur != nullptr){//仿函数对象kot获取结点中的键值if (kot((cur->_data)) == key){return cur;}cur = cur->_next;}return nullptr;}bool Insert(const T& data){//仿函数对象kot获取data中的键值keyKeyOfT kot;//仿函数对象hs将data中的键值key转换为整型Hash hs;Node* ret = Find(kot(data));if (ret != nullptr){return false;}if (_n == _tables.size()){vector<Node*> _newtables(_tables.size() * 2);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur != nullptr){Node* nextnode = cur->_next;//首先仿函数(KeyOfT)对象kot获取数据data的键值key//最后仿函数(Hash)对象hs将键值key的类型转换为整型size_t hashi = hs(kot(cur->_data)) % _newtables.size();cur->_next = _newtables[hashi];_newtables[hashi] = cur;cur = nextnode;}_tables[i] = nullptr;}_tables.swap(_newtables);}//首先仿函数(KeyOfT)对象kot获取数据data的键值key//最后仿函数(Hash)对象hs将键值key的类型转换为整型size_t hashi = hs(kot(data)) % _tables.size();Node* newnode = new Node(data);//单链表头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}bool Erase(const K& key){Hash hs;KeyOfT kot;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];Node* prev = nullptr;while (cur != nullptr){//仿函数(KeyOfT)对象kot获取数据data的键值key确定待删除数据的位置if (kot((cur->_data)) == key){//删除if (prev != nullptr){prev->_next = cur->_next;}else{_tables[hashi] = cur->_next;}delete cur;--_n;return true;}else{prev = cur;cur = cur->_next;}}return false;}
private:vector<Node*> _tables;size_t _n;
};

迭代器的模拟实现

所有容器必须具有迭代器,使用迭代器遍历容器中的数据;因此需要给哈希桶增加迭代器以供unordered_set与unordered_map使用;

思考:迭代器it的++如何实现?

++it操作:

  • 若it不是某个桶的最后一个元素,则it指向这个桶的下一个结点;
  • 若it为某个桶的最后一个元素,则it指向下一个桶的头节点;

若实现++it的操作,不仅需要结点指针记录当前结点,还需要一个哈希桶的指针,方便查找下一个桶;

template<class K, class T, class KeyOfT, class Hash>
struct __HTIterator
{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef __HTIterator<K, T, KeyOfT, Hash> Self;Node* _node;//记录当前结点HT* _ht;//哈希表指针用于查找下一个桶__HTIterator(Node* node, HT* ht):_node(node), _ht(ht){}
};

operator++()

Self& operator++()
{//当前桶未遍历结束,指向桶中下一个结点if (_node->_next != nullptr){_node = _node->_next;}//当前桶已遍历结束,寻找下一个桶else{//首先确定当前桶在哈希表中的位置Hash hs;KeyOfT kot;size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();//查找下一个桶++hashi;while (hashi < _ht->_tables.size()){//找到下一个桶,it指向桶的头节点if (_ht->_tables[hashi] != nullptr){_node = ht->_tables[hashi];break;//++结束}++hashi;}//跳出循环具有两种情形//case1:未找到哈希表中的下一个桶,采用空指针作为迭代器的end()//case2:找到哈希表中的下一个桶的头节点,返回迭代器if (hashi == _ht->_tables.size()){//case1_node = nullptr;}}//case2return *this;
}

类互相typedef时的前置声明

迭代器中一个成员变量为哈希表指针,而哈希表中需要给迭代器提供出口,哈希表与迭代器的定义必然存在先后顺序,本文先定义迭代器,后定义哈希表(若先定义哈希表,后定义迭代器也会面临同样的问题),此时迭代器中在重定义哈希表时必然找不到哈希表的定义,由于编译器只会向上查找而不会向下查找,所以必须在__HTIterator类前面先声明HashTable类,这种操作叫做前置声明;

友元声明

迭代器it ++时,使用哈希表指针访问_tables,而HashTable中的_tables为私有成员,类外不可以被访问,本文采用友元声明解决此问题,类模板的友元声明需要写模板参数,类名前面加friend关键字

迭代器的出口

将哈希表中第一个桶的头节点作为遍历的起始位置,将空指针作为迭代器遍历的终止位置;

iterator end()
{return iterator(nullptr, this);
}iterator begin()
{for (size_t i = 0; i < _tables.size(); i++){// 哈希表中第一个桶的第一个节点if (_tables[i]){return iterator(_tables[i], this);}}return end();
}

插入Insert()

Insert()函数返回类型修改为pair<iterator, bool>,为实现unordered_map中的operator[]重载做准备;

  • 键值对<iterator,bool>其中iterator代表新插入结点的迭代器,bool值表示插入是否成功;
  • 若新结点键值key原先存在,则返回哈希表中原本存在结点的迭代器,并且插入失败,返回false;
  • 若新结点键值key原先不存在,则插入结点,返回新插入结点在哈希表中的迭代器,返回true;
pair<iterator, bool> Insert(const T& data)
{KeyOfT kot;Hash hs;iterator ret = Find(kot(data));if (ret != end()){//键值key原先已存在,插入失败,返回已存在结点的迭代器并且返回falsereturn make_pair(ret, false);}if (_n == _tables.size()){vector<Node*> _newtables(_tables.size() * 2);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur != nullptr){Node* nextnode = cur->_next;size_t hashi = hs(kot(cur->_data)) % _newtables.size();cur->_next = _newtables[hashi];_newtables[hashi] = cur;cur = nextnode;}_tables[i] = nullptr;}_tables.swap(_newtables);}size_t hashi = hs(kot(data)) % _tables.size();Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;//键值key原先不存在,插入成功,返回新结点的迭代器并且返回truereturn make_pair(newnode, true);
}

查找Find()

//查找到返回哈希表指针与当前哈希表的结点指针
//若查找不到返回哈希表指针与空指针
iterator Find(const K& key)
{KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur != nullptr){//仿函数对象kot获取结点中的键值if (kot((cur->_data)) == key){return iterator(cur, this);}cur = cur->_next;}return iterator(nullptr, this);
}

哈希表的最终改造

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 *= 31;}return hash;}
};template<class T>
struct HashNode
{HashNode<T>* _next;T _data;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 KeyOfT, class Hash>
struct __HTIterator
{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef __HTIterator<K, T, KeyOfT, Hash> Self;Node* _node;//记录当前结点HT* _ht;//哈希表指针用于查找下一个桶__HTIterator(Node* node, HT* ht):_node(node), _ht(ht){}Self& operator++(){//当前桶未遍历结束,指向桶中下一个结点if (_node->_next != nullptr){_node = _node->_next;}//当前桶已遍历结束,寻找下一个桶else{//首先确定当前桶在哈希表中的位置Hash hs;KeyOfT kot;size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();//查找下一个桶++hashi;while (hashi < _ht->_tables.size()){//找到下一个桶,it指向桶的头节点if (_ht->_tables[hashi] != nullptr){_node = ht->_tables[hashi];break;//++结束}++hashi;}//跳出循环具有两种情形//case1:未找到哈希表中的下一个桶,采用空指针作为迭代器的end()//case2:找到哈希表中的下一个桶的头节点,返回迭代器if (hashi == _ht->_tables.size()){//case1_node = nullptr;}//case2return *this;}}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}bool operator!=(const Self& sl) const{return _node != sl._node;}bool operator==(const Self& sl) const{return _node == sl._node;}
};template<class K, class T,class KeyOfT, class Hash>
class HashTable
{template<class K, class T, class KeyOfT, class Hash>friend struct __HTIterator;typedef HashNode<T> Node;
public:typedef __HTIterator<K, T, KeyOfT, Hash> iterator;iterator end(){return iterator(nullptr, this);}iterator begin(){for (size_t i = 0; i < _tables.size(); i++){// 哈希表中第一个桶的第一个节点if (_tables[i]){return iterator(_tables[i], this);}}return end();}//构造函数HashTable(size_t n = 10){_tables.resize(n, nullptr);_n = 0;}//析构函数~HashTable(){for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur != nullptr){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}//拷贝构造函数  ht2(ht1)HashTable(const HashTable& ht){Hash hs;KeyOfT kot;//开辟相同大小的空间_tables.resize(ht._tables.size());//遍历旧表,头插到新表for (size_t i = 0; i < ht._tables.size(); i++){Node* cur = ht._tables[i];while (cur != nullptr){Node* next = cur->_next;//计算新表的插入位置size_t hashi = hs(kot(cur->_data)) % _tables.size();cur->_next = _tables[hashi];_tables[hashi] = cur;cur = next;}}_n = ht._n;}//赋值运算符重载 ht2=ht1HashTable& operator=(HashTable ht){_tables.swap(ht._tables);swap(_n, ht._n);return *this;}//查找到返回哈希表指针与当前哈希表的结点指针//若查找不到返回哈希表指针与空指针iterator Find(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur != nullptr){//仿函数对象kot获取结点中的键值if (kot((cur->_data)) == key){return iterator(cur, this);}cur = cur->_next;}return iterator(nullptr, this);}pair<iterator,bool> Insert(const T& data){KeyOfT kot;Hash hs;iterator ret = Find(kot(data));if (ret != end()){//键值key原先已存在,插入失败,返回已存在结点的迭代器并且返回falsereturn make_pair(ret, false);}if (_n == _tables.size()){vector<Node*> _newtables(_tables.size() * 2);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur != nullptr){Node* nextnode = cur->_next;size_t hashi = hs(kot(cur->_data)) % _newtables.size();cur->_next = _newtables[hashi];_newtables[hashi] = cur;cur = nextnode;}_tables[i] = nullptr;}_tables.swap(_newtables);}size_t hashi = hs(kot(data)) % _tables.size();Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;//键值key原先不存在,插入成功,返回新结点的迭代器并且返回truereturn make_pair(newnode, true);}bool Erase(const K& key){Hash hs;KeyOfT kot;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];Node* prev = nullptr;while (cur != nullptr){//仿函数(KeyOfT)对象kot获取数据data的键值key确定待删除数据的位置if (kot((cur->_data)) == key){//删除if (prev != nullptr){prev->_next = cur->_next;}else{_tables[hashi] = cur->_next;}delete cur;--_n;return true;}else{prev = cur;cur = cur->_next;}}return false;}
private:vector<Node*> _tables;size_t _n;
};

unordered_set的模拟实现

#include "HashBucket.h"
template<class K, class Hash = HashFunc<K>>
class Unordered_Set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public:typedef typename HashTable<K, K, SetKeyOfT, Hash>::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}//插入pair<iterator, bool> insert(const K& key){return _ht.Insert(key);}//查找iterator find(const K& key){return _ht.Find(key);}//删除bool erase(const K& key){return _ht.Erase(key);}
private:HashTable<K, K, SetKeyOfT, Hash> _ht;
};

unordered_map的模拟实现

#include "HashBucket.h"
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 HashTable<K, pair<K, V>, MapKeyOfT, Hash>::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);}//查找iterator find(const K& key){return _ht.Find(key);}//删除bool erase(const K& key){return _ht.Erase(key);}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}private:HashTable<K, pair<K, V>, MapKeyOfT, Hash> _ht;
};

欢迎大家批评指正,博主会持续输出优质内容,谢谢各位观众老爷观看,码字画图不易,希望大家给个一键三连支持~ 你的支持是我创作的不竭动力~

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

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

相关文章

运行游戏提示dll文件丢失,分享多种有效的解决方法

在我们日常频繁地利用电脑进行娱乐活动&#xff0c;特别是畅玩各类精彩纷呈的电子游戏时&#xff0c;常常会遭遇一个令人困扰的问题。当我们满怀期待地双击图标启动心仪的游戏程序&#xff0c;准备全身心投入虚拟世界时&#xff0c;屏幕上却赫然弹出一条醒目的错误提示信息&…

最受站长欢迎的wordpress模板

蓝色与黄色&#xff0c;作为经典的互补色&#xff0c;它们在企业网站设计中总能碰撞出令人印象深刻的火花。当这两种鲜艳的色彩巧妙结合时&#xff0c;不仅能够吸引访客的注意力&#xff0c;还能传达出一种活力四射、积极向上的企业形象。 今天&#xff0c;我们为您推荐的这款…

LAPGAN浅析

LAPGAN 引言 在原始 GAN和CGAN中&#xff0c;还只能生成 16*16, 28*28, 32*32 这种低像素小尺寸的图片。而LAPGAN首次实现 64*64 的图像生成。与其一下子生成这么大的图像 &#xff08;包含信息量这么多&#xff09;&#xff0c;不如一步步由小到大&#xff0c;这样每一步生成…

书籍推推荐之二--《生命的色彩》

史钧《生命的色彩》 在生活中&#xff0c;我们会注意到一个有趣的现象&#xff1a;每个人的头发颜色各不相同&#xff0c;有黑色、灰色、黄色、棕红色、银白色等&#xff0c;但就是没有绿色。对于生活在丛林中的早期人类来说&#xff0c;绿色的头发简直就是天然的迷彩服&#x…

随手记:树结构翻页和定位指定数据逻辑

业务背景&#xff1a; 树形组件展示数据&#xff0c;数据包含过去数据&#xff0c;现在数据&#xff0c;未来数据&#xff0c;用户在首次进入页面时&#xff0c;展示的是当天的数据&#xff0c;如果当天没有数据&#xff0c;则显示最近一条的过去数据。数据按照时间越长数据会…

可替代IBM DOORS的现代化需求管理解决方案Jama Connect,支持数据迁移及重构、实时可追溯性、简化合规流程

作为一家快速发展的全球性公司&#xff0c;dSPACE一直致力于寻找保持领先和优化开发流程的方法。为推进其全球现代化计划&#xff0c;dSPACE开始寻找可以取代传统需求管理平台&#xff08;IBM DOORS&#xff09;的需求管理解决方案。 通过本次案例&#xff0c;您将了解dSPACE为…

大数据第五天(操作hive的方式)

文章目录 操作hive的方式hive 存储位置hive 操作语法创建数据表的方式 操作hive的方式 hive 存储位置 hive 操作语法 创建数据表的方式 – 创建数据库 create database if not exists test我们创建数据库表的时候&#xff0c;hive是将我们的数据自动添加到数据表中&#xf…

江苏开放大学2024年春《机电设备安装与调试 050095》第三次形成性考核作业参考答案

电大搜题 多的用不完的题库&#xff0c;支持文字、图片搜题&#xff0c;包含国家开放大学、广东开放大学、超星等等多个平台题库&#xff0c;考试作业必备神器。 公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#…

一文解析golang中的协程与GMP模型

文章目录 前言1、线程实现模型1.1、用户级线程与内核级线程1.2、内核级线程模型1.3、用户级线程模型1.3、两级线程模型 2、GMP模型2.1、GMP模型概述2.1、GMP v1版本 - GM模型2.2、GMP v2版本 - GMP模型2.3、GMP相关源码2.4 调度流程2.5 设计思想 3.总结 前言 并发(并行&#x…

vue实现录音并转文字功能,包括PC端web,手机端web

vue实现录音并转文字功能&#xff0c;包括PC端&#xff0c;手机端和企业微信自建应用端 不止vue&#xff0c;不限技术栈&#xff0c;vue2、vue3、react、.net以及原生js均可实现。 原理 浏览器实现录音并转文字最快捷的方法是通过Web Speech API来实现&#xff0c;这是浏览器…

JTAG访问xilinx FPGA的IDCODE

之前调试过xilinx的XVC&#xff08;Xilinx virtual cable&#xff09;&#xff0c;突然看到有人搞wifi-JTAG&#xff08;感兴趣可以参考https://github.com/kholia/xvc-esp8266&#xff09;&#xff0c;也挺有趣的。就突然想了解一下JTAG是如何运作的&#xff0c;例如器件识别&…

淘宝/天猫按图搜索淘宝商品(拍立淘) API,按图搜索商品详情

淘宝/天猫的“按图搜索商品”功能&#xff0c;通常被称为“拍立淘”&#xff0c;允许用户通过上传图片来搜索相似的商品。这项服务背后是由淘宝提供的API支持&#xff0c;使得用户能够快速找到与上传图片相匹配或类似的商品。以下是关于“按图搜索淘宝商品”API的一些关键信息&…

Unity类银河恶魔城学习记录15-1,2 p153 Audio Manager p154 Audio distance limiter

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili AudioManager.cs using System.Collections; using System.Collections.Gen…

Ubuntu下载的nginx的位置

位置在/etc/nginx 启动nginx systemctl status nginx上面的命令不合适&#xff0c;就重启nginx sudo service nginx restart 关闭nginx nginx -s stop Ubuntu默认的html地址在该文件夹中的default中&#xff1a; /etc/nginx/sites-available if ($http_host ~* "^(w…

【计算机系统基础读书笔记】1.1.2 冯诺依曼机基本结构

1.1.2 冯诺依曼机基本结构 冯诺依曼机基本结构如图所示&#xff1a; 模型机中主要包括&#xff1a; 主存储器&#xff1a;用来存放指令和数据&#xff0c;简称主存或内存&#xff1b; 算数逻辑部件&#xff08;Arithmetic Logic Unit&#xff0c;简称ALU&#xff09;&#x…

实现SpringMVC底层机制(二)

文章目录 1. 动态获取spring配置文件1.修改SunWebApplicationContext.java2.修改SunDispatcherServlet.java 2.自定义Service注解1.需求分析2.编写Monster.java3.自定义Service注解4.编写Service接口MonsterService.java5.编写Service实现类MonsterServiceImpl.java6.修改SunWe…

华火电燃灶:市场认可度最高的品牌

华火电燃灶市场认可度高&#xff0c;这得益于其独特的技术优势、卓越的产品性能以及广泛的市场应用。作为一种新型的燃气灶具&#xff0c;华火电燃灶在市场上的表现备受瞩目&#xff0c;成为了众多消费者和业内人士关注的焦点。 首先&#xff0c;华火电燃灶的技术优势是其市场认…

pytorch-激活函数与GPU加速

目录 1. sigmod和tanh2. relu3. Leaky Relu4. selu5. softplus6. GPU加速7. 使用GPU加速手写数据训练 1. sigmod和tanh sigmod梯度区间是0&#xff5e;1&#xff0c;当梯度趋近0或者1时会出现梯度弥散的问题。 tanh区间时-1&#xff5e;1&#xff0c;是sigmod经过平移和缩放而…

【毕设绝技】基于 SpringCloud 的在线交易平台商城的设计与实现-数据库设计(三)

毕业设计是每个大学生的困扰&#xff0c;让毕设绝技带你走出低谷迎来希望&#xff01; 基于 SpringCloud 的在线交易平台商城的设计与实现 一、数据库设计原则 在系统中&#xff0c;数据库用来保存数据。数据库设计是整个系统的根基和起点&#xff0c;也是系统开发的重要环节…

Matlab|交直流混合配电网潮流计算(统一求解法)

目录 1 主要内容 算例模型 统一求解法迭代方程 算法流程图 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序为matlab代码&#xff0c;采用统一求解法对交直流混合配电网进行潮流计算&#xff0c;统一迭代法又称统一求解法&#xff0c;其思路是将混联系统中的交流网…