• 【数据结构】哈希桶


    目录

    前言:

    开散列(哈希桶)

    开散列的概念

    哈希桶的模拟实现

     整体框架

    查找

    插入

    删除

    析构函数


    前言:

    闭散列线性探测缺点:一旦发生哈希冲突,所有的产生哈希冲突的数据连续存储在一块区域,容易产生数据"堆积",即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低,并且闭散列导致空间利用率低,因此本文探索采用开散列(哈希桶)的数据结构从而避免数据 "堆积" ;

    开散列(哈希桶)

    开散列的概念

    开散列法又叫链地址法(拉链法),首先对关键码集合用哈希函数计算哈希地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链

    接起来,各链表的头结点存储在哈希表中;

    哈希桶的模拟实现

     整体框架

    1. template<class K, class V>
    2. struct HashNode
    3. {
    4. HashNode* _next;
    5. pair _kv;
    6. //开辟结点时需要结点的构造函数
    7. HashNode(const pair& kv)
    8. {
    9. _kv = kv;
    10. _next = nullptr;
    11. }
    12. };
    13. template<class K, class V>
    14. class HashTable
    15. {
    16. typedef HashNode Node;
    17. public:
    18. //...
    19. private:
    20. vector _tables;
    21. size_t _n;//记录哈希表中实际存放的数据个数
    22. };

    查找

    思路:

    首先根据键值key使用哈希函数计算哈希地址,确定待查找的数据的位置;

    其次遍历桶中数据,查找到返回数据所在结点的指针,查找不到返回空指针;

    1. Node* Find(const K& key)
    2. {
    3. size_t hashi = key % _tables.size();
    4. Node* cur = _tables[hashi];
    5. while (cur != nullptr)
    6. {
    7. if ((cur->_kv).first == key)
    8. {
    9. return cur;
    10. }
    11. cur = cur->_next;
    12. }
    13. return nullptr;
    14. }

    插入

    开散列最优情形:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,考虑哈希表扩容

    • 查看哈希表中是否存在该键值的键值对,若已存在则插入失败;
    • 当负载因子增加到1时,进行扩容操作(即创建一个新的哈希表,该哈希表的大小为原哈希表的两倍,然后遍历原哈希表,将原哈希表中的结点插入到新哈希表,最后将原哈希表与新哈希表交换即可);
    • 将结点插入哈希桶(头插);
    • 哈希表中的记录实际存储的数据个数自增1;
    1. //构造函数
    2. HashTable(size_t n = 10)
    3. {
    4. _tables.resize(n, nullptr);
    5. _n = 0;
    6. }
    1. bool Insert(const pair& kv)
    2. {
    3. //若键值对中的键值key已存在,则插入失败
    4. Node* ret = Find(kv.first);
    5. if (ret != nullptr)
    6. {
    7. return false;
    8. }
    9. //控制负载因子为1,即哈希表中实际存放的数据个数与哈希表长度长度相等
    10. if (_n == _tables.size())
    11. {
    12. //新表扩容到旧表的两倍
    13. vector _newtables(_tables.size() * 2);
    14. //遍历旧表,取旧表结点头插到新表
    15. for (size_t i = 0; i < _tables.size(); i++)
    16. {
    17. Node* cur = _tables[i];
    18. while (cur != nullptr)
    19. {
    20. Node* nextnode = cur->_next;
    21. size_t hashi = cur->_kv.first % _newtables.size();
    22. cur->_next = _newtables[hashi];
    23. _newtables[hashi] = cur;
    24. cur = nextnode;
    25. }
    26. _tables[i] = nullptr;
    27. }
    28. //交换新表与旧表
    29. _tables.swap(_newtables);
    30. }
    31. //插入
    32. //计算插入位置
    33. size_t hashi = kv.first % _tables.size();
    34. Node* newnode = new Node(kv);
    35. //单链表头插
    36. newnode->_next = _tables[hashi];
    37. _tables[hashi] = newnode;
    38. ++_n;
    39. return true;
    40. }

    删除

    1. bool Erase(const K& key)
    2. {
    3. //确定待删除数据的位置
    4. size_t hashi = key % _tables.size();
    5. Node* cur = _tables[hashi];
    6. Node* prev = nullptr;
    7. while (cur != nullptr)
    8. {
    9. if ((cur->_kv).first == key)
    10. {
    11. //删除
    12. if (prev != nullptr)
    13. {
    14. prev->_next = cur->_next;
    15. }
    16. else
    17. {
    18. _tables[hashi] = cur->_next;
    19. }
    20. delete cur;
    21. --_n;
    22. return true;
    23. }
    24. else
    25. {
    26. prev = cur;
    27. cur = cur->_next;
    28. }
    29. }
    30. return false;
    31. }

    析构函数

    1. //析构函数
    2. ~HashTable()
    3. {
    4. for (size_t i = 0; i < _tables.size(); i++)
    5. {
    6. Node* cur = _tables[i];
    7. while (cur != nullptr)
    8. {
    9. Node* next = cur->_next;
    10. delete cur;
    11. cur = next;
    12. }
    13. _tables[i] = nullptr;
    14. }
    15. }

    当哈希表中的HashNode存储string时,string类型的数据不支持取模运算,若采用运算符重载,需要更改库中的string,除留余数法如何建立string类型数据与存储位置的映射关系吗?

    解决方案:

    1. 首先将string类型转换为整型,建立string类型与整型的映射关系(采用仿函数实现);
    2. 其次转换后的整型值与存储位置建立映射关系(模版参数增加1个接收仿函数);

    思考:任意类型的值如何转换为整型值 ?

    • 本身为整型家族的成员,则可以通过强制类型转换可以转化为整型;
    • 对于string类型,可以取每个字符的ASCII码值,逐个相加且每相加一次乘以权重31;
    • 对于其他类型,按数据类型各自的特征自定义仿函数实现转换为整型;
    1. template<class K>
    2. struct HashFunc
    3. {
    4. size_t operator()(const K& key)
    5. {
    6. return (size_t)key;
    7. }
    8. };
    9. //string类型使用模版的特化
    10. template<>
    11. struct HashFunc
    12. {
    13. size_t operator()(const string& key)
    14. {
    15. size_t hash = 0;
    16. for (auto e : key)
    17. {
    18. hash += e;
    19. hash *= 31;//BKDR字符串哈希算法,累乘因子为31
    20. }
    21. return hash;
    22. }
    23. };
    1. template<class K, class V, class Hash = HashFunc>
    2. class HashTable
    3. {
    4. typedef HashNode Node;
    5. public:
    6. //...
    7. private:
    8. vector _tables;
    9. size_t _n;//记录哈希表中实际存放的数据个数
    10. };

    欢迎大家批评指正,博主会持续输出优质内容,谢谢各位观众老爷观看,码字画图不易,希望大家给个一键三连支持~ 你的支持是我创作的不竭动力~

  • 相关阅读:
    Kafka快速入门------kafka的基础知识
    抽象工厂模式
    基于SpringBoot的社团管理系统的设计与实现
    Git子模块使用说明
    ADW300物联网电力仪表-ONENET_STUDIO物模型方案操作说明
    JavaFX Scene Builder Miscellaneous 控件详解
    [激光原理与应用-25]:《激光原理与技术》-11- 控制技术-非线性技术之激光倍频、非线性晶体CLBO、BBO、LBO
    C语言中的自定义类型详解(结构体 + 枚举 + 联合(共用体))
    交换机/路由器的存储介质-华三
    在Web服务器(IIS)上安装SSL证书并绑定网站
  • 原文地址:https://blog.csdn.net/m0_58963318/article/details/138191818