【C++】哈希——unordered系列容器&哈希概念&哈希冲突(下)

简介: 【C++】哈希——unordered系列容器&哈希概念&哈希冲突(下)

闭散列二次探测的哈希表代码实现插入删除查找

实际上,线性探测也是具有一定的缺陷的,线性探测的缺陷就是会将产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为image.png

其中:i=1,2,3...,H0是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小。

那么,对于2.1中如果要插入44,产生冲突,使用解决后的情况为

5739f252583296fa362baa490091fbc8.png

由于闭散列不管使用什么探测方式,都是治标不治本,所以这里就不再继续代码实现二次探测了,下面我们看一看开散列的实现方式


5.2 开散列

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

54071a3ab2a0c28089ba4eacfb18f70e.png

6502dabbf0a37003b17a1123088b7d88.png

从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素

开散列哈希表代码实现插入删除查找


1、首先由于结构的原因,要把数据链接起来需要一个节点类:

template<class K, class V>
struct HashNode
{
    std::pair<K, V> _kv;
    HashNode* _next;
    HashNode(const std::pair<K, V>& kv)
        :_kv(kv)
            ,_next(nullptr)
        {}
};


2、同样的,由于KV结构中的key是一个泛型,当我们在进行哈希映射的时候,需要先让其映射成为整型,然后才能够映射到哈希地址,所以这里提供一个仿函数,用于将key映射到整型家族

template<class K>
struct HashFunc
{
    size_t operator()(const K& key)
    {
        return (size_t)key;
    }
};
template<>//模板的特化
struct HashFunc<std::string>
{
    size_t operator()(const std::string& key)
    {
        size_t hash = 0;
        for (auto ch : key)
        {
            hash *= 131;
            hash += ch;
        }
        return hash;
    }
};


3、开散列的哈希表结构

template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
    typedef HashNode<K, V> Node;
public:
    HashTable()
        :_n(0)
        {
            _tables.resize(10);
        }
private:
    std::vector<Node*> _tables;//本质上是一个指针数组,存放节点指针。
    size_t _n = 0;
};


4、插入

bool Insert(const std::pair<K,V>& kv)
{
    if(Find(kv.first))
        return false;
    //扩容
    //负载因子为1的时候就扩容
    if(_n == _tables.size())
    {
        //扩容方式有两种,一种是遍历,然后创建新节点挂在新表上
        //由于方案一造成的消耗较大,所以这里就不实现了
        //                HashTable<K, V, Hash> newTable;
        //                newTable._tables.resize(2 * _tables.size());
        //另一种是直接更改节点的指向
        std::vector<Node*> newTables;
        newTables.resize(2* _tables.size(), nullptr);//这里暂时使用2倍的方式扩容
        for(size_t i = 0; i < _tables.size(); ++i)//遍历旧表,依次拿到每个桶的头节点
        {
            Node* cur = _tables[i];
            while(cur)
            {
                Node* next = cur->_next;//先使用一个指针保存next,以免更改cur指向之后找不到其他节点
                size_t hashi = Hash()(cur->_kv.first) % newTables.size();//计算哈希位置
                //头插到新表中
                cur->_next = newTables[hashi];
                newTables[hashi] = cur;
                cur = next;//迭代到next
            }
            _tables[i] = nullptr;//将旧表的内容置空,以免出现自动析构旧表的时候释放节点
        }
        _tables.swap(newTables);//交换旧表和新表
    }
    //插入
    size_t hashi = Hash()(kv.first) % _tables.size();//定址
    //头插
    Node* newnode = new Node(kv);
    newnode->_next = _tables[hashi];
    _tables[hashi] = newnode;
    ++_n;
    return true;
}


删除

bool Erase(const K& key)
{
    size_t hashi = Hash()(key) % _tables.size();
    Node* prev = nullptr;//用prev存放当前节点的上一个节点,从而链接cur的前后节点
    Node* cur = _tables[hashi];
    while(cur)
    {
        if(cur->_kv.first == key)//找到了,准备删除
        {
            if(_tables[hashi] == cur)//删除桶的头节点
            {
                _tables[hashi] = cur->_next;
            }
            else//删除非头节点
            {
                prev->_next = cur->_next;
            }
            delete cur;
            --_n;
            return true;
        }
        else//没找到
        {
            prev = cur;
            cur = cur->_next;
        }
    }
    return false;
}


查找

Node* Find(const K& key)
{
    size_t hashi = Hash()(key) % _tables.size();//找到key对应的哈希地址
    Node* cur = _tables[hashi];//遍历该地址对应的哈希桶
    while(cur)
    {
        if(cur->_kv.first == key)//找到了
        {
            return cur;
        }
        else//没找到
        {
            cur = cur->_next;
        }
    }
    return nullptr;
}


本节完……

相关文章
【C++】unordered系列
`unordered`容器是C++11及其后续版本中STL的一部分,包括`unordered_map`和`unordered_set`,基于哈希表实现,支持快速查找、插入和删除操作。`unordered_map`存储键值对,`unordered_set`存储唯一元素,两者均提供高效的存取性能,特别适合处理大数据量的应用场景。这些容器通过哈希函数将数据映射到特定位置,虽然存在哈希冲突问题,但可通过开放定址法、链地址法等策略有效解决。
142 3
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
《docker高级篇(大厂进阶):5.Docker-compose容器编排》包括是什么能干嘛去哪下、Compose核心概念、Compose使用三个步骤、Compose常用命令、Compose编排微服务
《docker高级篇(大厂进阶):5.Docker-compose容器编排》包括是什么能干嘛去哪下、Compose核心概念、Compose使用三个步骤、Compose常用命令、Compose编排微服务
421 24
《docker高级篇(大厂进阶):5.Docker-compose容器编排》包括是什么能干嘛去哪下、Compose核心概念、Compose使用三个步骤、Compose常用命令、Compose编排微服务
《docker高级篇(大厂进阶):5.Docker-compose容器编排》包括是什么能干嘛去哪下、Compose核心概念、Compose使用三个步骤、Compose常用命令、Compose编排微服务
452 6
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
167 2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++】优先级队列(容器适配器)
本文介绍了C++ STL中的线性容器及其适配器,包括栈、队列和优先队列的设计与实现。详细解析了`deque`的特点和存储结构,以及如何利用`deque`实现栈、队列和优先队列。通过自定义命名空间和类模板,展示了如何模拟实现这些容器适配器,重点讲解了优先队列的内部机制,如堆的构建与维护方法。
123 0
|
9月前
|
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器1
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
153 5
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
168 2
【C++】C++ STL探索:容器适配器 Stack 与 Queue 的使用及模拟实现(二)
【C++】C++ STL探索:容器适配器 Stack 与 Queue 的使用及模拟实现
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问