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

【C++】哈希表模拟:开散列技术与哈希冲突处理

在这里插入图片描述

C++语法相关知识点可以通过点击以下链接进行学习一起加油!
命名空间缺省参数与函数重载C++相关特性类和对象-上篇类和对象-中篇
类和对象-下篇日期类C/C++内存管理模板初阶String使用
String模拟实现Vector使用及其模拟实现List使用及其模拟实现容器适配器Stack与QueuePriority Queue与仿函数
模板进阶-模板特化面向对象三大特性-继承机制面向对象三大特性-多态机制STL 树形结构容器二叉搜索树
AVL树红黑树红黑树封装map/set哈希-开篇闭散列-模拟实现哈希

在上篇中,我们使用闭散列技术解决了哈希冲突并实现了哈希表。然而,我们发现闭散列并不理想,因此本篇将探讨如何通过开散列方法来处理哈希冲突。

请添加图片描述
Alt
🌈个人主页:是店小二呀
🌈C语言专栏:C语言
🌈C++专栏: C++
🌈初阶数据结构专栏: 初阶数据结构
🌈高阶数据结构专栏: 高阶数据结构
🌈Linux专栏: Linux

🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅 请添加图片描述

文章目录

  • 一、开散列(哈希桶)
  • 二、实现哈希表
    • 2.1 哈希表基本构架
    • 2.2 哈希表插入数据
    • 2.3 哈希表析构函数
    • 2.4 哈希表扩容
    • 2.5 哈希表删除数据
    • 2.6 哈希桶中查找
  • 三、HashTable.h

一、开散列(哈希桶)

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

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

二、实现哈希表

2.1 哈希表基本构架

template<class K>
    struct HashFunc
    {
        size_t operator()(const K& key)
        {
            return (size_t)key;
        }
    };

template<>
struct HashFunc<string>
{
    size_t operator()(const string& key)
    {
        size_t hash = 0;
        for (auto ch : key)
        {
            hash *= 131;
            hash += ch;
        }
        return hash;
    }
};
namespace hash_buck
{
    template<class K,class V>
        struct HashNode
        {
            pair<K, V> _kv;
            HashNode<K, V> _next;
        };

    template<class K, class V,class Hash = HashFunc<K>>
        class HashTable
        {
            public:
            typedef HashNode<K, V> Node;
            HashTable()
            {
                _tables.resize(10nullptr);
            }

            private:
            vector<Node*> _tables;
            //vector<list<..>> _tables;
            size_t _n = 0;
        };
}

这里如果想实现哈希桶结构,可以使用一个个节点连接起来或者直接套上一个链表容器。这里为了更好地实现迭代器,我们选择第一种方式,第二种方式实现迭代器有一丝麻烦。

开散列中每个桶中放的都是发生哈希冲突的元素 ,并没有顺序之分。

2.2 哈希表插入数据

在这里插入图片描述

bool Insert(const pair<K, V>& kv)
{
    Hash hs;
    size_t hashi = hs(kv.first) % _tables.size();

    //进行头插操作
    Node* newnode = new Node(kv);
    _tables[hashi]->_next = newnode;
    _tables[hashi] = newnode;
}

这里链表节点只需要单纯地存储数据,那么只需要设计单链表即可,没有必要设双向链表。对于单链表插入数据,一般推荐采用头插,由于尾插需要找尾比较麻烦。

2.3 哈希表析构函数

这里涉及堆上空间资源的开辟,一般需要涉及析构函数进行资源处理。

由于vector容器元素为内置类型,析构函数对内置类型不进行处理,那么就导致指向空间没有得到释放,需要显式写析构函数,完成资源释放。

~HashTable()
{
    for (size_t i = 0; i < _tables.size(); i++)
    {
        Node* cur = _tables[i];
        while (cur)
        {
            Node* next = cur->_next;
            delete cur;
            cur = next;
        }

    }
}

2.4 哈希表扩容

