数据结构-考研难点代码突破(查找算法 - 散列表(哈希表)C++实现除留余数法拉链法哈希)

news2024/11/15 10:29:49

文章目录

  • 1. 哈希表与解决哈希冲突的方法
  • 2. C++实现除留余数法拉链法哈希

1. 哈希表与解决哈希冲突的方法

散列表(Hash Table),又称哈希表。是一种数据结构。

特点:数据元素的关键字与其存储地址直接相关。

关键字通过散列函数(哈希函数)来实现。


如果不同的关键字通过哈希函数映射到相同的位置,此时这种情况称为哈希冲突。

哈希冲突的解决方法:

- 拉链法:把所有的冲突的元素以链表的形式挂起来
在这里插入图片描述

上图的平均成功查找长度:

这里规定如果数组对应位置阿伟空链表,这里为比较0次

  • ASL(成功):1/12(1 * 6+2 * 4+3 * 1+4 * 1)=1.75

  • ASL(失败):失败查找次数/哈希表长度

    (0+4+0 + 2+0 + 0 + 2 + 1+0+0 + 2 + 1+0)/13=0.92

可以注意到,ASL查找失败的分子就是哈希表上的有效节点个数

装填因子(负载因子)=表中记录数/散列表长度

装填因子直接影响查找效率.

哈希冲突越严重,效率越低。最理想情况下,不会发生哈希冲突,此时查找效率为O(1)

这个与选择的哈希函数有关,常见的哈希函数如下:
1. 除留余数:H(key) = key % p (p选择不大于m但接近m的质数)
2. 直接定址法:H(key) = key || H(key) = a*key + b,其中,a和b是常数。
	这种方法计算最简单,且不会产生冲突。
	它适合关键字的分布基本连续的情况,若关键字分布不连续,空位较多,则会造成存储空间的浪费。
3. 数字分析法:选取数码分布较为均匀的若干位作为散列地址
4. 平方取中法:取关键字的平方值的中间几位作为散列地址。

- 开放地址法:如果发生冲突后,将冲突的元素按照一定规则放到哈希表其他地方。

这里的规则如下:

  1. 线性探测法:发生冲突时,每次往后探测相邻的下一个单元是否为空

    需要注意:线性探测法在查找时如果这个位置没有数据,也算一次比较。

    线性探测法很容易造成关键字、非关键字的“聚集(堆积)”现象,严重影响查找效率

  2. 平方探测法:发生冲突时,每次向前/后探测的单元个数按照平方依次递增。

    d = 02,12,-12,22,-22, …, k2, -k2时,称为平方探测法,又称二次探测法

    比起线性探测法更不易产生“聚集(堆积)”问题.

    如果使用平方探测法:散列表长度m必须是一个可以表示成4j+3的素数,才能探测到所有位置

  3. 伪随机序列方式:定义一个伪随机序列,每次冲突时探测距离和伪随机序列相同。

- 再散列方式:(再哈希法)除了原始的散列函数H(key)之外,多准备几个散列函数,当散列函数冲突时,用下一个散列函数计算一个新地址,直到不冲突为止。


综上影响哈希效率的因素如下:

  1. 哈希函数
  2. 处理哈希冲突的方式
  3. 装填因子

2. C++实现除留余数法拉链法哈希

#include <iostream>
#include <vector>
#include <string>

template <class Key, class Value>
struct HashData
{
    std::pair<Key, Value> _kv;
    HashData<Key, Value> *_next;
    HashData(const std::pair<Key, Value> &data) : _kv(data), _next(nullptr) {}
};

// 泛型仿函数
template <class Key>
struct HashKey
{
    // 默认仿函数将key转化为可以使用除留余数法的整数型,如果转化失败,需要用户自己提供转化函数
    int operator()(const Key &key) { return key; }
};
// 特化字符串类型的转化方式
template <>
struct HashKey<std::string>
{
    int operator()(const std::string &Str)
    {
        int Sum = 0;
        for (int i = 0; i < Str.size(); i++)
        {
            Sum += ((i + 1) * Str[i]); // 尽量避免冲突
        }
        return Sum;
    }
};

