当前位置: 首页 > wzjs >正文

东莞seo网站推广网络营销发展方案策划书

东莞seo网站推广,网络营销发展方案策划书,成都公司网站设计哪家专业,商场网站设计目录 1、哈希的概念 1.1 直接定址法 1.2 哈希冲突 1.3 负载因子 1.4 将关键字转为整数 2、哈希函数 2.1 除法散列法/除留余数法 2.2 乘法散列法(了解) 2.3 全域散列法(了解) 2.4 其他方法(了解) 3、处理哈希冲突 3.1 开放定址法 3.1.1 线性探测 3.1.2 二次探测 3…

目录

1、哈希的概念

1.1 直接定址法

1.2 哈希冲突

1.3 负载因子

1.4 将关键字转为整数

2、哈希函数

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

2.2 乘法散列法(了解)

2.3 全域散列法(了解)

2.4 其他方法(了解)

3、处理哈希冲突

3.1 开放定址法

3.1.1 线性探测

3.1.2 二次探测

3.1.3 双重探测(了解)

3.2 开放地址法的代码实现

3.3 链地址法

3.4 连地址法的代码实现

4、测试代码

4.1 Test.cpp


1、哈希的概念

哈希(hash),又称散列,是一种高效的数据组织方式。其核心思想是通过哈希函数建立关键字(Key)存储位置之间的映射关系,从而实现快速查找。

Key  →  [哈希函数]  →  存储位置

1.1 直接定址法

关键字的范围比较集中时,直接定址法是一种非常简单高效的方法。例如:

  1. 如果一组关键字都在 [0,99] 之间,那么我们可以开一个包含100个数的数组,每个关键字的值直接作为存储位置的下标。
  2. 如果一组关键字值都在 [a,z] 的小写字母范围内,那么我们可以开一个包含26个数的数组,每个关键字的 ASCII 码减去 'a' 的 ASCII 码,结果就是存储位置的下标。

1.2 哈希冲突

直接定址法的缺点也非常明显,当关键字的范围比较分散时,就很浪费内存甚至内存不够用。假设我们只有数据范围 [0, 9999] N 个值,我们要映射到一个 M 个空间的数组中(一般情况下 M ≥ N),那么就要借助哈希函数(hash function)hf,关键字 key 映射到数组的 h(key) 位置,这里要注意的是 h(key) 计算出的值必须在 [0, M) 之间。

这里存在的一个问题就是,两个不同的 key 可能会映射到同一个位置,这种问题我们叫做哈希冲突,或者哈希碰撞

哈希冲突不可避免,可以选择发生哈希冲突较少的哈希函数比较好的处理哈希冲突的方法

1.3 负载因子

假设哈希表中已经映射存储了 N 个值,哈希表的大小为 M,那么
负载因子 = N / M
负载因子有些地方也翻译为载荷因子/装载因子等,它的英文为 load factor

负载因子哈希冲突的概率越空间利用率
负载因子哈希冲突的概率越空间利用率越

1.4 将关键字转为整数

我们将关键字映射到数组中的位置,通常使用整数便于映射计算。如果关键字不是整数,需要先将其转换为整数

2、哈希函数

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

除法散列法也叫做除留余数法,假设哈希表的大小为 M,那么通过 key 除以 M 的余数作为映射位置的下标,哈希函数为:h(key) = key % M

当使用除法散列法时,要尽量避免 M某些值,如 2 的幂10 的幂等。如果是 2^X,那么 key % M 本质相当于保留 key 的后 X 位,那么后 X 位相同的值计算出的哈希值都是一样的,就冲突了。如:{63, 31} 看起来没有关联的值,如果 M 是 16,也就是 2^4,那么计算出的哈希值都是 15,因为 63 的二进制后 8 位是 00111111,31 的二进制后 8 位是 00011111。如果是 10^X,就更明显了,保留的都是 10 进制的后 X 位,如:{112, 12312},如果 M 是 100,也就是 10^2,那么计算出的哈希值都是 12。

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

