• C++ 哈希:HashTable模拟实现(补充)


    目录

    1.引例:两个练习题

    2.定义基本的存储结构

    3.Insert()

    4.Erase()

    5.string为参数的情况

    6.添加类模板 

    7.整体代码


    1.引例:两个练习题

    2.定义基本的存储结构

    1. #pragma once
    2. namespace CloseHash
    3. {
    4. template<class K,class V>
    5. struct HashData
    6. {
    7. pair _kv;
    8. };
    9. template<class K,class V>
    10. class HashTable
    11. {
    12. private:
    13. HashData* _table;
    14. size_t _size;
    15. size_t _capacity; //用于增容
    16. };
    17. }

    后者直接用vector存储

    1. #pragma once
    2. #include
    3. #include
    4. using namespace std;
    5. namespace CloseHash
    6. {
    7. template<class K,class V>
    8. struct HashData
    9. {
    10. pair _kv;
    11. };
    12. template<class K,class V>
    13. class HashTable
    14. {
    15. public://待写
    16. private:
    17. //HashData* _table;
    18. //size_t _size;
    19. //size_t _capacity; //用于增容
    20. vector _table;
    21. size_t _n;//存储的有效数据的个数
    22. };
    23. }

    3.Insert()

    如何判断一个位置是空还是满?若删除其中一个值,用空值去标识,下一次找后面冲突的值找不到了。前一个冲突的值被删了,导致不连续了,找到空就结束了。

     提供一个状态标识即可

    1. namespace CloseHash
    2. {
    3. enum State//状态
    4. {
    5. EMPTY,
    6. EXITS,
    7. DELTE,
    8. };
    9. template<class K,class V>
    10. struct HashData
    11. {
    12. pair _kv;
    13. State _state = EMPTY;//默认状态给空
    14. };
    15. template<class K,class V>
    16. class HashTable
    17. {
    18. public://待写函数
    19. private:
    20. //HashData* _table;
    21. //size_t _size;
    22. //size_t _capacity; //用于增容
    23. vector _table;
    24. size_t _n;//存储的有效数据的个数
    25. };
    26. }
    1. bool Insert(const pair& kv)
    2. {
    3. HashData* ret = Find(kv, first);
    4. if (ret)
    5. {
    6. return false;
    7. }
    8. //判断是否满
    9. //计算负载因子,大于0.7就增容
    10. //if (_n*10 / _table.size() > 7)
    11. if (_tabale.size() == 0)
    12. {
    13. _table.resize(10);
    14. }
    15. else if ((double)_n / (double)_table.size() > 0.7)
    16. {
    17. //方法一,不足之处在于要重复写插入逻辑
    18. //vectornewtable;
    19. //newtable.resize(_table.size*2); 新表增容,增到旧表的二倍
    20. //for (auto& e : _table)
    21. //{
    22. // if (e._table == EXITS)
    23. // {
    24. // //重新计算放到newtable中去
    25. // //跟下面插入逻辑类似
    26. // }
    27. //}
    28. //_table.swap(newtable);
    29. HashTablenewHT;
    30. newHT._table.resize(_table.size() * 2);
    31. for (auto& e : _table)
    32. {
    33. if (e._state == EXITS)
    34. {
    35. newHT.Insert(e._kv);
    36. }
    37. }
    38. _table.swap(newHT._table);
    39. }
    40. size_t start = kv.first % _table.size();//不能%_table.capacity()
    41. size_t index = start;
    42. //探测后面的位置 -- 线性探测或二次探测
    43. size_t i = 1;
    44. while (_table[index]._state == EXITS)
    45. {
    46. index += start + i;//此处的线性探测想改成二次探测将i改为i*i即可
    47. index %= _table.size();
    48. ++i;
    49. }
    50. _table[index]._kv = kv;
    51. _table[index]._state = EXITS;
    52. ++_n;
    53. return true;
    54. }
    55. HashData* Find(const K& key)
    56. {
    57. //排除0的情况
    58. if (_table.size() == 0)
    59. {
    60. return nullptr;
    61. }
    62. size_t start = key % _table.size();
    63. size_t index = start;
    64. size_t i = 1;
    65. while (_table[index]._state != EMPTY)
    66. {
    67. if (_table[index]._state == EXITS//避免已经删除的元素仍可被寻找的情况
    68. && _table[index]._kv.first == key)
    69. {
    70. return &_table[index];
    71. }
    72. inded = start + i;
    73. index %= _table.size();
    74. ++i;
    75. }
    76. return nullptr;
    77. }

    4.Erase()

    1. bool Erase(const K& key)
    2. {
    3. HashData* ret = Find(key);
    4. if (ret == nullptr)
    5. {
    6. return false;
    7. }
    8. else
    9. {
    10. ret->_state = DELETE;
    11. return true;
    12. }
    13. }

    5.string为参数的情况

    若出现下面测试代码的情况,如何更改?

    1. void TestHashTable2()
    2. {
    3. string a[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "橘子", "苹果" };
    4. HashTableint> ht;
    5. for (auto str : a)//string作k的情况
    6. {
    7. auto ret = ht.Find(str);
    8. if (ret)
    9. {
    10. ret->_kv.second++;
    11. }
    12. else
    13. {
    14. ht.Insert(make_pair(str, 1));
    15. }
    16. }
    17. }

    所有要取模的地方添加仿函数,该仿函数的功能就是将数据转换成整型。

    1. struct IniHashFunc
    2. {
    3. int operator()(int i)
    4. {
    5. return i;
    6. }
    7. };

     

    特别的,将字符串转换成整型的仿函数仍存在问题。采用字符串哈希算法的思路改正即可    各种字符串Hash函数 - clq - 博客园

    以此类推,K为不同类型元素的情况写对应的仿函数即可

    思考:

     一个类型去作map/set的Key有什么要求? 能支持比较大小

    一个类型去作unordered_map/unordered_set的Key有什么要求?  能支持转换成整型+相等比较

    6.添加类模板 

    可以转化成整型的数据采用下面模板,返回值为int

    1. template<class K>
    2. struct Hash
    3. {
    4. size_t operator()(const K& key)
    5. {
    6. return key;
    7. }
    8. };

    当K是string时不会走原生的模板,采用特化单独创建一个模板

    1. template<>
    2. struct Hash
    3. {
    4. //字符串转成对应的整型,整型才能取模算映射位置
    5. //期望->字符串不同,转出的整型值尽量不同
    6. size_t operator()(const string& s)
    7. {
    8. //return s[0]; 存在int insert类似首字母相同的情况
    9. //存在abcd,bcad或abbb.abca的情况(ascii码和相同的情况)
    10. size_t value = 0;
    11. for (auto ch : s)
    12. {
    13. value += ch;
    14. value *= 131;//采用BKDRHash算法的思想 乘以131
    15. }
    16. return value;
    17. }
    18. };

    使用类模板后不同的数据类型会调用不同类型的仿函数 

     此时可以省略仿函数,参数自动使用隐式类型转换

     

    7.整体代码

    1. #pragma once
    2. #include
    3. #include
    4. using namespace std;
    5. namespace CloseHash
    6. {
    7. enum State
    8. {
    9. EMPTY,
    10. EXITS,
    11. DELETE,
    12. };
    13. template<class K, class V>
    14. struct HashData
    15. {
    16. pair _kv;
    17. State _state = EMPTY; // 状态
    18. };
    19. template<class K>
    20. struct Hash
    21. {
    22. size_t operator()(const K& key)
    23. {
    24. return key;
    25. }
    26. };
    27. // 特化
    28. template<>
    29. struct Hash
    30. {
    31. // "int" "insert"
    32. // 字符串转成对应一个整形值,因为整形才能取模算映射位置
    33. // 期望->字符串不同,转出的整形值尽量不同
    34. // "abcd" "bcad"
    35. // "abbb" "abca"
    36. size_t operator()(const string& s)
    37. {
    38. // BKDR Hash
    39. size_t value = 0;
    40. for (auto ch : s)
    41. {
    42. value += ch;
    43. value *= 131;
    44. }
    45. return value;
    46. }
    47. };
    48. template<class K, class V, class HashFunc = Hash>
    49. class HashTable
    50. {
    51. public:
    52. bool Insert(const pair& kv)
    53. {
    54. HashData* ret = Find(kv.first);
    55. if (ret)
    56. {
    57. return false;
    58. }
    59. // 负载因子大于0.7,就增容
    60. //if (_n*10 / _table.size() > 7)
    61. if (_table.size() == 0)
    62. {
    63. _table.resize(10);
    64. }
    65. else if ((double)_n / (double)_table.size() > 0.7)
    66. {
    67. //vector newtable;
    68. // newtable.resize(_table.size*2);
    69. //for (auto& e : _table)
    70. //{
    71. // if (e._state == EXITS)
    72. // {
    73. // // 重新计算放到newtable
    74. // // ...跟下面插入逻辑类似
    75. // }
    76. //}
    77. //_table.swap(newtable);
    78. HashTable newHT;
    79. newHT._table.resize(_table.size() * 2);
    80. for (auto& e : _table)
    81. {
    82. if (e._state == EXITS)
    83. {
    84. newHT.Insert(e._kv);
    85. }
    86. }
    87. _table.swap(newHT._table);
    88. }
    89. HashFunc hf;
    90. size_t start = hf(kv.first) % _table.size();
    91. size_t index = start;
    92. // 探测后面的位置 -- 线性探测 or 二次探测
    93. size_t i = 1;
    94. while (_table[index]._state == EXITS)
    95. {
    96. index = start + i;
    97. index %= _table.size();
    98. ++i;
    99. }
    100. _table[index]._kv = kv;
    101. _table[index]._state = EXITS;
    102. ++_n;
    103. return true;
    104. }
    105. HashData* Find(const K& key)
    106. {
    107. if (_table.size() == 0)
    108. {
    109. return nullptr;
    110. }
    111. HashFunc hf;
    112. size_t start = hf(key) % _table.size();
    113. size_t index = start;
    114. size_t i = 1;
    115. while (_table[index]._state != EMPTY)
    116. {
    117. if (_table[index]._state == EXITS
    118. && _table[index]._kv.first == key)
    119. {
    120. return &_table[index];
    121. }
    122. index = start + i;
    123. index %= _table.size();
    124. ++i;
    125. }
    126. return nullptr;
    127. }
    128. bool Erase(const K& key)
    129. {
    130. HashData* ret = Find(key);
    131. if (ret == nullptr)
    132. {
    133. return false;
    134. }
    135. else
    136. {
    137. ret->_state = DELETE;
    138. return true;
    139. }
    140. }
    141. private:
    142. /* HashData* _table;
    143. size_t _size;
    144. size_t _capacity;*/
    145. vector> _table;
    146. size_t _n = 0; // 存储有效数据的个数
    147. };
    148. void TestHashTable1()
    149. {
    150. int a[] = { 1, 5, 10, 100000, 100, 18, 15, 7, 40};
    151. HashTable<int, int> ht;
    152. for (auto e : a)
    153. {
    154. ht.Insert(make_pair(e, e));
    155. }
    156. auto ret = ht.Find(100);
    157. if (ret)
    158. {
    159. cout << "找到了"<
    160. }
    161. else
    162. {
    163. cout << "没有找到了" << endl;
    164. }
    165. ht.Erase(100);
    166. ret = ht.Find(100);
    167. if (ret)
    168. {
    169. cout << "找到了" << endl;
    170. }
    171. else
    172. {
    173. cout << "没有找到了" << endl;
    174. }
    175. }
    176. struct stringHashFunc
    177. {
    178. // "int" "insert"
    179. // 字符串转成对应一个整形值,因为整形才能取模算映射位置
    180. // 期望->字符串不同,转出的整形值尽量不同
    181. // "abcd" "bcad"
    182. // "abbb" "abca"
    183. size_t operator()(const string& s)
    184. {
    185. // BKDR Hash
    186. size_t value = 0;
    187. for (auto ch : s)
    188. {
    189. value += ch;
    190. value *= 131;
    191. }
    192. return value;
    193. }
    194. };
    195. void TestHashTable2()
    196. {
    197. string a[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "橘子", "苹果" };
    198. HashTableint> ht;
    199. for (auto str : a)
    200. {
    201. auto ret = ht.Find(str);
    202. if (ret)
    203. {
    204. ret->_kv.second++;
    205. }
    206. else
    207. {
    208. ht.Insert(make_pair(str, 1));
    209. }
    210. }
    211. }
    212. struct Student
    213. {
    214. // ...
    215. };
    216. struct StudentHashFunc
    217. {
    218. size_t operator()(const Student& kv)
    219. {
    220. // 如果是结构体
    221. // 1、比如说结构体中有一个整形,基本是唯一值 - 学号
    222. // 2、比如说结构体中有一个字符串,基本是唯一值 - 身份证号
    223. // 3、如果没有一项是唯一值,可以考虑多项组合
    224. size_t value = 0;
    225. // ...
    226. return value;
    227. }
    228. };
    229. void TestHashTable3()
    230. {
    231. string a[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "橘子", "苹果" };
    232. // 任意类型都可以做key,跟上一个把这个类型对象转换成整形的仿函数即可
    233. HashTableint, StudentHashFunc> ht;
    234. }
    235. void TestStringHashFunc()
    236. {
    237. stringHashFunc hf;
    238. cout << hf("insert")<
    239. cout << hf("int") << endl<
    240. cout << hf("abcd") << endl;
    241. cout << hf("bacd") << endl << endl;
    242. cout << hf("abbb") << endl;
    243. cout << hf("abca") << endl << endl;
    244. }
    245. }

  • 相关阅读:
    Elasticsearch 入门 索引、分词器
    六大银行数据治理现状盘点:治理架构、数据标准与数据平台
    使用TensorRT 和 Triton 在Jetson NX上的模型部署
    hypermesh 圆周阵列-插件
    竞赛选题 基于机器视觉的停车位识别检测
    从 HTA 中启动应用程序
    大火的4D Radar数据集及基线模型汇总
    业主方怎么管理固定资产
    Markdown 表格
    学会用数据分析汇报工作,升职加薪指日可待
  • 原文地址:https://blog.csdn.net/m0_61548909/article/details/126591355