C++哈希表

目录

  • 介绍
    • 哈希概念
    • 哈希冲突
    • 哈希函数
    • 解决哈希冲突
  • 闭散列
    • 介绍
      • 线性探测
      • 二次探测
      • 负载因子
    • 实现
      • 哈希表结构
      • 哈希函数
      • 元素查找
      • 插入元素
      • 删除元素
  • 开散列
    • 介绍
    • 实现
      • 哈希表结构
      • 元素查找
      • 插入元素
      • 删除元素
      • 析构函数

介绍

哈希概念

    了解过搜索二叉树与红黑树后,它们的结构特点主要是为了进行快速查找, O ( l o g 2 N ) O(log_2N) O(log2N)的时间复杂度,通常在几次关键码的比较后就能找到目标元素。但是最最理想的搜索,是能够像数组那样,知道元素的下标,直接就可以访问,时间复杂度达到 O ( 1 ) O(1) O(1)

    其实哈希结构就是与数组类似的,通过元素的关键码计算出下标(哈希映射)。通过这一步计算,可以将元素存储到对于位置,同样也可以在对应位置访问该元素。

示例:将6个数字存储到哈希结构中
在这里插入图片描述

哈希冲突

对于两个数据元素i和j,其中关键码 k i ≠ k j k_i \ne k_j ki=kj,但是hash( k i k_i ki) = hash( k j k_j kj)

即:不同关键字通过相同哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。

当插入数字9时,进行哈希计算后下标为1,但是该位置以及存在其他元素。
在这里插入图片描述

哈希函数

引起哈希冲突的一个原因:哈希函数不够合理

哈希函数:能将任意长度的关键码映射成固定长度的数据(下标)

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值
    域必须在0到m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应该较简单

常见的哈希函数(了解):

  • 直接定址法

取关键字的某个线性函数为散列地址:$Hash(Key)= A*Key + B $

  • 除留余数法

散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数
H a s h ( k e y ) = k e y   m o d   p ( p < = m ) Hash(key) = key \bmod p (p <= m) Hash(key)=keymodp(p<=m)
选择质数作为除数:由于质数本身不存在多余的因子,所以与其他数做取模运算的结果更加分散

  • 平方取中法

例如:关键字为1234,平方为1522756,选取中间3位:277作为哈希地址

  • 折叠法

折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这
几部分叠加求和,并按散列表表长,取后几位作为散列地址

  • 随机数法

H a s h ( k e y ) = r a n d o m ( k e y ) Hash(key) = random(key) Hash(key)=random(key)

解决哈希冲突

解决哈希冲突两种常见的方法是:闭散列开散列

闭散列

介绍

闭散列也叫做开放定址法

    当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中下一个空位置中

线性探测

当发生哈希冲突时,从发生冲突的位置开始,依次向后探测,直到找到下一个空位置为止

int index = Hash(key);
while(hashTable[index].state == EXITS)
{
    ++index;//向后遍历
}

对于发生冲突的情况,向后遍历寻找最近的一个空位置—线性探测
在这里插入图片描述

线性探测的缺陷是产生冲突的数据会堆积在一起 ,寻找某个关键码可能需要多次比较,导致效率降低。

二次探测

寻找下一个空位置时,不再挨着逐个去找,选择逐步跳跃式的去找,避免冲突堆积

int index = Hash(key);
int start = index, i = 0;
while(hashTable[index].state == EXITS)
{
    index = start + i * i;
    ++i;
}

插入数字16时,发生冲突,选择二次探测的方式查找空位置
在这里插入图片描述

负载因子

随着插入数据的增多, 插入的数据产生冲突的概率也增加了。冲突的增加,在查找时的效率也会降低。

装载因子(load factor) = 表中有效数据个数 / 空间大小

  • 装载因子越大,数据越多,冲突的可能性越大
  • 装载因子越小,数据越少,冲突的可能性越小

通常来说,当哈希表的装载因子超过0.7时就需要考虑进行扩容,否则可能会导致插入/查找操作的效率大幅下降。

实现

哈希表结构

enum State
{
	EMPTY,	//空状态
	EXITS,	//使用状态
	DELETE,	//删除状态
};

template<class K, class V>
struct HashDate
{
	pair<K, V> _kv; // 存储数据的键值对
	State _state;   // 标记状态
};

template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
private:
	vector<HashDate<K, V>> _table; // 哈希数组
	size_t _n = 0;  // 有效数据个数,来计算装载因子
};

