【C++篇】哈希表

目录

一,哈希概念

1.1,直接定址法

1.2,哈希冲突 

1.3,负载因子

二,哈希函数

2.1,除法散列法 /除留余数法

2.2,乘法散列法

2.3,全域散列法

三,处理哈希冲突

3.1,开放定址法

线性探测

二次探测

双重探测

3.2,开放定址法代码实现

哈希表扩容问题 

key不能取模的问题 

 完整代码实现

3.3,链地址法

哈希表扩容问题 

链地址法代码实现

小结:


一,哈希概念

哈希(hash)又称散列,是一种组织数据的方式。从译名看,有散乱排列的意思。本质就是通过哈希函数把关键字key和存储位置建立一个映射关系,查找时通过这个哈希函数计算出key存储的位置,实现快速查找。

说白了,hash函数就是根据key计算出应该存储地址的位置,而哈希表是基于hash函数建立的一种查找表。

1.1,直接定址法

当关键字的范围比较集中时,直接定址法就是非常简单高效的方法。比如一组关键字的值在[0,99]之间,那么我们开一个100大小的数组,每个关键字的值直接就是存储位置的下标。再比如一组数据a~z的字符,我们可以开一个大小为26的数组,每个关键字的acsll值减去 a的ascll值,就是对应的存储位置下标。也就是说直接定址法是用关键字计算出一个绝对位置或相对位置。

本题链接:387. 字符串中的第一个唯一字符 - 力扣(LeetCode)

class Solution {

public:

    int firstUniqChar(string s) {

        int hash[26]={0};

        //统计次数

        for(auto& ch:s)

        {

            hash[ch-'a']++;

        }

        for(int i=0;i<s.size();i++)

        {

            if(hash[s[i]-'a']==1)

            return i;

        }

        return -1;

    }

};

1.2,哈希冲突 

不同的key值产生相同的地址,即H(key1)=H(key2)。这种问题叫做哈希冲突或哈希碰撞。

1.3,负载因子

假设哈希表已经存储N个值,哈希表的大小为M,那么负载因子=N/M。负载因子越大,代表哈希冲突的概率越高,空间利用率越高;负载因子越小,代表哈希冲突的概率越低,空间利用率越低。

二,哈希函数

两个不同的key值可能 会映射到同一个位置,这个问题叫做哈希冲突。理想情况是找一个哈希函数避免冲突,但是实际场景中,冲突不可避免,所以我们尽可能设计出好的哈希函数,来减少哈希冲突。

哈希函数的设计可能有很多讲究,比如要考虑均匀性、确定性、高效性等等。不同的应用场景可能需要不同的哈希函数。比如,非加密的哈希函数可能更注重速度,而加密的哈希函数则需要更高的安全性,防止被逆向或者找到碰撞。

2.1,除法散列法 /除留余数法

  • 除法散列法也叫除留余数法,假设哈希表的大小为M,那么通过key除以M的余数作为映射的下标。也就是哈希函数为H(key)=key%M。
  • 当使用 除法散列法时,应避免M的大小为某些值,比如2的幂,10的幂等。如果是2的x次方,那么key%2^x相当于保留key的二进制前x位。那么前x位二进制相同的key值,计算出的哈希值都是一样的,就会加剧哈希冲突。原因:key%2^x,相当于key&(2^x-1),其中 2^x-1的二进制表示中前x均为1,其余为均为0,所以最后按位与的结果取决于key的前x位。【见下图】

  • 当使用除法散列 法时,建议M取不太接近2的整数次幂的一个质数(素数)

2.2,乘法散列法

  • 乘法散列法对哈希表的大小M没有要求,它的大思路第一步:用关键字key乘上A(0<A<1),并抽取出key*A的小数部分。第二步:再用M乘以key*A的小数部分,再向下取整。
  • H(key)=floor(M*((A*key)%1.0)),其中floor表示对表达式进行向下取整。0<A<1,这里最重要的时A的值该如何设定。Knuth认为设为黄金分割点比较好

