【C++】精妙的哈希算法

目录
- 一、哈希结构
- 1、哈希概念
- 2、哈希函数
- 3、哈希冲突
- 3.1 闭散列
- 3.2 开散列
- 4、完整代码
一、哈希结构
1、哈希概念
AVL树、红黑树等平衡树搜索效率取决于搜索过程中的比较次数,一般时间复杂度为O(logN),虽然平衡树的搜索效率已经很快,但如果可以不经过任何比较或者常数次的比较后就能搜索到我们要找的元素,会极大的提高效率。
哈希结构,是一种通过特定函数(哈希函数)将关键码映射到表中的一个位置,那么在查找时通过该函数就可以很快的找到该元素。
但是上述的映射方法存在一个问题,就是不同的元素可能会映射到同一个位置,这时就发生了哈希冲突(也叫哈希碰撞),解决哈希冲突,是实现哈希结构的关键。
2、哈希函数
引起哈希冲突的一个原因可能是:哈希函数设计不合理。
哈希函数的设计要保证高效性和可靠性:
- 一致性:确保相同的输入总是产生相同的输出哈希值
- 均匀分布:哈希值应在哈希表的地址空间中尽可能均匀分布,以减少哈希冲突
- 计算效率:哈希函数应简单且计算快速,以便在实际应用中能够快速执行
- 冲突最小化:设计哈希函数时应尽量减少哈希冲突的发生,以提高哈希表的性能
| 常见哈希函数:
哈希函数是哈希表的核心,它决定了如何将关键字映射到哈希地址。
- 直接定制法:取关键字的某个线性函数为散列地址,Hash(Key)=A*Key+B。这种方法简单、均匀,但需要事先知道关键字的分布情况
- 除留余数法:取一个不大于哈希表地址数m的质数p,按照哈希函数Hash(key)=key%p将关键码转换成哈希地址。这种方法实现简单,且当p选择合理时,哈希冲突的概率较低
- 平方取中法:对关键字进行平方运算,然后抽取中间的几位作为哈希地址。这种方法适用于不知道关键字分布情况,且位数不是很大的场景
- 折叠法:将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按哈希表表长取后几位作为哈希地址。这种方法适用于关键字位数较多的情况
此外,还有随机数法、数学分析法等哈希函数设计方法,可以根据具体应用场景选择合适的哈希函数。
哈希函数设计的越好,产生哈希冲突的可能性就越低,但是哈希冲突还是无可避免。
3、哈希冲突
解决哈希冲突的两种常见方法是:闭散列(开放定址法)和开散列(链地址法)。
3.1 闭散列
当发生哈希冲突时,如果哈希表中还有空位置,就把key
存放到冲突位置的“下一个”空位置去。找下一个空位置,常见的探测方法有线性探测、二次探测和双重散列等。
| 线性探测: 从发生冲突的位置开始,依次向后探测,直到找到下一个空位置为止。
- 插入
上图中在插入15前,通过哈希函数得到映射位置为5,但是5位置被占了,就依次向后找,在7位置找到了一个空位置将15插入。 - 删除
闭散列解决哈希冲突时,不好随便物理删除某个元素,可以考虑标记的方法来伪删除一个元素。
//每个位置都给标记
enum State
{EXIST,//存在DELETE,//删除EMPTY//空
}
| 线性探测实现:
enum State
{EXIST,EMPTY,DELETE
};template<class K, class V>
struct HashData
{pair<K, V> _kv;State _state = EMPTY;
};template<class K, class V>
class HashTable
{
public:HashTable(){_tables.resize(10);//提前开10个位置} private:vector<HashData<K, V>> _tables;size_t _n = 0;//存储元素个数
};
- 插入
关键码对表的size()
取模,不能对capacity()
取模,因为哈希表支持[]
访问,只能访问下标小于size()
的元素。
散列表的载荷因子 = 表中的元素个数 / 表的大小
当载荷因子达到某个临界值,就需要扩容。载荷因子越大,产生冲突的可能性就越大,相反产生冲突的可能性就越小。通常载荷因子应限制在0.7-0.8一下。
不能直接对原表进行扩容,无论是原地扩还是异地扩,都会把原数据拷贝过来。但是扩完容后元素的相对位置可能会发生改变,原本冲突的元素扩完容后就不冲突了,所以直接对原表进行扩容是不行的。
扩容有两种方法:
- 方法一:新建原表两倍大小的
vector
,遍历原表的元素重新映射到vector
中,再将新建的vector
和原表的vector
交换。 - 方法二:因为方法一还需要重写一遍映射过程,所以可以直接新建一个哈希表,遍历原表的元素插入到新建的哈希表中,最后交换两个哈希表的
vector
,这个方法的好处是新建的哈希表复用了原哈希表的Insert
。
方法一我们就不实现了,直接用更好一点的方法二:
bool Insert(const pair<K, V>& kv)
{if (_n * 10 / _tables.size() >= 7)//载荷因子达到一定的值进行扩容{HashTable<K, V> newHT;newHT._tables.resize(2 * _tables.size());for (int i = 0; i < _tables.size(); i++){if (_tables[i]._state == EXIST){newHT.Insert(_tables[i]._kv);}}_tables.swap(newHT._tables);}size_t hashi = kv.first % _tables.size();//确定映射位置while (_tables[hashi]._state == EXIST){++hashi;hashi %= _tables.size();//防止越界}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;
}
但是现在还有个问题,在上面的代码中要求我们的key
可以取模,也就是key只能是无符号整数,如果是浮点数、字符串等上面的代码就行不通,所以还需要想办法将可能出现的浮点数、字符串等类型的key
转换为无符号的整型再做映射。
像浮点数等可以直接强转为无符号整型,可以考虑用仿函数解决。字符串一般不能直接强转为无符号整型,我们可以对字符串特殊处理,也就是模版特化,将字符串中字符的ASCII码值加起来作为映射值。
但是这里还有个问题,将字符串中字符的ASCII码值加起来也可能冲突,比如相同的字符按不同的顺序组合起来的字符串。不过好在有专门的字符串哈希函数(字符串哈希函数有好多种,这里使用其中一种:BKDR Hash函数),这里就不做过多介绍了,有兴趣的同学请百度了解。他给出的解决办法是字符每次相加之前+31(31、131、1313、13131…都行)来尽可能减少冲突。
template<class K>
struct HashFunc //key强转为整型
{size_t operator()(const K& key){return (size_t)key;}
};//对string类型特殊处理
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash = hash * 31 + e;}return hash;}
};
- 删除
删除指定的元素,只需要找到该元素的位置,将该位置的状态标记为DELETE
即可。
bool Erase(const K& key)
{HashData<K, V>* ret = Find(key);if (ret == nullptr){return false;}else{ret->_state = DELETE;return true;}
}
- 查找
查找过程需要注意的是,当在表中找到被查找的元素时还要判断此位置是否被标记为已删除,因为删除操作我们并没有实际物理上的删除某个元素。
HashData<K, V>* Find(const K& key)
{Hash hs;size_t hashi = hs(key) % _tables.size();while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state != DELETE && _tables[hashi]._kv.first == key){return &_tables[hashi];}++hashi;hashi %= _tables.size();}return nullptr;
}
线性探测的优点是简单好理解,缺点是数据容易堆积,查找时可能需要多次比较。
闭散列 / 开放定址法我们就先实现到这里,它是一种零和博弈,和下面将要介绍的开散列 / 链地址法对比还是稍逊一筹。
3.2 开散列
通过哈希函数计算散列地址,具有相同映射地址的元素归于同一子集合,每一个子集合称为一个哈希桶,各个桶中的元素通过一个单链表链接起来,哈希表中存各链表的头节点。开散列每个桶中存放的都是产生哈希冲突的元素。
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 = hash * 31 + e;}return hash;}
};template<class K, class V>
struct HashNode
{HashNode(const pair<K, V>& kv):_kv(kv),_next(nullptr){}pair<K, V> _kv;HashNode<K, V>* _next;
};template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{typedef HashNode<K, V> Node;
public:HashTable(){_tables.resize(10, nullptr);}~HashTable(){for (int i = 0; i < _tables.size(); i++){Node* pcur = _tables[i];while (pcur){Node* next = pcur->_next;delete pcur;pcur = next;}_tables[i] = nullptr;}}private:vector<Node*> _tables;size_t _n = 0;
};
- 插入
bool Insert(const pair<K, V>& kv)
{size_t hashi = kv.first % _tables.size();//负载因子==1就扩容if (_n == _tables.size()){HashTable<K, V> newHT;newHT._tables.resize(2 * _tables.size(), nullptr);for (int i = 0; i < _tables.size(); i++){Node* pcur = _tables[i];while (pcur){newHT.Insert(pcur->_kv);pcur = pcur->_next;}}_tables.swap(newHT._tables);}Node* newnode = new Node(kv);//头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;
}
上面的扩容过程虽然可行,但是不够好。假如原表中有很多个节点,新建新表扩容后复用Insert
就要new
很多个节点再插入,这实际上是很有消耗的。因为原节点和新new
的节点并无差别,所以可以直接将原表中的节点拿下来头插到新表中,这样就不用再new
新节点。
| 优化:
bool Insert(const pair<K, V>& kv)
{size_t hashi = hs(kv.first) % _tables.size();//负载因子==1就扩容if (_n == _tables.size()){vector<Node*> newtables(2 * _tables.size(), nullptr);for (int i = 0; i < _tables.size(); i++){Node* pcur = _tables[i];while (pcur){Node* next = pcur->_next;size_t hashi = pcur->_kv.first % newtables.size();pcur->_next = newtables[hashi];newtables[hashi] = pcur;pcur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}Node* newnode = new Node(kv);//头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;
}
- 删除
学习过链表我们知道,单链表的头删和其他位置的删除需要分开处理,因为其他位置删除节点后要将前后节点链接起来,而单链表的头节点没有前一个节点。
bool Erase(const K& key)
{Hash hs;size_t hashi = hs(key) % _tables.size();Node* pcur = _tables[hashi];Node* prev = nullptr;while (pcur){if (pcur->_kv.first == key){if (prev == nullptr){_tables[hashi] = pcur->_next;}else{prev->_next = pcur->_next;}delete pcur;--_n;return true;}prev = pcur;pcur = pcur->_next;}return false;
}
4、完整代码
namespace open_address
{enum State{EXIST,EMPTY,DELETE};template<class K, class V>struct HashData{pair<K, V> _kv;State _state = EMPTY;};template<class K>struct HashFunc //key强转为整型{size_t operator()(const K& key){return (size_t)key;}};//对string类型特殊处理template<>struct HashFunc<string>{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash = hash * 31 + e;}return hash;}};template<class K, class V, class Hash = HashFunc<K>>class HashTable{public:HashTable(){_tables.resize(10);//提前开10个位置}bool Insert(const pair<K, V>& kv){//去冗余if (Find(kv.first)){return false;}if (_n * 10 / _tables.size() >= 7)//载荷因子达到一定的值进行扩容{HashTable<K, V, Hash> newHT;newHT._tables.resize(2 * _tables.size());for (int i = 0; i < _tables.size(); i++){if (_tables[i]._state == EXIST){newHT.Insert(_tables[i]._kv);}}_tables.swap(newHT._tables);}Hash hs;size_t hashi = hs(kv.first) % _tables.size();//确定映射位置while (_tables[hashi]._state == EXIST){++hashi;hashi %= _tables.size();//防止越界}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;}HashData<K, V>* Find(const K& key){Hash hs;size_t hashi = hs(key) % _tables.size();while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state != DELETE && _tables[hashi]._kv.first == key){return &_tables[hashi];}++hashi;hashi %= _tables.size();}return nullptr;}bool Erase(const K& key){HashData<K, V>* ret = Find(key);if (ret == nullptr){return false;}else{ret->_state = DELETE;return true;}}private:vector<HashData<K, V>> _tables;size_t _n = 0;//存储元素个数};
}namespace close_address
{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 = hash * 31 + e;}return hash;}};template<class K, class V>struct HashNode{HashNode(const pair<K, V>& kv):_kv(kv), _next(nullptr){}pair<K, V> _kv;HashNode<K, V>* _next;};template<class K, class V, class Hash = HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public:HashTable(){_tables.resize(10, nullptr);}~HashTable(){for (int i = 0; i < _tables.size(); i++){Node* pcur = _tables[i];while (pcur){Node* next = pcur->_next;delete pcur;pcur = next;}_tables[i] = nullptr;}}bool Insert(const pair<K, V>& kv){Hash hs;size_t hashi = hs(kv.first) % _tables.size();负载因子==1就扩容//if (_n == _tables.size())//{// HashTable<K, V> newHT;// newHT._tables.resize(2 * _tables.size(), nullptr);// for (int i = 0; i < _tables.size(); i++)// {// Node* pcur = _tables[i];// while (pcur)// {// newHT.Insert(pcur->_kv);// pcur = pcur->_next;// }// }// _tables.swap(newHT._tables);//}//负载因子==1就扩容if (_n == _tables.size()){vector<Node*> newtables(2 * _tables.size(), nullptr);for (int i = 0; i < _tables.size(); i++){Node* pcur = _tables[i];while (pcur){Node* next = pcur->_next;//记录下一个节点size_t hashi = hs(pcur->_kv.first) % newtables.size();//映射新表的相对位置pcur->_next = newtables[hashi];//头插newtables[hashi] = pcur;pcur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}Node* newnode = new Node(kv);//头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}Node* Find(const K& key){Hash hs;size_t hashi = hs(key) % _tables.size();Node* pcur = _tables[hashi];while (pcur){if (key == pcur->_kv.first){return pcur;}pcur = pcur->_next;}return nullptr;}bool Erase(const K& key){Hash hs;size_t hashi = hs(key) % _tables.size();Node* pcur = _tables[hashi];Node* prev = nullptr;while (pcur){if (pcur->_kv.first == key){if (prev == nullptr){_tables[hashi] = pcur->_next;}else{prev->_next = pcur->_next;}delete pcur;--_n;return true;}prev = pcur;pcur = pcur->_next;}return false;}private:vector<Node*> _tables;size_t _n = 0;};
}
本篇文章的分享就到这里了,如果您觉得在本文有所收获,还请留下您的三连支持哦~