由于哈希桶去解决哈希冲突对于哈希表空间需要相对比较小,不同于开发定址法去解决哈希冲突占用表内空间,而是表中一个指针指向一个空间。

负载因子满足1即可扩容】:_n == _tables.size()

第一个方案】:复用Insert完成CV操作

//扩容逻辑
    if (_n == _tables.size())
    {
        //这里可以忽略类型
        HashTable NewTable;
        NewTable._tables.resize(n * 10);

        //CV工作
        for (size_t i = 0; i < _tables.size(); i++)
        {
            Node* cur = _tables[i];
            while (cur)
            {
                NewTable.Insert(_tables[i]._kv);
                cur = cur->_next;
            }
        }
        _tables.swap(NewTable);
    }

缺陷】:如果存在一万个节点,意味着需要复制一万个节点又要释放一万个节点,显得很浪费

第二个方案】:直接将节点拿下来

在这里插入图片描述

if (_n == _tables.size())
{
    vector<Node*> NewTable(n * 10, nullptr);

    for (size_t i = 0; i < _tables.size(); i++)
    {
        Node* cur = _tables[i];
        while (cur)
        {
            Node* next = cur->_next;

            cur->_next = _tables[i];
            _tables[i] = cur;
            cur = cur->_next;
        }
        _tables[i] = nullptr;
    }
    _tables.swap(NewTable);
}

关于这段代码是比较难懂,每个指针指向一块空间,先cur->next = newTables[hashi]抓住新表的位置,newTables[hashi] = cur新表将旧表节点拿下来,可以好好理解拿下来的逻辑,得到了这个地址,这个地址指向一块节点。

这里会导致newTables[hashi]_tables[i]共同指向一块空间,虽然不会去使用旧表去影响到新表,为了保险起见,可以将旧表这块位置指针置空。

2.5 哈希表删除数据

bool Erase(const K& key)
{
    Hash hs;
    size_t hashi = hs(kv.first) % _tables.size();

    Node* cur = _tables[hashi];
    Node* prev = nullptr;

    while (cur)
    {
        if (cur->_kv.first == key)
        {
            if (prev == nullptr)
            {
                _tables[hashi] = cur->_next;
            }
            else
            {
                prev->_next = cur->_next;
            }
            delete cur;

            return true;
        }
        else
        {
            prev = cur;
            cur = cur->_next;
        }
    }
    return false;
}

这里删除数据,无非需要考虑两种情况的删除,一种是删除第一个节点,另一种是删除其他节点prev->_next = cur->_next。在删除节点需要前后兼顾,保存下前驱指针指向节点。

2.6 哈希桶中查找

Node* Find(const K& key)
{
    Hash hs;
    size_t hashi = hs(kv.first) % _tables.size();
    Node* cur = _tables[hashi];
    while (cur)
    {
        if (cur->_kv.first == key)
        {
            return &cur->_kv.first;
        }
        else
        {
            cur = cur->_next;
        }
    }
    return nullptr;
}

三、HashTable.h

template<class K>
    struct HashFunc
    {
        size_t operator()(const K& key)
        {
            return (size_t)key;
        }
    };

template<>
struct HashFunc<string>
{
    size_t operator()(const string& key)
    {
        size_t hash = 0;
        for (auto ch : key)
        {
            hash *= 131;
            hash += ch;
        }
        return hash;
    }
};

namespace hash_bucket
{
    template<class K,class V>
        struct HashNode
        {
            pair<K, V> _kv;
            HashNode<K, V> _next;
        };

    template<class K, class V,class Hash = HashFunc<K>>
        class HashTable
        {
            public:
            typedef HashNode<K, V> Node;
            HashTable()
            {
                _tables.resize(10,nullptr);

            }