template <class Key, class Value, class HashFuc = HashKey<Key>>
class Hash
{
    typedef HashData<Key, Value> HashData;

private:
    std::vector<HashData *> _table;
    size_t _size; // 哈希表中的元素个数
public:
    Hash()
    {
        _size = 0;
        _table.resize(4);
        for (size_t i = 0; i < _table.size(); i++)
        {
            _table[i] = nullptr;
        }
    }

    ~Hash()
    {
        for (size_t i = 0; i < _table.size(); i++)
        {
            HashData *node = _table[i];
            if (node != nullptr)
            {
                HashData *next = node->_next;
                delete node;
                node = next;
            }
            _table[i] = nullptr;
        }
    }

    bool insert(const std::pair<Key, Value> &value)
    {
        HashFuc kot;
        HashData *node = _findPos(value);
        if (node != nullptr)
        {
            // 不允许相同值出现
            return false;
        }
        else
        {
            if (_size == _table.size())
            {
                // 装填因子为1,扩大哈希表,提高哈希效率
                std::vector<HashData *> buff;
                buff.resize(_table.size() * 2);
                // 重新计算映射关系
                for (size_t i = 0; i < _table.size(); i++)
                {
                    HashData *ptr = _table[i];
                    while (ptr != nullptr)
                    {
                        HashData *next = ptr->_next;
                        size_t new_pos = kot(ptr->_kv.first) % buff.size();
                        ptr->_next = buff[new_pos];
                        buff[new_pos] = ptr;
                        ptr = next;
                    }
                }
                _table.swap(buff);
            }
            size_t pos = kot(value.first) % _table.size();
            // 头插法
            HashData *data = new HashData(value);
            data->_next = _table[pos];
            _table[pos] = data;
            _size += 1;
            return true;
        }
    }

    bool erase(const Key &key)
    {
        HashFuc kot;
        size_t pos = kot(key.first) % _table.size();
        HashData *node = _table[pos];
        HashData *prev = nullptr;
        while (node != nullptr)
        {
            if (node->_kv.first != key)
            {
                prev = node;
                node = node->_next;
            }
        }
        if (node != nullptr)
        {
            if (prev == nullptr)
            {
                // 头删
                _table[pos] = node->_next;
                delete node;
            }
            else
            {
                prev->_next = node->_next;
                delete node;
            }
            return true;
        }
        else
        {
            // 无此元素
            return false;
        }
    }

    void disPlay()
    {
        for (size_t i = 0; i < _table.size(); i++)
        {
            HashData *node = _table[i];
            while (node != nullptr)
            {
                std::cout << node->_kv.first << ":" << node->_kv.second << " | ";
                node = node->_next;
            }
            std::cout << "\n";
        }
    }

    Value operator[](const Key &key)
    {
        HashData *find = _findPos(std::make_pair(key, Value()));
        if (find != nullptr)
        {
            return find->_kv.second;
        }
        else
        {
            // 没有找到这个元素,直接插入到哈希表中
            insert(std::make_pair(key, Value()));
            return Value();
        }
    }

private:
    HashData *_findPos(const std::pair<Key, Value> &value)
    {
        HashFuc kot;
        size_t pos = kot(value.first) % _table.size();
        HashData *cur = _table[pos];
        while (cur != nullptr)
        {
            if (cur->_kv.first == value.first)
            {
                break;
            }
            cur = cur->_next;
        }
        return cur;
    }
};

#include "Hash.h"

using namespace std;

void test1()
{
    Hash<char, int> hash;
    hash.insert({'a', 1});
    hash.insert({'c', 2});
    hash.insert({'d', 4});
    hash.insert({'e', 7});
    hash.insert({'b', 8});
    hash.insert({'p', 2});
    hash.disPlay();
    cout << hash['a'] << endl;
    cout << hash['z'] << endl;
}

