【数据结构】哈希表的原理及其实现

文章目录

  • 哈希表的概念
  • 哈希函数的设计
    • 常见的哈希函数
  • 哈希冲突
    • 1. 闭散列
      • 代码实现
    • 2. 开散列
    • 拉链法的优点
  • 针对开散列哈希的扩展
  • 基于开散列拉链法封装哈希表
    • MyHash.h
  • 基于哈希表实现unordered_map类
    • Myunordered_map.h
  • 基于哈希表实现unordered_set类
    • Myunordered_map.h

哈希表的概念

哈希表,也被称为散列表。是一种通过键可以快速找到对应值的一种数据结构。像我们常使用的unordered系列的容器,其本质就是一个哈希表。哈希表最显著的特点就是查找数据的效率非常的高,平均查找的时间复杂度为O(1)。通过哈希函数将键值映射到数组的具体位置,再对其进行操作。

哈希表查找的过程为:通过键来找到数组中的一个位置,通过这个位置我们能找到该键唯一映射的值
哈希表的y重要设计模块就是哈希函数以及解决哈希冲突的方法。下面将对此做出解释:

哈希函数的设计

哈希表的性能的一个关键因素就是哈希函数的设计。具体的来说,哈希函数指的是通过键值找到映射到数组具体位置的方法。一个好的哈希函数能将键均匀的分布到哈希表的各个位置。

常见的哈希函数

  1. 直接定值法
    取关键字的某个线性函数为散列地址,该线性函数的返回值应该是一个整数。比如Hash(key)=A*Key+B
  2. 除留余数法
    设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址

哈希冲突

什么叫哈希冲突?哈希冲突又叫做哈希碰撞。具体是指,多个键通过一个哈希函数得到的位置相同。造成哈希冲突的主要原因是,哈希表的大小是有限的,而输入的数据可能会非常多,因此不可能避免出现哈希冲突。

那出现哈希冲突之后该如何解决呢?下面给出几种常见的解决哈希冲突的方法:

1. 闭散列

闭散列又叫开放定址法。当发生哈希冲突时,如果该哈希表还有空位,那么就从冲突位置开始往后找,直到找到空位。该如何往后找空位呢?一个一个找嘛?

  • 线性探测:线性探测,从发生冲突的位置开始,一个一个往后面找,直到找到空位。
    在这里插入图片描述
  • 二次探测:从发生冲突的位置开始,使用一个二次探测公式来寻找下一个探测的位置,探测公式为:
    H(k)=(Hash(k)+C1*i+C2*(i^2))%p
    • 其中H(k)表示的是第i次探测的位置。
    • Hash(k)表示通过哈希函数得到的初始地址
    • C1C2是一个常数,C1通常为0,C2通常为1,即H(k)=(Hash(k)+(i^2))%p
    • i是探测的次数,从0开始
    • p是哈希表的大小

二次探测的优点是能有效减少一次探测中数据过于“集中”的问题,即连续的冲突位置,会形成长链。而二次探测会使得探测的位置更加分散。

研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容。

装载因子又叫做负载因子,是哈希表中元素个数与哈希表大小的比值

代码实现

哈希表类主要有两个模块需要实现,一个是节点类,还有一个是存储节点的容器。容器我们选择使用STL中的vector。对于节点类,我们希望至少能实现以下功能:

  • 存储键值对
  • 存储状态