相关文章:

【C++】精妙的哈希算法
🚀个人主页:小羊 🚀所属专栏:C 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 一、哈希结构1、哈希概念2、哈希函数3、哈希冲突3.1 闭散列3.2 开散列 4、完整代码 一、哈希结构 1、哈希概念 A…...

智慧链动青春:国家区块链中心接待北京市十一学校青少年访学探索
以生动科学的方法点燃青少年科学探索欲望是构建未来科技人才梯队的基石。近期国家区块链技术创新中心接待北京市十一学校新生访学,以科普讲座、实操互动的方式让学生在深度思考中感受科学魅力、接触前沿科技,激发学生对区块链、隐私计算和芯片设计制造的…...

利用C++封装鼠标轨迹算法为DLL:游戏行为检测的利器
在现代软件开发中,鼠标轨迹模拟技术因其在自动化测试、游戏脚本编写等领域的广泛应用而备受青睐。本文将介绍如何使用C语言将鼠标轨迹算法封装为DLL(动态链接库),以便在多种编程环境中实现高效调用,同时探讨其在游戏行…...

Qt- QSS风格选择器常用属性选择器样式表盒子
1. 风格设置 Qt 提供了 3 种整体风格,使用 QStyleFactory::keys() 来获取 (windowsvista 、Windows 、Fusion) 可以在 main.cpp 中调用 setStyle 方法对应用程序进行全局风格的设置 int main(int argc, char *argv[]) {QApplication a(arg…...

粤智助自助一体机大厂浮出水面 OBOO鸥柏已成服务终端中坚力量
自助服务查询一体机作为操作自主化便民的重要窗口,OBOO鸥柏自助服务终端机以其显著的技术优化,通过触摸屏或其他交互界面,使用户能够自助服务完成各种操作,如支付、查询信息终端、办理业务,自助查档答应一体化等。为交…...

SpringBoot-application.properties配置
默认配置最终都是映射/关联到某个类 #SPRING CONFIG(ConfigFileApplicationListener) spring.config.name #配置文件名(默认 为 application ) spring.config.location #配置文件的位置 …...

STM32-ADC模数转换
一、概述 ADC(Analog-Digital Converter)模拟-数字转换器 ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁12位逐次逼近型ADC,1us转换时间输入电压范围:0~3.3Vÿ…...

lspci | grep VGA
执行lspci | grep VGA后如下,解释含义 00:0f.0 VGA compatible controller: VMware SVGA II Adapter 0b:00.0 VGA compatible controller: NVIDIA Corporation GA104 [GeForce RTX 3070] (rev a1) 执行 lspci | grep VGA 命令后,您得到了两条输出&#…...

智慧厂区车辆导航解决方案;智慧工厂电子地图应用解决方案;大型工厂内部导航解决方案;智慧工厂可视化地图应用方案
智慧厂区车辆导航解决方案;智慧工厂电子地图应用解决方案 在智慧工业的蓬勃发展背景下,上海懒图科技凭借其室内电子地图技术的深厚积淀,正为智慧工厂物流管理领域注入革新力量。其创新的车辆导航与可视化管理系统,凭借高精度定位…...

决策树C4.5算法详解及实现
C4.5决策树是一种广泛使用的机器学习算法,它用于分类任务。它是在ID3算法的基础上改进的,主要通过生成决策树来构建分类模型。C4.5通过以下步骤工作: 1. 数据集分裂 C4.5通过选择具有最高信息增益率的特征来分裂数据集。信息增益率…...

prompt learning
prompt learning 对于CLIP(如上图所示)而言,对其prompt构造的更改就是在zero shot应用到下游任务的时候对其输入的label text进行一定的更改,比如将“A photo of a{obj}”改为“[V1][V2]…[Vn][Class]”这样可学习的V1-Vn的token…...

适用于 Windows 11 的 5 大数据恢复软件 [免费和付费]
为什么我们需要Windows 11数据恢复软件? 计算机用户经常遇到的一件事就是数据丢失,这种情况随时可能发生。错误地删除重要文件和文件夹可能会非常令人担忧,但幸运的是,有一种方法可以恢复 PC 上丢失的数据。本文将向您展示可用于…...

vue实现获取当前时间并实时显示
以下代码可以实现获取当前时间并实时显示,朋友们直接copy使用即可,希望可以帮助到有需要的朋友们! <template><div class"time">{{ datetimeStr }}</div> </template> <script>export default {data…...
【论文阅读】SRCNN
学习资料 论文题目:Learning a Deep Convolutional Network for Image Super-Resolution(学习深度卷积网络用于图像超分辨率)论文地址:link.springer.com/content/pdf/10.1007/978-3-319-10593-2_13.pdf代码:作者提出的…...

数据结构与算法——Java实现 32.堆
目录 堆 大顶堆 威廉姆斯建堆算法 Floyd建堆算法 Floyd建堆算法复杂度 大顶堆代码实现 人的想法和感受是会随着时间的认知改变而改变, 原来你笃定不会变的事,也会在最后一刻变得释然 —— 24.10.10 堆 堆是基于二叉树实现的数据结构 大顶堆任意一个父节…...

深度学习 .dot()
在 MXNet 中,.dot() 是用于计算两个数组的点积(矩阵乘法)的方法。这个方法适用于一维和二维数组,并返回它们的点积结果。 语法 ndarray1.dot(ndarray2) 参数 ndarray1: 第一个输入数组。ndarray2: 第二个输入数组,…...

idea2024 git merge 时丢失 Merge remote-tracking branch问题
idea2024 git merge 时丢失 Merge remote-tracking branch问题 处理建议 直接修改本地git的配置 git config --global merge.ff false 分析 在 IntelliJ IDEA 中进行 Git merge 操作时,有时你可能会遇到提交历史中丢失 Merge remote-tracking branch 的信息&#…...

pdf怎么删除多余不想要的页面?删除pdf多余页面的多个方法
pdf怎么删除多余不想要的页面?在日常办公或学习中,我们经常会遇到需要处理PDF文件的情况。PDF文件因其格式稳定、不易被篡改的特点而广受青睐,但在编辑方面却相对不如Word等文档灵活。有时,在接收或创建的PDF文件中,可…...

树莓派应用--AI项目实战篇来啦-3.OpenCV 读取写入和显示图像
1. 介绍 在计算机视觉和图像处理领域,读取和显示图像是最基础且常见的操作之一,OpenCV作为一个强大的计算机视觉库,提供了丰富的功能来处理图像数据。 读取、显示和写入图像是图像处理和计算机视觉的基础,即使裁剪、调整大…...

一句话就把HTTPS工作原理讲明白了
号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部 上午好,我的网工朋友。 在当今互联网高度发达的时代,信息安全已成为不容忽视的重要议题。 随着越来越多的个人信息和敏感…...

CPU 和处理核心(Core)中间有3个缓存
一、CPU 和处理核心(Core)的关系 CPU和处理核心之间的关系是整体与部分的关系。随着多核技术的发展,现代CPU通过包含多个处理核心来提高其并行处理能力和整体性能,同时在核心之间实现资源的有效共享和独立使用。这种架构的进步使…...

前后分离项目记录
一.前端设置 1.打包问题 打包报错 Thread Loader时,增加以下代码: 2.上线时api设置 二.Nginx问题 1.缓存问题:添加如下代码以禁止缓存,否则在关闭nginx后仍然可以访问页面 2.跨域问题在后端加CrossOrigin注解即可 3.上线时co…...

一句话木马的多种变形方式
今天来和大家聊一聊,一句话木马的多种变形方式。 经常有客户的网站碰到被上传小马和大马,这里的“马”是木马的意思,可不是真实的马。 通常,攻击者利用文件上传漏洞,上传一个可执行并且能被解析的脚本文件,…...

【NestJS入门到精通】装饰器
目录 方法装饰器通过prototype添加属性、方法 属性装饰器拓展 方法装饰器参数装饰器 方法装饰器 ClassDecorator 定义了一个类装饰器 a,并将其应用于类 A。装饰器 a 会在类 A 被定义时执行。 const a:ClassDecorator (target:any)>{console.log(target,targe…...

XML 编辑最简单好用的 QXmlEdit 软件已经完整中文化
XML 编辑最简单好用的 QXmlEdit 软件已经完整中文化 简介一、软件界面二、安装下载2.1 QXmlEdit 官方网站下载2.2 QXmlEdit 源代码下载2.3 QXmlEdit 软件中文版下载 三、QXmlEdit 编辑 ADCP 测量项目的 MMT 文件3.1 应用 XML 文件缩进3.2 使用 QXmlEdit 缩进编辑保存后…...

ref标签、style的scope
ref标签 作用:用于注册模板引用。 用在普通DOM标签上,获取的是DOM节点。用在组件标签上,获取的是组件实例对象。 DOM: <template><div class"person"><h2 ref"title2">上海</h2>&l…...

22年408数据结构
第一题: 解析: 观察一下这个程序:我们注意到最外层的循环是从i1开始的,每次ii*2,直到i<n为止,假设程序总共执行k次执行,则有2^(k1)>n。则k1>log(2)n这里是以2为底n的对数, k>log(2)…...

ubuntu 虚拟机将linux文件夹映射为windows网络位置
在使用虚拟机时可以选择将windows的文件夹设置为共享文件夹方便在虚拟机中访问windows中的文件,同理,也可以将linux的文件夹共享为一个网络文件夹,通过windows的添加一个网络位置功能,将linux的文件夹映射到windows本地,方便windows访问使用linux的文件夹 参照如下:https://blo…...

Pytho逻辑回归算法:面向对象的实现与案例详解
这里写目录标题 Python逻辑回归算法:面向对象的实现与案例详解引言一、逻辑回归算法简介1.1 损失函数1.2 梯度下降 二、面向对象的逻辑回归实现2.1 类的设计2.2 Python代码实现2.3 代码详解 三、逻辑回归案例分析3.1 案例一:简单二分类问题问题描述数据代…...

AWS WAF实战指南:从入门到精通
1. 引言 Amazon Web Services (AWS) Web Application Firewall (WAF) 是一款强大的网络安全工具,用于保护Web应用程序免受常见的Web漏洞攻击。本文将带您从入门到精通,深入探讨AWS WAF的实际应用策略,并提供具体案例,帮助您更好地保护您的Web应用程序。 2. AWS WAF基础 …...