2.3,全域散列法

  • 如果存在一个对手,他针对我们的哈希函数,特意构造出一个发生严重冲突的数据集。比如,让所有关键字落入同一个位置,这种情况是可以存在的。只要哈希函数公开且时确定的,就可以实现次攻击。解决此方法就是给哈希函数增加随机性,攻击者就无法找出确定的导致冲突 加剧的数据。这种方法叫做全域散列。
  • H[a][b](key)=((a*key+b)%P)%M。P选择一个足够大的质数,a可以任意选[1,P-1]之间的一个整数,b可以选[0,P-1]之间的任意整数,这些函数就构成了一个P*(P-1)组全域散列函数组。假设P=17,M=6,a=3,b=4,则H[3][4](8)=((3*8+4)&17)%6=5。
  • 需要注意的是,每次初始化哈希表时,随机选取全域散列函数组中的一个散列(哈希)函数使用,后序增删查改都固定使用这个散列函数。否则每次哈希都是随机选一个散列函数,那么插入是一个散列函数,查找又是另一个散列函数,就会导致查找不到插入的key值。

总结:实践中,哈希表一般是选择除法散列法作为哈希函数。当然哈希表无论选择什么哈希函数,都无法避免哈希冲突,那么插入数据时,如何解决哈希冲突呢?主要有两种方法,开放定址法和链地址法。

三,处理哈希冲突

3.1,开放定址法

开放定址法中,所有元素都放到哈希表里。当一个关键字key用哈希函数计算出的位置冲突了,则按照某种规则找到一个没有存储数据的位置存储。这里的规则有三种:线性探测,二次探测,双重探测。

线性探测
  • 从发生冲突的位置开始,依次向后进行 线性探测,直到寻找到一个没有存储数据的位置为止,如果走到哈希表尾,则回绕到哈希表头 的位置。
  • H(key)=key%M=hash0,其中hash0代表映射的位置,如果该位置没有数据,则将key填入 。如果hash0位置已经存在数据,也就是hash0位置冲突了,则线性探测公式为:Hc(key)=(hash0+i)%M=hashi,i=0,1,2,3...,M-1。其中hashi就是经过线性探测找到没有存储数据的位置,再将key填入。

下面演示{19,30,5,36,13,20,21,12} 等映射到M=11的表中.

  • 线性探测问题,假设hash0位置连续冲突,hash0,hash1,hash2已经存储数据了,后序映射到hash0,hash1,hash2的值都会 争夺hash3位置,这种现象叫做群集/堆积。下面的二次探测可以解决这个问题。
二次探测
  • 从发生冲突的位置,依次左右按二次方跳跃式探测,直到寻找到下一个没有存储数据的位置为止。如果 往右走到哈希表尾,则回绕到哈希表头的位置;如果往左走到哈希表头,则会绕道哈希表尾的位置。
  • H(key)=key%M=hash0,hash0位置冲突了,则二次探测公式为,Hc(key)=(hash0+/-i*i)%M,i=1,2,3..,M/=hashi.
  • 二次探测结果hashi可能为负数,当hashi<0时,hashi+=M。

下面演示{19,30,52,63,11,22}映射到M=11的表中

双重探测
  • 第⼀个哈希函数计算出的值发生冲突,使用第二个哈希函数计算出⼀个跟key相关的偏移量值,不断往后探测,直到寻找到下一个没有存储数据的位置为止。
  • H(key)=key%M=hash0,hash0位置冲突了,则双重探测公式为:Hc(key)=(hash0+i*H2(key))%M=hashi,i=1,2,3,...,M

3.2,开放定址法代码实现

开放定址法在时间中不如下面的连地址法,所以我们选择简单的线性探测实现即可。

结构:


//当前位置的状态
//存在  空  已删除
enum State
{EXIT,EMPTY,DELETE
};template <class k,class v>
struct HashData
{pair<k, v> _kv;State _state = EMPTY;
};template <class k,class v>
class HashTable
{
private:vector<Hashdata<k, v>> _tables; //哈希表size_t n = 0;                   //表中存储数据的个数
};