            ~HashTable()
            {
                for (size_t i = 0; i < _tables.size(); i++)
                {
                    Node* cur = _tables[i];
                    while (cur)
                    {
                        Node* next = cur->_next;
                        delete cur;
                        cur = next;
                    }

                }
            }
            bool Insert(const pair<K, V>& kv)
            {
                Hash hs;
                size_t hashi = hs(kv.first) % _tables.size();

                //扩容逻辑
                //if (_n == _tables.size())
                //{
                //	//这里可以忽略类型
                //	HashTable NewTable;
                //	NewTable._tables.resize(n * 10);
                //	
                //	//CV工作
                //	for (size_t i = 0; i < _tables.size(); i++)
                //	{
                //		Node* cur = _tables[i];
                //		while (cur)
                //		{
                //			NewTable.Insert(_tables[i]._kv);
                //			cur = cur->_next;
                //		}
                //	}
                //	_tables.swap(NewTable);
                //}

                if (_n == _tables.size())
                {
                    vector<Node*> NewTable(n * 10, nullptr);

                    for (size_t i = 0; i < _tables.size(); i++)
                    {
                        Node* cur = _tables[i];
                        while (cur)
                        {
                            Node* next = cur->_next;
                            // 头插新表的位置
                            size_t hashi = hs(kot(cur->_data)) % NewTable.size();
                            cur->_next = NewTable[hashi];
                            NewTable[hashi] = cur;

                            cur = cur->_next;
                        }
                        _tables[i] = nullptr;
                    }
                    _tables.swap(NewTable);
                }

                //进行头插操作
                Node* newnode = new Node(kv);
                _tables[hashi]->_next = newnode;
                _tables[hashi] = newnode;
                ++_n;
            }

            Node* Find(const K& key)
            {
                Hash hs;
                size_t hashi = hs(kv.first) % _tables.size();
                Node* cur = _tables[hashi];
                while (cur)
                {
                    if (cur->_kv.first == key)
                    {
                        return &cur->_kv.first;
                    }
                    else
                    {
                        cur = cur->_next;
                    }
                }
                return nullptr;
            }

            bool Erase(const K& key)
            {
                Hash hs;
                size_t hashi = hs(kv.first) % _tables.size();

                Node* cur = _tables[hashi];
                Node* prev = nullptr;

                while (cur)
                {
                    if (cur->_kv.first == key)
                    {
                        if (prev == nullptr)
                        {
                            _tables[hashi] = cur->_next;
                        }
                        else
                        {
                            prev->_next = cur->_next;
                        }
                        delete cur;

                        return true;
                    }
                    else
                    {
                        prev = cur;
                        cur = cur->_next;
                    }
                }
                return false;
            }
            private:
            vector<Node*> _tables;
            size_t _n = 0;
        };
}

在这里插入图片描述

以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!


http://www.kler.cn/a/385943.html

相关文章:

  • Jav项目实战II基于微信小程序的助农扶贫的设计与实现(开发文档+数据库+源码)
  • 查询DBA_FREE_SPACE缓慢问题
  • Mac终端字体高亮、提示插件
  • Rust学习(五):泛型、trait
  • Java集合(Collection+Map)
  • AlphaFold3中文使用说明
  • 这是一个bug求助帖子--安装kali 遇坑
  • 【青牛科技】GC5931:工业风扇驱动芯片的卓越替代者
  • Stable Diffusion的解读(一)
  • CS61b part5
  • 【C++】C++的单例模式、跟踪内存分配的简单方法
  • U3D游戏开发之骨骼动画相关
  • Linux内核USB2.0驱动框架分析--USB设备枚举过程
  • 有功功率,无功功率,视在功率,额定功率关系
  • TSMI252012PMX-2R2MT电子元器件详解
  • 理想汽车Android面试题及参考答案
  • 【Linux内核】trigger_load_balance(struct rq *rq)内核函数
  • C 语言学习-05【数组】
  • multiprocessing
  • [MySQL]索引
  • RK3568平台开发系列讲解(字符设备驱动篇)创建设备节点实验
  • ENAS和DARTs的比较
  • 1、了解家庭网络历史
  • InfluxDB性能优化指南
  • 用 Collections.synchronizedSet 创建线程安全的 HashSet
  • 健尔康在A股上市:市值84亿元,陈国平、陈麒宇父女成大赢家