注意:哈希表大小 M 是HashTable.size(),因为%size,就放到[0,size-1)

Java 的 HashMap 为什么用 2 的幂次?(了解)

可以位运算,相对而言位运算比模更高效一些。比如 M 是 2^16 次方,本质是取后 16 位,那么用 key' = key >> 16,然后把 key 和 key' 异或的结果作为哈希值映射出的值还是在 [0, M) 范围内,但是尽量让 key 所有的位都参与计算,这样映射出的哈希值更均匀一些

2.2 乘法散列法(了解)

乘法散列法哈希表大小 M 没有要求

他的大思路第一步:key * A (0 < A < 1)。第二步:再用 M 乘以 key * A 的小数部分,再向下取整。

h(key) = floor(M × ((A × key) % 1.0))

其中 floor 表示对表达式进行下取整,A ∈ (0,1),这里最重要的是 A 的值应该如何设定,Knuth 认为 A = (√5 − 1)/2 ≈ 0.6180339887...(黄金分割点比较好

乘法散列法对哈希表大小 M 是没有要求的,假设 M 为 1024,key 为 1234,A = 0.6180339887,A * key = 762.6539420558,取小数部分为 0.6539420558,M × ((A × key) % 1.0) = 0.6539420558 * 1024 = 669.6366651392,那么 h(1234) = 669。

2.3 全域散列法(了解)

如果存在一个恶意的对手,他针对我们提供的散列函数,特意构造出一个发生严重冲突的数据集,比如,让所有关键字全部落入同一个位置中。这种情况是可以存在的,只要散列函数是公开且确定的,就可以实现此攻击。解决方法自然是见招拆招,给散列函数增加随机性,攻击者就无法找出确定可以导致最坏情况的数据。这种方法叫做全域散列

h_ab(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_34(8) = ((3 × 8 + 4) % 17) % 6 = 5。

需要注意的是每次初始化哈希表时随机选取全域散列函数组中的一个散列函数使用,后续增删查改都固定使用这个散列函数

2.4 其他方法(了解)

上面的几种方法是《算法导论》书籍中讲解的方法。

《殷人昆 数据结构:用面向对象方法与C++语言描述(第二版)》和《数据结构(C语言版) 严蔚敏 吴伟民》等教材型书籍上面还给出了平方取中法、折叠法、随机数法、数学分析法等,这些方法相对更适用于一些局限的特定场景,有兴趣可以去看看这些书籍。

3、处理哈希冲突

3.1 开放定址法

3.1.1 线性探测

h(key) = hash0 = key % M 

若hash0位置冲突了,就向后走,hc(key,i) = hashi = (hash0 + i) % M  i = {1,2,3,...,M −1} ,

3.1.2 二次探测

h(key) = hash0 = key % M

若hash0位置冲突了,就前后跳跃走,hc(key,i) = hashi = (hash0 ± i^2 ) % M ,  i = {1,2,3,..., M/2}

hashi<0 时,需要 hashi+=M

3.1.3 双重探测(了解)

h1(key) = hash0 = key % M

若hash0位置冲突了,hc(key,i) = hashi = (hash0 + i∗h2(key) ) % M i = {1,2,3,..., M}

要求 h2(key) < M  且 h(key)与M互质。

两种简单取值方法:

  1. M2 的整数幂时,h₂(key)可以随机选 [0, M-1] 之间的任意奇数

  2. M质数时,取 h2(key)  = key % (M −1) + 1

3.2 开放地址法的代码实现

1. 开放定址法实践中不如下面链地址法,所以我们简单选择线性探测实现即可

2. 这里需要给每个存储值的位置加一个状态标识{EXIST,EMPTY,DELETE},否则真的删除一些值以后,会影响后面冲突的值的查找。

3. 开放定址法相互占位置负载因子一定<1。一般负载因子>=0.7扩容,扩容使用sgi版本的方法,给了一个近似2倍的质数表,每次去质数表获取扩容后的大小。

4. 不是整数的key通过仿函数转成整数

#pragma once#include <iostream>
#include <vector>using namespace std;namespace Lzc
{inline size_t __stl_next_prime(size_t n) {static const int __stl_num_primes = 28;static const size_t __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 size_t* first = __stl_prime_list;const size_t* last = __stl_prime_list + __stl_num_primes;const size_t* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;}template<class K>struct HashFunc{size_t operator()(const K& key){return key;}};template<>struct HashFunc<string>{// 字符串转换成整形,可以把字符ASCII码相加即可// 但是直接相加的话,类似"abcd"和"bcad"这样的字符串计算出是相同的// 这里使用BKDR哈希,用上次的计算结果去乘以一个质数,// 这个质数一般取31, 131等效果会比较好size_t operator()(const string& key){size_t hash = 0;for (auto& ch : key){hash = hash * 131 + ch;}return hash;}};namespace open_address{enum State{EMPTY,EXIST,DELETE};template<class K, class V>struct HashData{pair<K, V> _kv;State _state;HashData():_kv(),_state(EMPTY){ }};template<class K, class V,class Hash = HashFunc<K>>class HashTable{public:HashTable():_table(__stl_next_prime(0), HashData<K,V>()), _n(0){ }Hash hash;bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;if (_n >= _table.size() * MAX_LOAD_FACTOR){vector<HashData<K,V>> newtable(__stl_next_prime(_table.size() + 1), HashData<K, V>());for (size_t i = 0; i < _table.size(); ++i){if (_table[i]._state == EXIST){size_t hash0 = hash(_table[i]._kv.first) % newtable.size();size_t hashi = hash0;int j = 1;while (newtable[hashi]._state == EXIST){hashi = (hash0 + j) % newtable.size();++j;}newtable[hashi] = _table[i];}}_table.swap(newtable);}size_t hash0 = hash(kv.first) % _table.size();size_t hashi = hash0;int i = 1;while (_table[hashi]._state == EXIST){hashi = (hash0 + i) % _table.size();++i;}_table[hashi]._kv = kv;_table[hashi]._state = EXIST;++_n;return true;}HashData<K,V>* Find(const K& key){size_t hash0 = hash(key) % _table.size();size_t hashi = hash0;int i = 1;while (_table[hashi]._state!=EMPTY){if (_table[hashi]._state == EXIST && _table[hashi]._kv.first == key)return &_table[hashi];hashi = (hash0 + i) % _table.size();++i;if (hashi == hash0)break;}return nullptr;}bool Erase(const K& key){HashData<K,V>* data = Find(key);if (data){data->_state = DELETE;return true;}elsereturn false;}private:static constexpr double MAX_LOAD_FACTOR = 0.7; // 负载因子vector<HashData<K,V>> _table;size_t _n;};}
}

3.3 链地址法

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

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

3.4 连地址法的代码实现

1. 链地址法的负载因子就没有限制STL中 unordered_xxx的负载因子>=1扩容,我们也使用这个方式。

2. 极端场景,如果被针对,哈希函数使用全域散列法,如果不是被针对,在Java8HashMap中当桶的长度超过一定阀值(8)时就把链表转换成红黑树。下面我们实现就不搞这么复杂了,这个解决极端场景的思路,大家了解一下。

#pragma once#include <iostream>
#include <vector>using namespace std;namespace Lzc
{inline size_t __stl_next_prime(size_t n) {static const int __stl_num_primes = 28;static const size_t __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 size_t* first = __stl_prime_list;const size_t* last = __stl_prime_list + __stl_num_primes;const size_t* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;}template<class K>struct HashFunc{size_t operator()(const K& key){return key;}};template<>struct HashFunc<string>{// 字符串转换成整形,可以把字符ASCII码相加即可// 但是直接相加的话,类似"abcd"和"bcad"这样的字符串计算出是相同的// 这里使用BKDR哈希,用上次的计算结果去乘以一个质数,// 这个质数一般取31, 131等效果会比较好size_t operator()(const string& key){size_t hash = 0;for (auto& ch : key){hash = hash * 131 + ch;}return hash;}};namespace hash_bucket{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 = HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public:HashTable():_table(__stl_next_prime(0), nullptr), _n(0){}~HashTable(){for (size_t i = 0; i < _table.size(); ++i){if (_table[i]){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}}_table[i] = nullptr;}_n = 0;}Hash hash;bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;// 负载因子>=1扩容if (_n >= _table.size()){vector<Node*> newtable(__stl_next_prime(_table.size() + 1), nullptr);for (size_t i = 0; i < _table.size(); ++i){if (_table[i]){Node* cur = _table[i];while (cur){Node* next = cur->_next;size_t hash0 = hash(cur->_kv.first) % newtable.size();cur->_next = newtable[hash0]; // 头插newtable[hash0] = cur;cur = next;}}_table[i] = nullptr;}_table.swap(newtable);}size_t hash0 = hash(kv.first) % _table.size();Node* newnode = new Node(kv);newnode->_next = _table[hash0];// 头插_table[hash0] = newnode;++_n;return true;}Node* Find(const K& key){size_t hash0 = hash(key) % _table.size();Node* cur = _table[hash0];while (cur){if (cur->_kv.first == key)return cur;cur = cur->_next;}return nullptr;}bool Erase(const K& key){size_t hash0 = hash(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hash0];while (cur){if (cur->_kv.first == key){if (prev)prev->_next = cur->_next;else_table[hash0] = cur->_next;delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _table;size_t _n;};}
}

4、测试代码

4.1 Test.cpp

#include "HashTable.h"
#include <assert.h>void TestInsertAndFind()
{//Lzc::open_address::HashTable<int, string> table;Lzc::hash_bucket::HashTable<int, string> table;table.Insert({ 1, "one" });table.Insert({ 2, "two" });table.Insert({ 3, "three" });auto* data1 = table.Find(1);auto* data2 = table.Find(2);auto* data3 = table.Find(3);auto* data4 = table.Find(4);  // 不存在的键assert(data1 != nullptr && data1->_kv.second == "one");assert(data2 != nullptr && data2->_kv.second == "two");assert(data3 != nullptr && data3->_kv.second == "three");assert(data4 == nullptr);  // 应返回 nullptrcout << "TestInsertAndFind passed." << endl;
}void TestErase()
{//Lzc::open_address::HashTable<int, string> table;Lzc::hash_bucket::HashTable<int, string> table;table.Insert({ 1, "one" });table.Insert({ 2, "two" });bool Erased1 = table.Erase(1);bool Erased2 = table.Erase(3);  // 不存在的键assert(Erased1 == true);assert(Erased2 == false);assert(table.Find(1) == nullptr);  // 应已被删除cout << "TestErase passed." << endl;
}void TestHashCollision()
{// 使用固定大小的哈希表(如大小为 3)强制触发冲突//Lzc::open_address::HashTable<int, string> table;Lzc::hash_bucket::HashTable<int, string> table;table.Insert({ 1, "one" });table.Insert({ 4, "four" });  // 假设 1 和 4 哈希冲突auto* data1 = table.Find(1);auto* data4 = table.Find(4);assert(data1 != nullptr && data1->_kv.second == "one");assert(data4 != nullptr && data4->_kv.second == "four");cout << "TestHashCollision passed." << endl;
}void TestReuseAfterErase()
{//Lzc::open_address::HashTable<int, string> table;Lzc::hash_bucket::HashTable<int, string> table;table.Insert({ 1, "one" });table.Erase(1);table.Insert({ 1, "new_one" });  // 应复用被删除的位置auto* data = table.Find(1);assert(data != nullptr && data->_kv.second == "new_one");cout << "TestReuseAfterErase passed." << endl;
}void TestResize()
{//Lzc::open_address::HashTable<int, int> table;Lzc::hash_bucket::HashTable<int, int> table;// 记录插入前的状态(无法直接获取 size,只能通过行为推断)// 插入足够多的元素触发扩容(初始大小为 __stl_next_prime(0) = 53)// 负载因子 0.7,所以触发扩容的临界值是 53 * 0.7 ≈ 37 个元素const int trigger_count = 38;  // 确保超过负载因子// 插入元素并验证for (int i = 0; i < trigger_count; ++i) {bool success = table.Insert({ i, i * 10 });assert(success == true);}// 验证所有插入的元素仍然可找到for (int i = 0; i < trigger_count; ++i) {auto* data = table.Find(i);assert(data != nullptr && data->_kv.second == i * 10);}cout << "TestResize passed." << endl;
}void TestStringKey()
{//Lzc::open_address::HashTable<string, int> table;Lzc::hash_bucket::HashTable<string, int> table;table.Insert({ "apple", 1 });table.Insert({ "banana", 2 });assert(table.Find("apple")->_kv.second == 1);assert(table.Find("banana")->_kv.second == 2);assert(table.Find("orange") == nullptr);cout << "TestStringKey passed." << endl;
}void TestEmptyTable()
{//Lzc::open_address::HashTable<int, int> table;Lzc::hash_bucket::HashTable<int, int> table;assert(table.Find(1) == nullptr);assert(table.Erase(1) == false);cout << "TestEmptyTable passed." << endl;
}void TestDuplicateInsert()
{//Lzc::open_address::HashTable<int, string> table;Lzc::open_address::HashTable<int, string> table;table.Insert({ 1, "one" });bool success = table.Insert({ 1, "new_one" });  // 重复插入assert(success == false);  // 应返回 false(或 true,取决于你的设计)assert(table.Find(1)->_kv.second == "one");  // 值未被覆盖cout << "TestDuplicateInsert passed." << endl;
}void RunAllTests()
{TestInsertAndFind();TestErase();TestHashCollision();TestReuseAfterErase();TestResize();TestStringKey();TestEmptyTable();TestDuplicateInsert();cout << "All tests passed!" << endl;
}int main()
{RunAllTests();return 0;
}
http://www.dtcms.com/wzjs/101173.html

相关文章:

  • 河南省建设厅陈华平官方网站品牌推广平台
  • 网站交易关键词优化报价推荐
  • 网络规划设计师考试考点分析网盘seo网站优化策划书
  • 陇南做网站百度快照
  • 北京西站疫情百度账号免费注册
  • wordpress关闭更新seo快速排名软件价格
  • 企业网站建设可行性分析搜索词热度查询
  • 淘宝客做网站怎样推广上海seo公司
  • 怎么提高网站百度权重如何推销自己的产品
  • 施工企业的维保技术方案泰安seo
  • java 做直播网站有哪些软件有哪些班级优化大师手机版下载
  • 网站商城制作石家庄疫情太严重了
  • 石家庄电子商务网站建设百度排名优化工具
  • 修改网站模板详解浙江网站推广运营
  • 网页制作素材去哪找成都网站seo公司
  • 南宁市城市建设档案馆网站网站如何被百度快速收录
  • 服装网站开发的需求分析百度知道合伙人官网
  • 邯郸网站设计培训seo搜索引擎优化是通过优化答案
  • 专业做网站优化需要多久合肥百度快速排名优化
  • 做酒店需要怎么上网站地推拉新app推广接单平台
  • 江苏省建设执业网站网络营销的8个基本职能
  • 虚拟币网站开发制作整合营销包括哪些内容
  • 网站的icon图标做多大宁波seo外包优化
  • 简洁的门户网站关键词排名推广
  • c 能用来做网站吗北京朝阳区优化
  • 免费虚拟服务器seo公司广州
  • 做网站需要准备什么条件行业网站有哪些平台
  • 温州 网站建设打开百度网站首页
  • 哪个网站有做烘焙蛋糕专业的配方如何搭建网站
  • 中国手机网站建设公司广东搜索引擎优化