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

数据结构(15)——哈希表(2)

欢迎来到博主的专栏:数据结构
博主ID:代码小豪

文章目录

    • 开散列式哈希表
      • 开散列式哈希表的结构
      • insert
    • find和erase
      • 析构函数

开散列式哈希表

前文提到,解决哈希冲突的方法分为闭散列和开散列,由于开散列的底层原理与闭散列的哈希表的底层原理不同,因此博主重新开一篇文章讲解。

开散列法又叫链地址法(开链法),首先对key值进行哈希函数计算出映射地址,具有相同地
址的key值归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链
接起来,各链表的头结点存储在哈希表中。

这样说好像有点抽象了,简单来说就是哈希表中存的不再是元素,而是一个指向链表的指针,这个链表存的是具有相同映射地址的key值。若某个地址不存在任何元素,则记为nullptr。
在这里插入图片描述

这种将多个相同映射值的数据装在一个集合的哈希结构也叫做哈希桶,即哈希表的每个元素都是一个桶,如果桶不存在任何元素,也就是nullptr。

开散列式哈希表的结构

关于哈希表的底层,博主仍然采用vector作为底层,根据上述的内容,该vector容器应该存指向链表头结点的指针,而节点应该存一个数据,还有指向下一个节点的指针。

节点的结构如下:

template<class T>
struct hash_Node
{hash_Node(const T& data):_data(data),_next(nullptr){}T _data;hash_Node<T>* _next;
};

哈希表的结构如下:

template<class key,class value>
class hash_tab
{
public:typedef pair<key, value> value_type;typedef hash_Node<value_type> Node;hash_tab(){_tab.resize(10);}
private:vector<Node<value_type>*> _tab;//哈希表size_t _n;//有效数据个数
};

insert

先使用哈希函数求出待插入元素的映射值,然后将节点头插在该位置。

插入方法如下:
(1)求出映射值
(2)在地址处将节点头插在对应的链表处
在这里插入图片描述
可以发现,开散列式的哈希表不需要线性探测,即使发生了哈希冲突,插入的效率依然是O(1)。插入的效率非常高。但是查找的效率却并非如此,比如在上例中查找34,需要遍历链表,因此插入的效率依然与负载因子挂钩,

负载因子=有效数据个数%哈希表的可容纳地址总数

由于开散列式哈希表空间利用率远高于闭散列,因此通常负载因子会控制在1以上。博主采用1作为负载因子的限制值。当负载因子超过1时,就要对哈希表进行扩容。

开散列式的哈希表依然采用异地扩容的战略,理由与闭散列式的哈希表一致:
哈希表扩容之后,key值的映射值也随之改变,因此需要重新插入。

异地扩容的方法如下:
(1)建立新表
(2)将旧表的内容移动到新表当中
(4)将旧表的所有指针置为nullptr
(3)交换新旧两表
在这里插入图片描述

bool insert(const value_type& val)
{if (_n / _tab.size() >= 1)//如果负载因子>=1就要扩容{hash_tab<key, value> newtab;newtab._tab.resize(2 * _tab.size());//2倍扩容for (int i = 0; i < _tab.size(); i++)//遍历旧表{Node* cur = _tab[i];while (cur != nullptr){Node* next = cur->_next;newtab.insert(cur);}_tab[i] = nullptr;}_tab.swap(newtab._tab);//交换新旧两表}size_t hashnum = val.first % _tab.size();Node* newnode = new Node(val);newnode->_next = _tab[hashnum];_tab[hashnum] = newnode;_n++;
}
privatebool insert(Node* newnode)//重载了一个插入节点的版本
{size_t hashnum = newnode->_data.first % _tab.size();newnode->_next = _tab[hashnum];_tab[hashnum] = newnode;_n++;return true;
}

为了支持插入节点,博主重载了一个insert函数,这个函数由于只用于将旧表的节点转移到新表,因此将接口隐藏起来。

find和erase

find函数的原理如下:

通过计算出key值的映射位置,在映射的桶中查找对应key值的数据,返回该节点。

Node* find(const key& key)
{size_t hashnum = key % _tab.size();Node* cur = _tab[hashnum];while (cur != nullptr){if (cur->_data.first == key){return cur;}cur = cur->_next;}return nullptr;
}

erase函数的原理如下:

通过find函数找到待删除的节点,然后删除该节点,删除节点的操作和链表删除节点的操作一致,博主不多赘述。

析构函数

由于开散列式哈希表的节点是开辟在堆区上的(new出来的),因此需要对这些数据进行销毁,否则就会导致内存泄漏。因此我们需要为哈希表设计一个析构函数。

析构函数的思路如下:
(1)遍历整个表的哈希桶
(2)将每个哈希桶内的数据释放
(3)释放后将哈希桶置为空桶。

~hash_tab()
{for (size_t i=0;i<_tab.size();i++){Node* cur = _tab[i];while (cur != nullptr){Node* next = cur->_next;delete cur;cur = next;}_tab[i] = nullptr;_n = 0;}
}

http://www.mrgr.cn/news/20589.html

相关文章:

  • pyro.optim pyro ppl 概率编程 优化器 pytorch
  • 《机器学习》—— PCA降维
  • pytorch对不同的可调参数,分配不同的学习率
  • c# Csv文件读写示例,如果文件存在追加写入
  • Word封面对齐技巧
  • 【PyQt6 应用程序】解说+原声视频混剪无显卡精简版,无显卡可用
  • 每日OJ_牛客_解读密码(简单模拟)
  • QT教程:start()和startTimer()的区别
  • 基于Java+SpringBoot+Vue的新闻稿件管理系统
  • 8. 如何在MyBatis中实现动态SQL?动态SQL有什么用?常见的动态SQL标签有哪些?
  • [数据集][目标检测]电梯内广告牌电动车检测数据集VOC+YOLO格式2787张4类别
  • 【CSS渐变】背景中的百分比:深入理解`linear-gradient`,进度条填充
  • s3c2440---PWM使用之蜂鸣器驱动移植
  • iOS——Block与内存管理
  • 单调队列(专项复习)
  • 2024年高教杯国赛(B题)数学建模竞赛解题思路|完整代码论文集合
  • 旅行商问题 | Matlab基于混合粒子群算法GA-PSO的旅行商问题TSP
  • Mysql运行原理
  • Java 入门指南:Java 并发编程 ——自定义 Java 线程池
  • Linux-(系统启动、用户管理)