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

c++哈希(开散列原理及实现)

我把c++哈希表上篇讲解放在这了,建议大家先看这篇。

前言

在上篇文章中我们详细介绍了,哈希的思想及实现原理,也认识到了闭散列最大的缺陷就是空间利用率比较低,容易这也是哈希的缺陷。怎么来解决这个问题呢?下面我们就来学习一种新的方法————————开散列(链地址法)


一、开散列概念

在前面我们讲的开放定址法,是通过哈希函数将关键码与存储位置建立映射关系,从而将关键码,存储在哈希表中,而这种方式,在数据量大的情况下很容易发生哈希冲突(哈希碰撞)。

在这里插入图片描述

为了解决这里的问题,有人提出了,开散列这种方法。
开散列:

当然这里也是利用了哈希映射,只是对底层存储结构做了改变。

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

通俗的讲,就是我们在闭散列使用的是,vector< int>来模拟哈希表,而使用它发生哈希冲突时我们就要,使用线性探测这种方法来解决它,这样太麻烦了效率还低,不如把哈希表中每个节点换成一个单链表(这里叫哈希桶),这样再发生哈希冲突,我们就可以直接在链表中插入,哈希冲突就很好的解决了。
在这里插入图片描述

二、开散列的实现(链地址法的实现)

2.1、哈希桶结点定义

我们想要利用单链表形式来存储关键码,首先要定义链表结点。
注:代码的注释可以帮助我们理解


	template<class K, class V>
	struct HashNode
	{
		HashNode* _next;//链接结点
		pair<K, V> _kv;//存储关键码

		HashNode(const pair<K, V>& kv)
			:_kv(kv)
			,_next(nullptr)
		{}
	};

2.2、哈希函数的实现

同闭散列一样,这里我们也需要哈希函数帮助我们建立,关键码与存储位置之间的关系。

 template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
template<>//,模板特化,应对string类型情况
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		// BKDR
		size_t hash = 0;
		for (auto e : key)
		{
			hash *= 31;//一种算法,可以有效降低字符串映射位置相同的概率
			hash += e;
		}
		cout << key << ":" << hash << endl;
		return hash;
	}
};

2.3、插入操作

通过哈希映射找到桶的位置,使用头插尾插都可(同单链表的插入操作)。

在进行插入操作时,如果哈希桶过长(单链表过长)会导致我们的查找效率变低,因此当插入一定量的数据时,我们仍需要进行扩容操作,这里我们也使用负载因此作为是否扩容的依据。

       bool Insert(const pair<K, V>& kv)
       {
           if (Find(kv.first))//查找防止插入重复
               return false;
           Hash hf;
           if (_n == _tables.size())
           {
               vector<Node*> newTables;
               newTables.resize(_tables.size() * 2, nullptr);
               // 遍历旧表
               for (size_t i = 0; i < _tables.size(); i++)
               {
                   Node* cur = _tables[i];
                   while (cur)
                   {
                       Node* next = cur->_next;
                       // 挪动到映射的新表
                       size_t hashi = hf(cur->_kv.first) % newTables.size();
                       cur->_next = newTables[hashi];
                       newTables[hashi] = cur;
                       cur = next;
                   }

                   _tables[i] = nullptr;
               }

               _tables.swap(newTables);
           }
           size_t hashi = hf(kv.first) % _tables.size();
           Node* newnode = new Node(kv);
           // 头插
           newnode->_next = _tables[hashi];
           _tables[hashi] = newnode;
           ++_n;
           return true;
       }
 }

2.4、查找操作

使用关键码(要查找值)映射出哈希值(也就是关键所在桶的序号),在对应桶中查找。

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

2.5、删除操作

同单链表删除相同

      bool Erase(const K& key)
      {
          Hash hf;
          size_t hashi = hf(key) % _tables.size();
          Node* prev = nullptr;
          Node* cur = _tables[hashi];
          while (cur)
          {
              if (cur->_kv.first == key)
              {
                  if (prev == nullptr)
                  {
                      _tables[hashi] = cur->_next;
                  }
                  else
                  {
                      prev->_next = cur->_next;
                  }
                  delete cur;

                  return true;
              }

              prev = cur;
              cur = cur->_next;
          }

          return false;
      }