哈希函数

将k类型的关键码转换为整型

template<class K>
    struct HashFunc
    {
        size_t operator()(const K& key)
        {
            return (size_t)key;
        }
    };
// 特化  20:12继续
template<>
struct HashFunc<string>
{
    // BKDR哈希算法
    size_t operator()(const string& key)
    {
        size_t val = 0;
        for (auto ch : key)
        {
            val *= 131;
            val += ch;
        }

        return val;
    }
};

BKDR哈希算法是由徐盛忠教授于1992年提出的一种哈希函数,它是通过将字符串视为整数来计算哈希值的。其基本思想是使用一个较小的质数seed(比如31、131、1313、13131等),将字符串中各个字符的ASCII码转化为整数后,与seed相乘并求和得到一个哈希值

元素查找

HashDate<K, V>* find(const K& key)
{
    if (_table.size() == 0)//没有元素
    {
        return nullptr;
    }


    HashFunc hf;	//通过仿函数将关键码转换为整型
    size_t index = hf(key) % _table.size();//除留余数法

    // 线性探测
    while (_table[index]._state != EMPTY)
    {
        // 找到待查找元素,并返回它的地址
        if (_table[index]._state == EXITS && 
            _table[index]._kv.first == key)
        {
            return &_table[index];
        }

        ++index;
        index %= _table.size();

    }
    return nullptr;// 不存在此元素
}

插入元素

bool insert(const pair<K, V>& kv)
{
    if (find(kv.first))
    {
        return false;	// 元素已存在
    }


    if (_table.size() == 0)
    {
        _table.resize(10); //设置哈希表初始空间大小
    }
    // 装载因子超过阈值0.7就进行扩容
    else if( _n * 1.0 /_table.size() > 0.7)
    {
        //创建一个新的哈希表,大小为原哈希表的2倍
        HashTable<K, V> newHT;
        newHT._table.resize(2 * _table.size());
        //将原数据插入到新哈希表
        for (auto& e : _table)
        {
            if (e._state == EXIST)
            {
                newHT.Insert(e._kv);
            }
        }
        //交换两个哈希表
        _table.swap(newHT._table);
    }

    // 用哈希函数算出哈希表中的映射位置
    HashFunc hf;
    size_t index = hf(kv.first) % _table.size();

    // 线性探测
    while (_table[index]._state == EXITS)
    {
        ++index;
        index %= _table.size(); // 防止下标越界
    }
    //插入元素
    _table[index]._kv = kv;
    _table[index]._state = EXITS;
    ++_n;

    return true;
}

删除元素

bool erase(const K& key)
{
    //查找该元素
    HashDate<K, V>* ret = find(key);
    if (ret == nullptr)
        return false;	//不存在
    else // 找到
    {
        ret->_state = DELETE;//删除
        --_n;
        return true;
    }
}

开散列

介绍

开散列也叫做链地址法

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

在这里插入图片描述
产生哈希冲突的元素会通过链表组织起来,形成一个子集合-----哈希桶

实现

哈希表结构

template<class K, class V>
struct HashNode
{
	pair<K, V> _kv;
	HashNode<K, V>* _next;

	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;
private:
	vector<Node*> _tables;//哈希表
	size_t _size = 0; // 存储有效数据个数
};

元素查找

Node* find(const K& key)
{
    if (_table.size() == 0)
        return nullptr;	//哈希表为空

    //哈希映射
    hash hash;
    size_t index = hash(key) % _table.size();	
    Node* cur = _table[index];
    while (cur != nullptr)
    {
        if (cur->_kv.first == key)
            return cur;
        else cur = cur->_next;
    }
    return nullptr;
}