void test2()
{
    vector<string> Array = {"苹果", "香蕉", "西瓜", "香蕉", "香蕉", "西瓜", "西瓜", "苹果"};
    Hash<string, int> Hash;
    for (int i = 0; i < Array.size(); i++)
    {

        Hash.insert(make_pair(Array[i], i));
    }
    Hash.disPlay();
}

int main(int argc, char const *argv[])
{
    test1();
    test2();
    return 0;
}

运行结果
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3.0文档整理:2、创建单页面应用程序

2.1&#xff1a;创建步骤 2.1.1&#xff1a;vue-cli 安装并执行create-vue:npm init vuelatest 它是Vue官方的项目脚手架工具 选择项目功能 除了第一项的项目名字外&#xff0c;其他可以暂时默认回撤或者选择No 切换到项目目录:cd <your-project-name> 安装项目依赖&…

山寨APP频出?安全工程师和黑灰产在较量

在山寨这个领域&#xff0c;没有人比黑灰产更懂模仿。 据安全从业者介绍&#xff0c;一般而言&#xff0c;对于成熟的山寨开发者来说&#xff0c;几天时间内就可以做出一套前端框架。服务器、源代码、域名、服务商这些内容的创建&#xff0c;通过网上租赁的方式就可以解决。 比…

【面试题】2023前端vue面试题及答案

Vue3.0 为什么要用 proxy&#xff1f;在 Vue2 中&#xff0c; 0bject.defineProperty 会改变原始数据&#xff0c;而 Proxy 是创建对象的虚拟表示&#xff0c;并提供 set 、get 和 deleteProperty 等处理器&#xff0c;这些处理器可在访问或修改原始对象上的属性时进行拦截&…

Window问题详解(下)

建议先看一下 Window问题详解(上) 思路② 既然会超时,那该怎么办呢? 显然需要一个更快速的方法来解决这个问题! 我们先来观察一下图片: 我们发现,每一次选中的数都会增加下一个。 !!!!! 因此,我们可以根据此特性优化时间!! 第一次先求出前 k − 1 k-1 k−

hdfs file system shell的简单使用

文章目录1、背景2、hdfs file system shell命令有哪些3、确定shell操作的是哪个文件系统4、本地准备如下文件5、hdfs file system shell5.1 mkdir创建目录5.2 put上传文件5.3 ls查看目录或文件5.4 cat 查看文件内容5.5 head 查看文件前1000字节内容5.6 tail 查看文件后1000字节…

Kubernetes12:k8s集群安全机制 ***与证书生成***

Kubernetes12&#xff1a;k8s集群安全机制 1、概述 1&#xff09;访问一个k8s集群的时候&#xff0c;需要经过以下三个步骤才能完成具体操作 第一步&#xff1a;认证操作第二部&#xff1a;鉴权操作&#xff08;授权&#xff09;第三部&#xff1a;准入控制操作 2&#xff…

小白晋升大牛的13个项目

入门到放弃 “C/C真的太难学了,我准备放弃了!” 很多初学者在学完C和C的基本语法后&#xff0c;就停滞不前了&#xff0c;最终走向“从入门到放弃”。其实&#xff0c;我们初学者最需要的不是HelloWorld&#xff0c;也不是语法知识的堆砌&#xff0c;需要的只是实战项目的磨砺…

「TCG 规范解读」基础设施架构和协议 (1)

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Alli…

9、STM32 SDIO FATFS(SD卡)

本篇文章使用STM32对SD卡通过SDIO配置&#xff0c;读写文件 在使用FATFS时值得注意得是若通信SDIO不启动DMA方式读写&#xff0c;容易导致其他任务中断打断读写时序&#xff0c;导致FATFS的执行出现异常&#xff0c;常见返回为FR_DISK_ERR, / (1) A hard error occurred in the…

【论文阅读】Robust Invertible Image Steganography (CVPR 2022)

作者来自北大深研院 网上已有介绍&#xff1a;https://news.pkusz.edu.cn/info/1002/6538.htm 针对传统图像隐写方法对高斯噪声、泊松噪声和有损压缩鲁棒性差的问题&#xff0c;提出了一种基于流的鲁棒可逆图像隐写框架RIIS。框架如下图 一、方法概述&#xff1a; 基于流的可…