要注意的是这⾥需要给每个存储值的位置加⼀个状态标识,否则删除⼀些值以后,会影响后⾯冲突的值的查找。如下图,我们删除30,会导致查找20失败,当我们给每个位置加⼀个状态标识{EXIST,EMPTY,DELETE},删除30就可以不用删除值,而是把状态改为DELETE,那么查找20时遇到EMPTY才停,就可以找到20。

哈希表扩容问题 

这里我们哈希表负载因子控制在0.7,当负载因子到0.7以后我们就需要扩容了,我们如果还是按照2倍扩 容,但是同时我们要保持哈希表大小是一个质数,第一个是质数,2倍后就不是质数了。所以我们可以按照sgi版本的哈希表使用的方法。,给了⼀个近似2倍的质数表,每次去质数表获取扩容后的大小。

inline unsigned long __stl_next_prime(unsigned long n)
{
    static 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
    };
    const unsigned long* first = __stl_prime_list;
    const unsigned long* last = __stl_prime_list + __stl_num_primes;
    const unsigned long* pos = lower_bound(first, last, n);
    return pos == last ? *(last - 1) : *pos;
}

在需要扩容时,调用__stl_next_prime(n),在__stl_prime_list数组中查找第一个大于等于n的数组并返回。

key不能取模的问题 

当key是string/Date等类型时,key不能取模,那么我们需要给HashTable增加⼀个仿函数这个仿函 数⽀持把key转换成⼀个可以取模的整形,如果key可以转换为整形并且不容易冲突,那么这个仿函数就用默认参数即可,如果这个Key不能转换为整形我们就需要自己实现⼀个仿函数传给这个参数,实 现这个仿函数的要求就是尽量key的每个值都参与到计算中,让不同的key转换出的整形值不同。string做哈希表的key值很常见,我们可以考虑把string特化一下。

template <class k>
class HashFunc
{
    size_t operator()(const k& key)
    {
        return (size_t)key;
    }
};
//特化
template<>
struct HashFunc<string>
{
    // 字符串转换成整形,可以把字符ascii码相加即可
    // 但是直接相加的话,类似"abcd"和"bcad"这样的字符串计算出是相同的
    // 这⾥我们使⽤BKDR哈希的思路,用上次的计算结果去乘以⼀个质数,这个质数⼀般去31, 131等效果会比较好

    size_t operator()(const string& s)
    {
        size_t hash = 0;
        for (auto ch : s)
        {
            hash += ch;
            hash *= 131;
        }
        return hash;
    }
};
 

 完整代码实现
//当前位置的状态
//存在  空  已删除
enum State
{EXIT,EMPTY,DELETE
};template <class k,class v>
struct HashData
{pair<k, v> _kv;State _state = EMPTY;
};template <class k>
class HashFunc
{size_t operator()(const k& key){return (size_t)key;}
};
//特化
template<>
struct HashFunc<string>
{// 字符串转换成整形,可以把字符ascii码相加即可// 但是直接相加的话,类似"abcd"和"bcad"这样的字符串计算出是相同的// 这⾥我们使⽤BKDR哈希的思路,⽤上次的计算结果去乘以⼀个质数,这个质数⼀般去31, 131等效果会比较好size_t operator()(const string& s){size_t hash = 0;for (auto ch : s){hash += ch;hash *= 131;}return hash;}
};template<class k, class v, class Hash = HashFunc<k>>
class HashTable
{
public:HashTable():_tables(__stl_next_prime(0)), _n(0){}inline unsigned long __stl_next_prime(unsigned long n){static 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};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;}bool insert(const pair<k, v>& kv){Hash hash;//负载因子>=0.7  扩容if (_n * 10 / _tables.size() >= 7){//创建一个新表HashTable<k, v, Hash> newht;//扩容newht._tables.resize(__stl_next_prime(_tables.size() + 1));//旧表数据映射到新表for (auto& data : _tables){if (data._state == EXIST)newht.insert(data._kv);}//_tables=newht._tables;_tables.swap(newht._tables);}size_t hash0 = hash(kv.first) % _tables.size();size_t hashi = hash0;int i = 1;while (_tables[hashi]._state == EXIST){//线性探测hashi = (hash0 + i) % _tables.size();//防止越界i++;}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;_n++;return true;}HashData<k, v>* find(const k& key){Hash hash;size_t hash0 = hash(key) % _tables.size();size_t hashi = hash0;int i = 1;while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._kv.first == key && _tables[hashi]._state == EXIST){return &_tables[hashi];}//线性探测hashi = (hash0 + i) % _tables.size();//防止越界i++;}return nullptr;}bool erase(const k& key){HashData<k, v>* ret = find(key);if (ret){ret->_state = DELETE;return true;}else{return false;}}
private:vector<HashData<k, v>> _tables;size_t _n;
};