3、全部代码

剩下的简单成员函数,就交给大家自己看吧

namespace hash_bucket
{
    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)
        {
            // BKDR
            size_t hash = 0;
            for (auto e : key)
            {
                hash *= 31;
                hash += e;
            }

            cout << key << ":" << hash << endl;
            return hash;
        }
    };
    template<class K, class V>
    struct HashNode
    {
        HashNode* _next;
        pair<K, V> _kv;

        HashNode(const pair<K, V>& kv)
            :_kv(kv)
            , _next(nullptr)
        {}
    };

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

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

        bool Insert(const pair<K, V>& kv)
        {
            if (Find(kv.first))
                return false;

            Hash hf;
            if (_n == _tables.size())
            {
                vector<Node*> newTables;
                newTables.resize(_tables.size() * 2, nullptr);
                // 遍历旧表
                for (size_t i = 0; i < _tables.size(); i++)
                {
                    Node* cur = _tables[i];
                    while (cur)
                    {
                        Node* next = cur->_next;

                        // 挪动到映射的新表
                        size_t hashi = hf(cur->_kv.first) % newTables.size();
                        cur->_next = newTables[hashi];
                        newTables[hashi] = cur;

                        cur = next;
                    }

                    _tables[i] = nullptr;
                }

                _tables.swap(newTables);
            }

            size_t hashi = hf(kv.first) % _tables.size();
            Node* newnode = new Node(kv);

            // 头插
            newnode->_next = _tables[hashi];
            _tables[hashi] = newnode;
            ++_n;

            return true;
        }

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

                cur = cur->_next;
            }

            return NULL;
        }

        bool Erase(const K& key)
        {
            Hash hf;
            size_t hashi = hf(key) % _tables.size();
            Node* prev = nullptr;
            Node* cur = _tables[hashi];
            while (cur)
            {
                if (cur->_kv.first == key)
                {
                    if (prev == nullptr)
                    {
                        _tables[hashi] = cur->_next;
                    }
                    else
                    {
                        prev->_next = cur->_next;
                    }
                    delete cur;

                    return true;
                }

                prev = cur;
                cur = cur->_next;
            }

            return false;
        }
    private:
        vector<Node*> _tables;//哈希表
        size_t _n = 0;
    };

总结

总的来说,这部分代码实现比较简单,主要时掌握哈希思想。


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

相关文章:

  • 曲面单值化定理
  • Pytorch使用手册- PyTorch 中 non_blocking 和 pin_memory() 的使用指南(专题十一)
  • docker启动容器,语句名词解释
  • Perforce SAST专家详解:自动驾驶汽车的安全与技术挑战,Klocwork、Helix QAC等静态代码分析成必备合规性工具
  • Springboot集成通义大模型
  • hdlbits系列verilog解答(Exams/m2014 q4a)-86
  • BUUCTF—Reverse—Java逆向解密(10)
  • 警钟长鸣,防微杜渐,遨游防爆手机如何护航安全生产?
  • Flink 离线计算
  • 【kafka02】消息队列与微服务之Kafka部署
  • 如何bug是前端还是后端
  • (即插即用模块-Attention部分) 二十、(2021) GAA 门控轴向注意力
  • 【Spring框架 二】
  • DimensionX 学习部署笔记
  • 大小写转换
  • Ubuntu 常用解压与压缩命令
  • 如何将WSL的虚拟机安装到任意目录中
  • Nginx和Apache有什么异同?
  • 关于NXP开源的MCU_boot的项目心得
  • Spring Boot 实战:分别基于 MyBatis 与 JdbcTemplate 的数据库操作方法实现与差异分析
  • 【QNX+Android虚拟化方案】125 - 如何创建android-spare镜像
  • 基于SpringBoot的欢迪迈手机商城架构设计
  • 从扩散模型开始的生成模型范式演变--SDE
  • AI/ML 基础知识与常用术语全解析
  • C# 数据类型详解:掌握数据类型及操作为高效编码奠定基础
  • 闪豆下载器(多平台视频批量下载器)v4.0