计算机组成原理4小时速成6:输入输出系统,io设备与cpu的链接方式,控制方式,io设备,io接口,并行串行总线

计算机组成原理4小时速成6&#xff1a;输入输出系统&#xff0c;io设备与cpu的链接方式&#xff0c;控制方式&#xff0c;io设备&#xff0c;io接口&#xff0c;并行串行总线 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c…

计算机网络--网络层 IPv4地址概述(day05)

网络层 网络层提供的两种服务 IPv4地址概述 IPv4地址就是给因特网(Internet)上的每一台主机(或路由器&#xff09;的每一个接口分配一个在全世界范围内是唯一的32比特的标识符 IPv4地址的编址方法经历了如下三个历史阶段&#xff1a; 分类编址 1981划分子网 1985无分类编址…

JavaScript Array 数组对象

文章目录JavaScript Array 数组对象什么是数组?创建一个数组访问数组JavaScript Array 数组对象 数组对象的作用是&#xff1a;使用单独的变量名来存储一系列的值。 创建数组, 为其赋值&#xff1a; 实例 var mycars new Array(); mycars[0] "Saab"; mycars[1]…

【虚幻引擎】UE4源码解析FWorldContent、UWorld、ULevel、UGameInstance、UEngine

一、UEngine Engine&#xff0c;因为也是很基础的类&#xff0c;再加上开发过程中会经常访问到该类型&#xff0c;因此UE4引擎也在代码全局范围内定义了一个该类型的全局变量&#xff1a;UEngine* GEngine供开发者直接调用。该最基础的类型分化成了两个子类&#xff1a;UGameE…

【面试题】 JavaScript 字符串截取方法有哪些?

大厂面试题分享 面试题库后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库在 JavaScript 中&#xff0c;可以使用 substr()、slice() 和 substring() 方法截取字符串。substring()substring() 方法返回一个字符串在开始…

Lock锁心得

lock接口最常见的实现类ReentrantLock&#xff0c;通常情况下&#xff0c;lock只允许一个线程访问共享资源&#xff0c;也有特殊情况&#xff0c;比如读写锁里的读锁。lock和synchronizrd是常见的锁&#xff0c;都可以让代码变得安全。但是功能上有差别&#xff0c;二者不是能替…

笃行不怠勾勒人才图,望城区人才工作为高质量发展增添强劲动力

功以才成&#xff0c;业以才广。人才是经济高质量发展的重要资源。党的二十大报告明确“深入实施人才强国战略”&#xff0c;指出“实施更加积极、更加开放、更加有效的人才政策”。作为湖南“一核两副三带四区”区域经济发展格局的战略支点&#xff0c;望城区积极发挥着政策主…

TCP协议详解—TCP各个报头属性的作用

文章目录一.TCP是什么二.TCP协议格式1.报头属性解释TCP首部长度/如何解包分用三.确认应答机制-tcp如何保证可靠性1.确认应答机制2.序号/确认序号-如何保证报文按序到达3.为什么要两个序号四.16位窗口大小-调整发送策略五.6个标志位一.TCP是什么 首先我们需要知道TCP是什么,TCP全…

你知道Object类和Objects的常用方法吗

文章目录Object的常用方法Objects的常用方法hashCodehashisNullequalsrequireNonNullcomparenonNull大家好&#xff0c;Leo又来了!!!最近在网上看到别人分析Objects中isNullfanfan方法来判断&#xff0c;我就想到之前写的一坨坨null&#xff0c;突然很惭愧&#xff0c;特地去研…

IIC总线式驱动开发(mpu6050)

目录 一、I2C总线背景知识 二、Exynos4412 I2C收发实现之裸机版 2.1 发送 2.2 接收 三、Linux内核对I2C总线的支持 四、MPU6050 五、应用层直接使用I2C通道 5.1 预备工作&#xff1a; 5.1.1 5.1.2 5.2 应用层直接使用i2c总线的代码实现 5.2.1 调用read、write实现接…