3.3,链地址法

开放定址法中所有的元素都放到哈希表里,链地址法中所有的数据不再直接存储在哈希表中。哈希表 中存储一个指针,没有数据映射这个位置时,这个指针为空有多个数据映射到这个位置时,我们把这些冲突的数据链接成⼀个链表,挂在哈希表这个位置下面,链地址法也叫做拉链法或者哈希桶。

下面演示{19,30,5,36,13,20,21,12,24,96} 等这一组值映射到M=11的表中

哈希表扩容问题 

开放定址法负载因子必须小于1,链地址法的负载因子就没有限制了,可以大于1。负载因子越大,哈 希冲突的概率越高,空间利用率越高;负载因子越小,哈希冲突的概率越低,空间利用率越低;STL中的unordered_map和unordered_set最大复杂因子进本控制在1,大于1就开始扩容。

链地址法代码实现
//链地址法
//哈希桶
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_bucket
{
public:typedef HashNode<k, v> Node;Hash_bucket():_tables(__stl_next_prime(0)), _n(0){}~Hash_bucket(){for (int i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}inline unsigned long __stl_next_prime(unsigned long n){static 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};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;}bool insert(const pair<k, v>& kv){//防止键值冗余if (find(kv.first))return false;//负载因子>=1时,扩容if (_n / _tables.size() >= 1){//创建新表vector<Node*> newtables(__stl_next_prime(_tables.size() + 1));for (int i = 0; i < _tables.size(); i++){Node* cur = _tables[i];//直接将旧表中的节点插入到新表中 所映射的位置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;}Node* find(const k& key){size_t hashi = 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){size_t hashi = key % _tables.size();Node* cur = _tables[hashi];Node* prev = nullptr;while (cur){if (cur->_kv.first == key){if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;--_n;return true;}else{prev = cur;cur = cur->_next;}}return false;}private:vector<Node*> _tables;size_t _n;
};

小结:

哈希表这种数据结构,是利用哈希函数来快速定位数据的位置,然后存储到数组中的相应位置。

这样在查找的时候,时间复杂度可以接近O(1),非常高效。不过如果多个键被哈希到同一个位置,就会发生冲突,这时候需要解决冲突的方法,比如链地址法或者开放寻址法。

那哈希表的实现原理大概是怎样的呢?当插入一个键值对时,首先用哈希函数计算键的哈希值,然后根据哈希值找到对应的数组下标,如果该位置已经有元素了,就用链表或者其他方式处理冲突,然后存储进去。查找的时候同样计算哈希值,找到位置后,如果该位置有多个元素,需要遍历链表进行查找。好的哈希函数应该尽量均匀分布,减少冲突,这样哈希表的效率才会高。

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

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

相关文章

基于RTOS的STM32游戏机

1.游戏机的主要功能 所有游戏都来着B站JL单片机博主开源 这款游戏机具备存档与继续游戏功能&#xff0c;允许玩家在任何时候退出当前游戏并保存进度&#xff0c;以便日后随时并继续之前的冒险。不仅如此&#xff0c;游戏机还支持多任务处理&#xff0c;玩家可以在退出当前游戏…

优选算法的灵动之章:双指针专题(一)

个人主页&#xff1a;手握风云 专栏&#xff1a;算法 目录 一、双指针算法思想 二、算法题精讲 2.1. 查找总价格为目标值的两个商品 2.2. 盛最多水的容器 ​编辑 2.3. 移动零 2.4. 有效的三角形个数 一、双指针算法思想 双指针算法主要用于处理数组、链表等线性数据结构…

ROS应用之SwarmSim在ROS 中的协同路径规划

SwarmSim 在 ROS 中的协同路径规划 前言 在多机器人系统&#xff08;Multi-Robot Systems, MRS&#xff09;中&#xff0c;SwarmSim 是一个常用的模拟工具&#xff0c;可以对多机器人进行仿真以实现复杂任务的协同。除了任务分配逻辑以外&#xff0c;SwarmSim 在协同路径规划方…

MVC、MVP和MVVM模式

MVC模式中&#xff0c;视图和模型之间直接交互&#xff0c;而MVP模式下&#xff0c;视图与模型通过Presenter进行通信&#xff0c;MVVM则采用双向绑定&#xff0c;减少手动同步视图和模型的工作。每种模式都有其优缺点&#xff0c;适合不同规模和类型的项目。 ### MVVM 与 MVP…

【BUUCTF杂项题】后门查杀、webshell后门

前言&#xff1a;Webshell 本质上是一段可在 Web 服务器上执行的脚本代码&#xff0c;通常以文件形式存在于 Web 服务器的网站目录中。黑客通过利用 Web 应用程序的漏洞&#xff0c;如 SQL 注入、文件上传漏洞、命令执行漏洞等&#xff0c;将 Webshell 脚本上传到服务器&#x…

Spring中@Conditional注解详解:条件装配的终极指南

一、为什么要用条件装配&#xff1f; 在实际开发中&#xff0c;我们经常需要根据不同的运行环境、配置参数或依赖情况动态决定是否注册某个Bean。例如&#xff1a; 开发环境使用内存数据库&#xff0c;生产环境连接真实数据库 当存在某个类时才启用特定功能 根据配置文件开关…

visual studio安装

一、下载Visual Studio 访问Visual Studio官方网站。下载 Visual Studio Tools - 免费安装 Windows、Mac、Linux 在主页上找到并点击“下载 Visual Studio”按钮。 选择适合需求的版本&#xff0c;例如“Visual Studio Community”&#xff08;免费版本&#xff09;&#x…

【C语言深入探索】:指针高级应用与极致技巧(二)

目录 一、指针与数组 1.1. 数组指针 1.2. 指向多维数组的指针 1.2.1. 指向多维数组元素的指针 1.2.2. 指向多维数组行的指针 1.3. 动态分配多维数组 1.4. 小结 二、指针与字符串 2.1. 字符串表示 2.2. 字符串处理函数 2.3. 代码示例 2.4. 注意事项 三、指针与文件…

基于开源AI智能名片2 + 1链动模式S2B2C商城小程序源码在抖音招商加盟中的应用与创新

摘要&#xff1a;本文深入探讨了在短视频蓬勃发展的时代背景下&#xff0c;招商加盟领域借助抖音平台所具备的独特优势。同时&#xff0c;全面剖析开源AI智能名片2 1链动模式S2B2C商城小程序源码这一创新工具&#xff0c;详细阐述其如何与抖音招商加盟深度融合&#xff0c;助力…

爬虫学习笔记之Robots协议相关整理

定义 Robots协议也称作爬虫协议、机器人协议&#xff0c;全名为网络爬虫排除标准&#xff0c;用来告诉爬虫和搜索引擎哪些页面可以爬取、哪些不可以。它通常是一个叫做robots.txt的文本文件&#xff0c;一般放在网站的根目录下。 robots.txt文件的样例 对有所爬虫均生效&#…

Unity游戏(Assault空对地打击)开发(4) 碰撞体和刚体的添加

前言 飞机和世界的大小关系不太对&#xff0c;我稍微缩小了一下飞机。 详细步骤 选中所有地形对象&#xff0c;如果没有圈起的部分&#xff0c;点击Add Component搜索添加。 接着选中Player对象&#xff0c;添加这两个组件&#xff0c;最好&#xff08;仅对于本项目开发&#x…

【Linux】从硬件到软件了解进程

个人主页~ 从硬件到软件了解进程 一、冯诺依曼体系结构二、操作系统三、操作系统进程管理1、概念2、PCB和task_struct3、查看进程4、通过系统调用fork创建进程&#xff08;1&#xff09;简述&#xff08;2&#xff09;系统调用生成子进程的过程〇提出问题①fork函数②父子进程关…

Maven全解析:从基础到精通的实战指南

概念&#xff1a; Maven 是跨平台的项目管理工具。主要服务基于 Java 平台的构建&#xff0c;依赖管理和项目信息管理项目构建&#xff1a;高度自动化&#xff0c;跨平台&#xff0c;可重用的组件&#xff0c;标准化的流程 依赖管理&#xff1a; 对第三方依赖包的管理&#xf…

MATLAB实现单层竞争神经网络数据分类

一.单层竞争神经网络介绍 单层竞争神经网络&#xff08;Single-Layer Competitive Neural Network&#xff09;是一种基于竞争学习的神经网络模型&#xff0c;主要用于数据分类和模式识别。其核心思想是通过神经元之间的竞争机制&#xff0c;使得网络能够自动学习输入数据的特…

Weevely代码分析

亲测php5和php8都无效&#xff0c;只有php7有效 ailx10 1949 次咨询 4.9 网络安全优秀回答者 互联网行业 安全攻防员 去咨询 上一次做weevely实验可以追溯到2020年&#xff0c;当时还是weevely3.7&#xff0c;现在的是weevely4 生成php网页木马依然差不多…… php菜刀we…

【AI大模型】DeepSeek API大模型接口实现

目录 一、DeepSeek发展历程 2023 年&#xff1a;创立与核心技术突破 2024 年&#xff1a;开源生态与行业落地 2025 年&#xff1a;多模态与全球化布局 性能对齐 OpenAI-o1 正式版​ 二、API接口调用 1.DeepSeek-V3模型调用 2.DeepSeek-R1模型调用 三、本地化部署接口调…

具身智能-强化学习-强化学习基础-马尔可夫

文章目录 参考强化学习基础强化学习特点reward函数两种强化学习两种策略&#xff1a;探索&#xff08;Exploration&#xff09; vs. 利用&#xff08;Exploitation&#xff09;gym库的使用 马尔可夫马尔可夫过程马尔可夫奖励过程&#xff08;Markov Reward Process, MRP&#x…

半导体器件与物理篇5 mosfet及相关器件

认识mos二极管 MOS二极管是研究半导体表面特性最有用的器件之一。MOS二极管可作为存储电容器&#xff0c;并且是电荷耦合器件(CCD)的基本结构单元。 MOS二极管结构的重要参数包括&#xff1a;氧化层厚度d&#xff1b;施加于金属平板上的电压V&#xff08;正偏压时V为正&#x…

037 DFS回溯

1.回溯模板求排列 2.回溯模板求子集 # 当前位于点x&#xff0c;步长为length def dfs(x,length):passvis[x]length #接下来走下一个点 #判断下一个点是否走过if vis[a[x]]!0:#此时存在环global ansansmax(ans,length-vis[a[x]]1)else:dfs(a[x],length1)nint(input()) a[0]list(…

RK3568使用QT搭建TCP服务器和客户端

文章目录 一、让RK3568开发板先连接上wifi二、客户端代码1. `widget.h` 文件2. `widget.cpp` 文件**详细讲解**1. **`Widget` 类构造函数 (`Widget::Widget`)**2. **UI 布局 (`setupUI`)**3. **连接按钮的槽函数 (`onConnectClicked`)**4. **发送消息按钮的槽函数 (`onSendMess…