值得注意的是,由于线性探测法的特性,删除一个元素之后可能会影响后续查找元素。我们需要给每个节点赋予三种状态表示:空节点、存在值得节点、被删除得节点。有了这三种种状态,我们在查找元素时,就不会因为中间曾经被删除的节点而影响。

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<vector>
using namespace std;
namespace Close_Hash{enum Statu{EMPTY,EXIST,DEL};template<class K,class V>struct Node {Node():_statu(EMPTY){}pair<K, V> _kv;Statu _statu;};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 res = 0;for (auto it : key) {int u = it - '0';res = res * 131 + u;}return res;}};template<class K,class V,class Hash=HashFunc<K>>class HashTable {public:HashTable(size_t capacity = 10): _totalSize(0), _ht(capacity){}V& operator[](const K& key) {Node<K, V>* node = Find(key);if (node) {return (node->_kv).second;}else {this->Insert(make_pair(key,0));return Find(key)->_kv.second;}}// 插入bool Insert(const pair<K, V>& val) {//负载超标if (_totalSize * 10 >= 7 * _ht.size()) {HashTable<K, V> newtable;newtable._ht.resize(_ht.size() * 2);for (int i = 0; i < _ht.size(); i++) {if (_ht[i]._statu == EXIST) {newtable.Insert(_ht[i]._kv);}}_ht.swap(newtable._ht);}Hash ha;size_t pos = ha(val.first) % _ht.size();//线性探测while (_ht[pos]._statu == EXIST) {pos++;pos = pos % _ht.size();}_ht[pos]._kv = val;_ht[pos]._statu = EXIST;_totalSize++;return true;}// 查找Node<K,V>* Find(const K& key) {Hash ha;size_t pos = ha(key) % _ht.size();while (_ht[pos]._statu != EMPTY) {if (_ht[pos]._statu == EXIST && _ht[pos]._kv.first == key) {return &_ht[pos];}pos++;pos = pos % _ht.size();}return nullptr;}// 删除bool Erase(const K& key) {Node<K, V>* node = Find(key);if (!node)return false;node->_statu = DEL;return true;}size_t Size() {return _ht.size();}bool Empty() const{return _ht.empty();}private:vector<Node<K,V>> _ht;size_t _totalSize;};}

2. 开散列

开散列法又被叫做链地址法或者拉链法。和闭散列不同的是,具有相同地址的关键码属于同一集合,并用一个单链表维护这个集合。我们将这个集合称为。开散列得到哈希表的每一个元素实际上是一个名为桶的单链表。于是,当我们发生哈希冲突时,不用去哈希表中找空位了,因为当前桶是一个单链表,插入元素时直接头插就好了。
在这里插入图片描述

拉链法的优点

拉链法处理哈希冲突的效率是非常高的。即使多个元素通过哈希函数得到的地址是一样的,也不会显著影响哈希表的性能。此外,拉链法下的负载因子是可以超过1的,因此拉链法下的哈希表可以灵活的应对动态数据集的增长,不需要频繁的调整哈希表的大小。值得一提的是,虽然看上去会使用额外的空间来存放指针,但由于不需要严格维护平衡因子,空间效率并不会比开放定址法低。因为开放定址法需要开大量额外的空间来保证负载因子不溢出。

代码实现:

跟线性探测代码不一样的地方在于,拉链法的节点实际上是一个链表的头节点(桶)。所以对于哈希表的节点类,我们希望有以下功能:

  • 指向下一个元素的指针
  • 存储键值对

在插入元素时,将新节点插入到桶中。

#pragma once
#include<vector>
#include<iostream>
using namespace std;namespace hash_bucket {template<class K,class V>struct HashNode {HashNode(const pair<K, V>& kv): _kv(kv),_next(nullptr){}HashNode():_next(nullptr){}pair<K, V> _kv;HashNode<K, V>* _next;};template<class K>struct HashFunc {size_t operator()(const K& k) {return (size_t)k;}};template<>struct HashFunc<string> {size_t operator()(const string& str) {size_t res = 0;for (auto it : str) {int u = it;res = res * 131 + u;}return res;}};template<class K,class V,class Hash=HashFunc<K>>class HashTable {public:typedef HashNode<K, V> Node;typedef Node* pNode;HashTable():_table(10, nullptr), _n(0){}//随机访问V& operator[](const K& k) {if (!Find(k)) {Insert(make_pair(k,V()));}return Find(k)->_kv.second;}//插入bool Insert(const pair<K, V>& kv) {Hash ha;//负载因子为1时扩容if (_n == _table.size()) {HashTable<K, V> newtable;newtable._table.resize(_table.size() * 2);for (size_t i = 0; i < _table.size(); i++) {if (_table[i]) {pNode cur = _table[i];while (cur) {pNode temp = cur->_next;size_t pos = ha(cur->_kv.first) % newtable._table.size();if (newtable._table[pos]) {cur->_next = newtable._table[pos];}newtable._table[pos] = cur;cur = temp;}}}_table.swap(newtable._table);}size_t pos = ha(kv.first) % _table.size();pNode newnode = new Node(kv);if (_table[pos] != nullptr) {newnode->_next = _table[pos];}_table[pos] = newnode;_n++;return true;}//查找pNode Find(const K& k) {Hash ha;size_t pos= ha(k) % _table.size();pNode cur = _table[pos];while (cur) {if (cur->_kv.first == k)return cur;cur = cur->_next;}return nullptr;}//删除bool Erase(const K& k) {Hash ha;if (!Find(k))return false;size_t pos = ha(k) % _table.size();pNode pre = nullptr;pNode cur = _table[pos];if (cur->_kv.first == k) {delete cur;_table[pos] = nullptr;return true;}else {while (cur) {if (cur->_kv.first == k) {pre->_next = cur->_next;delete cur;cur = nullptr;return true;}pre = cur;cur = cur->_next;}return false;}}private:vector<pNode> _table;int _n;//存储有效桶的个数};}

上述代码大致实现了基于开散列法的哈希表,支持键值对的插入,删除、查询以及修改。

注意开散列法哈希表的扩容方案。当负载因子达到一定值时,我们选择扩大哈希表的大小,对于旧哈希表的元素不用再重新拷贝构造,而是移植到新表中。这样就节省了很多空间。具体实现如下:

//负载因子为1时扩容
if (_n == _table.size()) {HashTable<K, V> newtable;//新表newtable._table.resize(_table.size() * 2);for (size_t i = 0; i < _table.size(); i++) {if (_table[i]) {//遍历旧表,移植节点pNode cur = _table[i];while (cur) {pNode temp = cur->_next;size_t pos = ha(cur->_kv.first) % newtable._table.size();if (newtable._table[pos]) {cur->_next = newtable._table[pos];}newtable._table[pos] = cur;cur = temp;}}}_table.swap(newtable._table);
}

针对开散列哈希的扩展

如何给上述开散列哈希表类增加迭代器呢?
由于迭代器中需要访问哈希表而不仅仅是桶,而哈希表又属于哈希表类的私有成员。因此,设计迭代器模块时,我们可以将迭代器类设置为哈希表类的内部类。而一个类的内部类是这个类的友元,因此迭代器就能访问到哈希表。给出以迭代器代码,每一个迭代器对象指向哈希表中桶内的一个节点

//迭代器内部类
template<class Ptr, class Ref>
struct _HashIterator {typedef _HashIterator<Ptr, Ref> self;pNode _node;const HashTable* _pht;_HashIterator(pNode pnode, const HashTable* pht):_node(pnode), _pht(pht){}Ref operator*() {return _node->_data;}Ptr operator->() {return &_node->_data;}self& operator=(const self it) {_node = it._node;_pht = it._pht;return *this;}self& operator++() {if (_node->_next) {_node = _node->_next;}else {KeyOft kft;Hash ha;size_t pos = ha(kft(_node->_data)) % _pht->_table.size();size_t i = pos + 1;for (; i < _pht->_table.size(); i++) {if (_pht->_table[i]) {break;}}if (i == _pht->_table.size()) {_node = nullptr;}else {_node = _pht->_table[i];}}return *this;}bool operator!=(const self& iterator) {return iterator._node != _node;}
};

该迭代器主要实现了重载操作符的作用,希望能将迭代器当指针来使用。

基于开散列拉链法封装哈希表

MyHash.h

该文件封装实现了一个哈希表类,哈希函数是除留余数,解决哈希冲突的方法采用拉链法。
代码:

#pragma once
#include<vector>
#include<iostream>
using namespace std;namespace hash_bucket {template<class T>struct HashNode {HashNode(const T& data): _data(data), _next(nullptr){}HashNode():_next(nullptr){}T _data;HashNode<T>* _next;};template<class K>struct HashFunc {size_t operator()(const K& k) {return (size_t)k;}};template<>struct HashFunc<string> {size_t operator()(const string& str) {size_t res = 0;for (auto it : str) {int u = it;res = res * 131 + u;}return res;}};template<class K, class T, class KeyOft, class Hash = HashFunc<K>>class HashTable {public:typedef HashNode<T> Node;typedef Node* pNode;HashTable():_table(10, nullptr), _n(0){}//迭代器内部类template<class Ptr, class Ref>struct _HashIterator {typedef _HashIterator<Ptr, Ref> self;pNode _node;const HashTable* _pht;_HashIterator(pNode pnode, const HashTable* pht):_node(pnode), _pht(pht){}Ref operator*() {return _node->_data;}Ptr operator->() {return &_node->_data;}self& operator=(const self it) {_node = it._node;_pht = it._pht;return *this;}self& operator++() {if (_node->_next) {_node = _node->_next;}else {KeyOft kft;Hash ha;size_t pos = ha(kft(_node->_data)) % _pht->_table.size();size_t i = pos + 1;for (; i < _pht->_table.size(); i++) {if (_pht->_table[i]) {break;}}if (i == _pht->_table.size()) {_node = nullptr;}else {_node = _pht->_table[i];}}return *this;}bool operator!=(const self& iterator) {return iterator._node != _node;}};typedef _HashIterator<T*, T&> Iterator;typedef _HashIterator<const T*, const T&> Const_Iterator;//迭代器Iterator begin() {for (size_t i = 0; i < _table.size(); i++) {if (_table[i]) {return Iterator(_table[i], this);}}return Iterator(nullptr, this);}Iterator end() {return Iterator(nullptr, this);}Const_Iterator begin()const {for (size_t i = 0; i < _table.size(); i++) {if (_table[i]) {return Iterator(_table[i], this);}}return Iterator(nullptr, this);}Const_Iterator end() const {return Iterator(nullptr, this);}//随机访问/*V& operator[](const K& k) {if (!Find(k)) {Insert(make_pair(k,V()));}return Find(k)->_kv.second;}*///插入pair<Iterator, bool> Insert(const T& val) {Hash ha;KeyOft kft;Iterator it = Find(kft(val));if (it._node != nullptr)return make_pair(it, false);//负载因子为1时扩容if (_n == _table.size()) {HashTable<K, T, KeyOft> newtable;newtable._table.resize(_table.size() * 2);for (size_t i = 0; i < _table.size(); i++) {if (_table[i]) {pNode cur = _table[i];while (cur) {pNode temp = cur->_next;size_t pos = ha(kft(cur->_data)) % newtable._table.size();if (newtable._table[pos]) {cur->_next = newtable._table[pos];}newtable._table[pos] = cur;cur = temp;}}}_table.swap(newtable._table);}size_t pos = ha(kft(val)) % _table.size();pNode newnode = new Node(val);if (_table[pos] != nullptr) {newnode->_next = _table[pos];}_table[pos] = newnode;_n++;return make_pair(Iterator(newnode, this), true);}//查找Iterator Find(const K& k) {Hash ha;KeyOft kft;size_t pos = ha(k) % _table.size();pNode cur = _table[pos];while (cur) {if (kft(cur->_data) == k)return Iterator(cur, this);cur = cur->_next;}return Iterator(nullptr, this);}//删除bool Erase(const K& k) {Hash ha;KeyOft kft;if (!Find(k))return false;size_t pos = ha(k) % _table.size();pNode pre = nullptr;pNode cur = _table[pos];if (kft(cur->_data) == k) {delete cur;_table[pos] = nullptr;return true;}else {while (cur) {if (kft(cur->_data) == k) {pre->_next = cur->_next;delete cur;cur = nullptr;return true;}pre = cur;cur = cur->_next;}return false;}}private:vector<pNode> _table;int _n;};}

基于哈希表实现unordered_map类

基于MyHash.h封装的哈希表类提供的接口,实现一个unordered_map。unordered_map的值是一个键值对。

Myunordered_map.h

该文件基本实现了unordered_map类。
代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include"MyHash.h"namespace bit {template<class K, class V, class Hash =	hash_bucket::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;//typedef typename hash_bucket::HashTable<K, pair<const K, V>, KeyOft, Hash>::Const_Iterator const_iterator;iterator begin() {return _ht.begin();}iterator end() {return _ht.end();}V& operator[](const K& k) {pair<iterator,bool>res=insert(make_pair(k, V()));return res.first->second;}pair<iterator, bool> insert(const pair<K,V>& kv) {return _ht.Insert(kv);}bool erase(const K& k) {return _ht.Erase(k);}pair<iterator, bool> find(const K& k) {return _ht.Find(k);}private:hash_bucket::HashTable<K, pair<const K, V>, MapKeyOft, Hash> _ht;};}

在这里插入图片描述

基于哈希表实现unordered_set类

基于MyHash.h封装的哈希表类提供的接口,实现一个unordered_set类。

Myunordered_map.h

该文件基本实现了unordered_set类。
代码:

#include"MyHash.h"namespace bit {template<class K,class Hash = hash_bucket::HashFunc<K> >class unordered_set {struct SetKeyOft {const K& operator()(const K& k) {return k;}};public:typedef typename hash_bucket::HashTable<K, const K, SetKeyOft, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, const K, SetKeyOft, Hash>::Const_Iterator const_iterator;iterator begin() {return _ht.begin();}iterator end() {return _ht.end();}pair<iterator, bool> insert(const K& kv) {return _ht.Insert(kv);}bool erase(const K& k) {return _ht.Erase(k);}pair<iterator, bool> find(const K& k) {return _ht.Find(k);}private:hash_bucket::HashTable<K, const K, SetKeyOft, Hash> _ht;};}

在这里插入图片描述

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

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

相关文章

匠心独运的掺Si量子势垒策略,显著提升了AlGaN基深紫外LED出光率

WHU团队凭借匠心独运的三明治式掺Si量子势垒策略&#xff0c;显著提升了AlGaN基深紫外光LED的效率&#xff0c;这一创新成果为中国武汉大学的研究团队所取得。他们巧妙地设计出一种三明治状Si掺杂&#xff08;未掺杂&#xff09;方案&#xff0c;应用于Al0.6Ga0.4N量子势垒中&a…

WSL安装CentOS系统

1.首选找一个linux系统&#xff0c;执行docker命令 docker run -it --rm centos:7 bash 2.开一个新窗口&#xff0c;将系统导出 docker export e0ee25406703 -o centos.tar 3.切换到wsl命令&#xff0c;导入tar包 wsl --import centos D:\wsl\centos D:\wsl\centos.tar cen…

queue学习

std::queue 类是一种容器适配器&#xff0c;它提供队列的功能——尤其是 FIFO&#xff08;先进先出&#xff09;数据结构。此类模板用处为底层容器的包装器——只提供特定的函数集合。queue 在底层容器尾端推入元素&#xff0c;从首端弹出元素。 元素访问 front 访问第一个元素…

Elastic Cloud 将 Elasticsearch 向量数据库优化配置文件添加到 Microsoft Azure

作者&#xff1a;来自 Elastic Serena Chou, Jeff Vestal, Yuvraj Gupta 今天&#xff0c;我们很高兴地宣布&#xff0c;我们的 Elastic Cloud Vector Search 优化硬件配置文件现已可供 Elastic Cloud on Microsoft Azure 用户使用。 此硬件配置文件针对使用 Elasticsearch 作…

web如何做接口层面自动化测试?

接口层面约等于集成化测试&#xff0c;且需要启动web容器 一般web项目的&#xff0c;代码都是按照分层开发的&#xff0c;业务主要是集中在service和dao层&#xff0c;而我们如果仅仅是利用之前的单元测试,然后把依赖的代码直接mock掉&#xff0c;仅仅测试controller这一块是没…

数据库SQL语言实战(十)(最后一篇)

目录 前言 练习题 实验八 实验九 题目一 题目二 总结 前言 本篇练习题的重点有两个&#xff1a; 一、测试提交commit和回滚rollback的作用,了解锁等待、授权等知识。 二、学会复制表结构、学会插入数据&#xff0c;特别是学会如何避免重复插入&#xff0c;也就是如何避…

ASP.NET MVC 快速入门(图文版)

今年是2024年了&#xff0c;没有多少人在ASP.NET 去做开发&#xff0c;都使用ABP框架 &#xff0c;不过我们仍然需要了解ASP.NET MVC 的一个开发流程 MVC概述 MVC是当前比较流行的WEB程序开发模式之一&#xff0c;ASP.NET MVC是.Net对MVC的一种实现。MVC&#xff08;Model View…

声压级越大,STIPA 越好,公共广播就越清晰吗?

在公共广播中&#xff0c;有些朋友经常问到是不是声压越大&#xff0c;广播清晰度就越高&#xff0c;下面我从搜集了一些专业技术资料&#xff0c;供大家参考。 一、声压级越大&#xff0c;STIPA 越好吗&#xff1f; 不完全是。最初&#xff0c;人们认为当声压级达到 60 dBA 以…

氢燃料电池汽车行业发展

文章目录 前言 市场分布 整车销售 发动机配套 氢气供应 发展动能 参考文献 前言 见《氢燃料电池技术综述》 见《燃料电池工作原理详解》 见《燃料电池发电系统详解》 见《燃料电池电动汽车详解》 市场分布 纵观全球的燃料电池汽车市场&#xff0c;截至2022年底&#xff…

2024最新 Jenkins + Docker实战教程(一) - Jenkins介绍及安装

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Android BACK键和HOME键应用差异详解

文章目录 1、应用层分析1.1 BACK键功能实现 1.2 HOME键功能实现 1.3 BACK键与HOME键的区别 2、系统层分析2.1 BACK键的处理2.2 HOME键的处理2.3 代码分析BACK键HOME键BACK键的系统代码分析HOME键的系统代码分析BACK键HOME键 3、优缺点分析3.1 BACK键3.2 HOME键 4、项目中的使用…

【学习笔记】Windows GDI绘图(七)图形路径GraphicsPath详解(下)

文章目录 前三篇回顾GraphicsPath方法Flatten压平(将曲线转成线段)GetBounds获取外接矩形GetLastPoint获取路径最后一个点IsOutlineVisibleIsVisiable是否在轮廓上或内部Reset重置Reverse逆转点的顺序Transform矩阵变换Wrap扭曲变换Widen将路径替换为指定画笔的填充区域 前三篇…

生成式AI导论2024-李宏毅

生成式AI导论2024-李宏毅 第0讲&#xff1a; 课程说明第1讲&#xff1a;生成式AI是什么第2講&#xff1a;今日的生成式人工智慧厲害在哪裡&#xff1f;從「工具」變為「工具人」 第0讲&#xff1a; 课程说明 生成式AI的入门课程 第1讲&#xff1a;生成式AI是什么 生成式人…

AI预测福彩3D采取888=3策略+和值012路一缩定乾坤测试5月26日预测第2弹

昨天的8883大底成功命中&#xff0c;但是由于昨天杀了对子&#xff0c;结果昨天开了对子&#xff0c;导致最终与中奖号码擦肩而过。今天继续基于8883的大底&#xff0c;使用尽可能少的条件进行缩号&#xff0c;同时&#xff0c;今天将准备两套方案&#xff0c;一套是我自己的条…

英语学习笔记28——Where are they?

Where are they? 他们在哪里&#xff1f; 课文部分

【NumPy】关于numpy.median()函数,看这一篇文章就够了

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

贝叶斯算法:机器学习中的“黄金法则”与性能提升之道

&#x1f440;传送门&#x1f440; &#x1f50d;机器学习概述&#x1f340;贝叶斯算法原理&#x1f680;贝叶斯算法的应用✨文本分类✨医疗系统 &#x1f496;贝叶斯算法优化✨贝叶斯算法优化的主要步骤✨贝叶斯算法优化的优点✨贝叶斯算法优化的局限性 &#x1f697;贝叶斯算…

【iOS开发】—— KVC

【iOS开发】—— KVC 一. KVC的定义key和keyPath的区别用法&#xff1a; 批量复制操作字典模型相互转化KVC的其他方法 KVC原理赋值原理取值原理 一. KVC的定义 KVC&#xff08;Key-value coding&#xff09;键值编码&#xff0c;就是指iOS的开发中&#xff0c;可以允许开发者通…

不用从头训练,通过知识融合创建强大的统一模型

在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;的开发和训练是一个复杂且成本高昂的过程。数据需求是一个主要问题&#xff0c;因为训练这些模型需要大量的标注数据来保证其准确性和泛化能力&#xff1b;计算资源也是一个…

Java学习路线思维导图

目录 Java学习流程1.学习大纲2.Java开发中常用的DOS命令 Java入门学习思维导图 Java学习流程 通过大纲了解学习的重点&#xff0c;通过目录依次深入【注&#xff1a;Java环境的搭建百度&#xff0c;提升自己百度的能力】 1.学习大纲 学习流程如下&#xff1a; Java基础语法 …