插入元素

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

    if (_table.size() == 0)
    {
        _table.resize(53); //设置哈希表初始空间大小
    }
    // 装载因子超过阈值0.7就进行扩容
    else if (_n * 1.0 / _table.size() > 0.7)
    {
        //创建一个新的哈希表,大小为原哈希表的2倍
        vector<Node*> newTables;
        newTables.resize(__stl_next_prime(_tables.size()), nullptr);
        // 旧表中节点移动映射新表
        Hash hash;
        for (size_t i = 0; i < _tables.size(); ++i)
        {
            Node* cur = _tables[i];//首节点
            while (cur != nullptr)
            {
                Node* next = cur->_next;

                size_t hashi = hash(cur->_kv.first) % newTables.size();
                //头插
                cur->_next = newTables[hashi];
                newTables[hashi] = cur;

                cur = next;
            }

            _tables[i] = nullptr;
        }
        //交换两个哈希表
        _tables.swap(newTables);
    }

    Hash hash;
    size_t hashi = hash(kv.first) % _tables.size();
    // 头插
    Node* newnode = new Node(kv);
    newnode->_next = _tables[hashi];
    _tables[hashi] = newnode;

    ++_size;

    return true;
}
//返回质数
size_t __stl_next_prime(size_t n)
{
    static const size_t __stl_num_primes = 28;
    static const size_t __stl_prime_list[__stl_num_primes] =
    {
        53, 97, 193, 389, 769,
        1543, 3079, 6151, 12289, 24593,
        49157, 98317, 196613, 393241, 786433,
        1572869, 3145739, 6291469, 12582917, 25165843,
        50331653, 100663319, 201326611, 402653189, 805306457,
        1610612741, 3221225473, 4294967291
    };

    for (size_t i = 0; i < __stl_num_primes; ++i)
    {
        if (__stl_prime_list[i] > n)
        {
            return __stl_prime_list[i];
        }
    }

    return -1;
}

使用质数作为除数

删除元素

bool erase(const K& key)
{
    //哈希表为空
    if (_tables.size() == 0) return nullptr;

    //find
    Hash hash;
    size_t hashi = hash(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;
            --_size;

            return true;
        }

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

    return false;
}

析构函数

~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;
    }
}

    🦀🦀观看~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/31170.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

测试用例excel转word(Office word篇)

场景 我们在项目中&#xff0c;默认情况下是用我们的Excel用例模版输出测试用例。但是有的项目中&#xff0c;会要求在Word版本的测试计划或者测试报告中&#xff0c;写明测试用例。而我们的测试用例&#xff0c;有的项目有上千条&#xff0c;这个时候如果从Excel往Word中复制…

Cortext-M3系统:异常(3)

1、异常 异常响应系统是再M3内核水平上的&#xff0c;支持众多的系统异常和外部中断。1-15为系统异常&#xff0c;大于16为外部中断。除了个别异常的优先级被定死外&#xff0c;其它异常的优先级都是可编程的。优先级数值越小&#xff0c;优先级越高。CM3支持中断嵌套&#xff…

MyBatis面试题

什么是 MyBatis&#xff1f; MyBatis 是一个半 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;它内部封装了 JDBC&#xff0c;开发时只需要关注 SQL 语句本身&#xff0c;不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。程序员直接编写原生…

机器学习——识别足球和橄榄球

一、选题的背景 橄榄球起源于足球&#xff0c;二者即相似又有所区别。计算机技术发展至今&#xff0c;AI技术也有了极大的进步&#xff0c;通过机器学习不断的训练&#xff0c;AI对于足球和橄榄球的识别能力可以帮助人们对足球和橄榄球的分辨。机器学习是一种智能技术&#xff…

详解Http的Content-Type

目录 1.概述 2.常用类型 2.1.application/x-www-form-urllencoded 2.2.application/json 3.Spring MVC支持的编码 3.1.实验 3.2.适配器 3.3.自定义适配器 1.概述 HTTP&#xff08;HyperText Transfer Protocol&#xff09;&#xff0c;超文本传输协议。超文本&#xf…

GEE:将地形山体阴影和类别概率信息结合起来,绘制概率山体阴影(Probability Hillshade)图

作者:CSDN @ _养乐多_ 本文将介绍使用哨兵数据将地形山体阴影和类别概率信息结合起来,绘制概率山体阴影图的代码。 “Probability Hillshade”(概率山体阴影)是指使用Dynamic World数据集中最可能的类别概率信息创建的一种可视化效果。它结合了地形山体阴影和类别概率信息…

【pytorch】新的windows电脑从头搭建pytorch深度学习环境(完整版+附安装包)

文章目录 新的windows电脑搭建pytorch深度学习环境电脑环境的配置显卡驱动cudacudnn pytorch开发软件的安装minicondavscode pytorch环境的安装conda安装python环境安装pytorch和torchvision 附录1&#xff1a;部分torch、torchvision、torchaudio版本对应关系附录2&#xff1a…

iOS App 上架流程图文教学

在上架App 之前必须先准备好开发者帐号&#xff0c;但申请开发者帐号因法兰克早在之前已经申请好了&#xff0c;故就跳过此步骤&#xff0c;直接从产生凭证到上传App开始讲起。首先&#xff0c;要将自己辛苦写好的App 送审的话&#xff0c;则要依序做完下列几件事情即可。 在开…

常见面试题之框架篇

1.Spring框架中的单例bean是线程安全的吗&#xff1f; 不是线程安全的&#xff0c;是这样的。 当多用户同时请求一个服务时&#xff0c;容器会给每一个请求分配一个线程&#xff0c;这是多个线程会并发执行该请求对应的业务逻辑&#xff08;成员方法&#xff09;&#xff0c;…

TensorFlow Core—基本分类:对服装图像进行分类

现在人工智能很火的&#xff0c;看到了这篇文章&#xff0c;给自己普及一下基础知识&#xff0c;也分享给大家&#xff0c;希望对大家有用。 本指南将训练一个神经网络模型&#xff0c;对运动鞋和衬衫等服装图像进行分类。即使您不理解所有细节也没关系&#xff1b;这只是对完…

3ds Max - Pivot Painter Tool

很久之前的笔记&#xff0c;整理归档&#xff1b; Pivot Painter Tool是3dsMax中的插件&#xff0c;主要是辅助将Mesh中每个Element生成自己的Pivot Position&#xff0c;方便如使用World Position Offset对每个Element进行精确控制&#xff0c;导入使用Pivot Painter Tool工具…

二进制搭建 Kubernetes v1.20

k8s集群master01&#xff1a;192.168.179.25 kube-apiserver kube-controller-manager kube-scheduler etcd k8s集群master02&#xff1a;192.168.179.26 k8s集群node01&#xff1a;192.168.179.23 kubelet kube-proxy docker k8s集群node02&#xff1a;192.168.179.22 …

统信UOS系统开发笔记(六):提取在线安装软件后,提取其安装包,部署目标机使用离线软件包方式安装软件

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/131348876 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

【性能设计篇】数据库拓展

前两篇文章介绍了分布式存储的机制&#xff0c;为保证数据的高性能以及可拓展&#xff0c;采用分片/分区机制。为保证数据的高可用性&#xff0c;采用复制/镜像机制存储数据。一份数据存储多份。而这两种方式在数据中&#xff0c;就是分片/分区机制。分库分表/读写分离。 读写…

深入理解深度学习——Transformer:解码器(Decoder)部分

分类目录&#xff1a;《深入理解深度学习》总目录 相关文章&#xff1a; 注意力机制&#xff08;Attention Mechanism&#xff09;&#xff1a;基础知识 注意力机制&#xff08;Attention Mechanism&#xff09;&#xff1a;注意力汇聚与Nadaraya-Watson核回归 注意力机制&…

一文了解RabbitMQ安装使用

什么是RabbitMQ? 官网&#xff1a;Messaging that just works — RabbitMQ RabbitMQ是一种开源的消息中间件软件&#xff0c;用于构建可扩展的分布式应用程序。它实现了高级消息队列协议&#xff08;AMQP&#xff09;&#xff0c;这是一种网络协议&#xff0c;用于在应用程序之…

C++ 新的类型转换

文章目录 前言一、静态转换&#xff08;static_cast&#xff09;二、动态转换&#xff08;dynamic_cast&#xff09;&#xff1a;三、常量转换&#xff08;const_cast&#xff09;&#xff1a;四、重新解释转换&#xff08;reinterpret_cast&#xff09;&#xff1a;总结 前言 …

【RabbitMQ教程】第三章 —— RabbitMQ - 发布确认

&#x1f4a7; 【 R a b b i t M Q 教程】第三章—— R a b b i t M Q − 发布确认 \color{#FF1493}{【RabbitMQ教程】第三章 —— RabbitMQ - 发布确认} 【RabbitMQ教程】第三章——RabbitMQ−发布确认&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人…

用Visual Studio 2022写出你第一个Windows程序(程序保证能正常运行)

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天来看看如何用Visual C写出你第一个Windows程序。 与其看很多Windows的书&#xff0c;不如先自己动手写一个Windows程序。由于Windows程序的特有机制&#xff0c;不建议去写那种简单的HELLO WORLD&#x…

5G是如何提升通行能力的?5G毫米波到底有多快?

高速公路&#xff0c;可以通过多层交通、多条车道、车道方向、车辆容量、货物包装、驾驶司机等多个因素&#xff0c;提升通行能力。 我们把5G比作高速公路&#xff0c;那么&#xff0c;5G是如何提升自身通行能力的呢&#xff1f;5G毫米波&#xff0c;到底能有多快呢&#xff1f…