std::hash——STL中的哈希函数

哈希函数在计算机科学中扮演着重要的角色,可用于实现数据存储、缓存等常用技术。在C++的STL库中,std::hash作为哈希函数的实现,提供了快速而简单的哈希算法。本文将从多个方面阐述std::hash的相关知识,为读者带来深入了解。

一、std::hash的基本用法

#include 
#include 
#include 

int main() {
    std::unordered_map myMap;
    myMap["apple"] = 1;
    myMap["banana"] = 2;
    myMap["cat"] = 3;
    
    std::hash hasher;
    std::cout << "Hash value of 'apple': " << hasher("apple") << std::endl;
    
    return 0;
}

如上述代码所示,std::hash是一个模板类,模板参数为被哈希的数据类型。在使用时,我们需要创建一个std::hash对象,并将待哈希的数据作为其参数传入,得到该数据的哈希值。

上述代码中,我们创建了一个std::unordered_map对象,并存入三组键值对。创建std::hash对象后,我们使用其计算了字符串”apple”的哈希值,将其输出至控制台。运行该段代码,输出结果为:

Hash value of 'apple': 3032715092448685573

可以看到,std::hash计算出来的哈希值是一个长整型的数值,用于表示该数据在哈希表中的位置。

二、自定义类型的哈希函数

std::hash模板类默认支持对大部分基本数据类型(例如整型、浮点型等)的哈希计算,同时如果需要对某个自定义类型进行哈希,可以通过提供一个哈希函数的实现来实现该功能。以下是一个自定义类型的示例:

struct Student {
    std::string name;
    int age;
    bool gender;
};

namespace std {
    template 
    struct hash {
        size_t operator()(const Student& s) const {
            return std::hash()(s.name) ^ std::hash()(s.age) ^ std::hash()(s.gender);
        }
    };
}

int main() {
    std::unordered_map myMap;
    myMap[{"Tom", 20, true}] = 1;
    myMap[{"Lucy", 21, false}] = 2;
    
    for (auto& [k, v] : myMap) {
        std::cout << "Name: " << k.name << " Age: " << k.age << " Gender: " << k.gender << " Value: "
        << v << std::endl;
    }
    
    return 0;
}

如上述代码所示,我们首先定义了一个Student结构体,其包含三个成员分别为姓名、年龄和性别。然后我们为该结构体提供了哈希函数的实现,该哈希函数使用异或运算符将姓名、年龄和性别三个数据分别计算哈希值并合并。

在主函数中,我们创建了一个std::unordered_map对象,并存入两个Student对象。值得注意的是,我们可以直接使用”{“和”}”来创建一个Student对象,这些语法是C++17的新特性。

最后,我们对哈希表中的每个对象进行遍历,并将Student成员的值输出至控制台。运行该段代码,输出结果为:

Name: Lucy Age: 21 Gender: 0 Value: 2
Name: Tom Age: 20 Gender: 1 Value: 1

可以看到,我们成功地将Student对象作为哈希表的键,并且得到了正确的输出结果。

三、std::hash的性能和冲突处理

哈希函数的性能是我们重点关注的部分之一,因为哈希表的性能往往会直接影响程序的执行效率。std::hash的性能取决于其计算算法的效率,同时也与哈希表的大小、被哈希的数据的分布情况等密切相关。

在处理哈希冲突方面,std::hash使用了拉链法(Chaining)来解决冲突。在拉链法中,哈希表的每个桶中存放着一个链表,若多个数据的哈希值映射到同一个桶中,则将这些数据存储在链表中。当需要访问某个数据时,先根据其哈希值定位到该桶,然后遍历该桶中的链表,找到对应的数据。

以下代码演示了哈希表中的冲突处理过程,以及哈希表大小对性能的影响:

#include 
#include 
#include 

int main() {
    std::unordered_map myMap;
    for (int i = 0; i < 50000; ++i) {
        myMap[i] = i;
    }
    
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 50000; ++i) {
        myMap[i];
    }
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration diff = end - start;
    std::cout << "Time taken on 50k elements: " << diff.count() << " seconds" << std::endl;
    
    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 500000; ++i) {
        myMap[i];
    }
    end = std::chrono::high_resolution_clock::now();
    diff = end - start;
    std::cout << "Time taken on 500k elements: " << diff.count() << " seconds" << std::endl;
    
    std::unordered_map myMap2;
    for (int i = 0; i < 50000; ++i) {
        myMap2[i] = i;
    }
    myMap2.reserve(100000);
    
    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 50000; ++i) {
        myMap2[i];
    }
    end = std::chrono::high_resolution_clock::now();
    diff = end - start;
    std::cout << "Time taken on 50k elements with capacity: " << diff.count() << " seconds" << std::endl;
    
    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 500000; ++i) {
        myMap2[i];
    }
    end = std::chrono::high_resolution_clock::now();
    diff = end - start;
    std::cout << "Time taken on 500k elements with capacity: " << diff.count() << " seconds" << std::endl;
    
    return 0;
}

通过上述代码,我们创建了两个哈希表myMapmyMap2,它们分别包含了50000和500000个元素。我们使用std::chrono库来测量对这两个哈希表中元素的访问时间,其中myMap2事先被调用了reserve函数,其容量为100000。

运行该段代码,输出结果为:

Time taken on 50k elements: 0.000267165 seconds
Time taken on 500k elements: 0.00288509 seconds
Time taken on 50k elements with capacity: 7.302e-06 seconds
Time taken on 500k elements with capacity: 7.95873e-05 seconds

可以看到,在没有reserve函数预先分配哈希表容量的情况下,访问500000个数据需要耗费2.88秒的时间;而在预先分配容量的情况下,访问500000个数据的耗时仅为0.000795873秒,大大提高了程序的性能。

此外,哈希表的负载因子(load factor)是衡量哈希表性能的重要指标之一。负载因子的计算公式为hashtable.size()/hashtable.bucket_count(),表示哈希表中键值对的数量与哈希表的容量之比。当哈希表的负载因子接近于1时,意味着哈希冲突较多,查询效率会受到很大影响。因此在实际应用中,我们应该尽可能保持哈希表的负载因子在一个合理的范围内。

四、std::hash的局限性

std::hash计算出的哈希值是一个长整型的数值,其占用空间较大。在有些情况下,我们需要将哈希值转换为指定的数据类型。这时我们可以使用std::hash_combine函数,该函数将多个哈希值合并为一个,从而生成一个新的哈希值。以下是一个使用std::hash_combine的例子:

#include 
#include 

template 
void hash_combine(std::size_t& seed, const T& val) {
    seed ^= std::hash()(val) + 0x9e3779b9 + (seed <> 2);
}

int main() {
    std::string s1 = "hello";
    std::string s2 = "world";
    std::size_t h1 = std::hash{}(s1);
    std::size_t h2 = std::hash{}(s2);
    std::size_t combined_hash = 0;
    hash_combine(combined_hash, h1);
    hash_combine(combined_hash, h2);
    std::cout << "Combined hash: " << combined_hash << std::endl;
    
    return 0;
}

在上述代码中,我们定义了一个hash_combine函数,使用XOR运算符将多个哈希值合并。在主函数中,我们计算了两个字符串的哈希值,并使用hash_combine函数将其合并。最终输出的哈希值为:

Combined hash: 4820441328262842544

除此之外,std::hash还存在其他的一些局限性,例如它不能处理无法比较相等的对象、不能处理重载了&运算符的类型等。在这些情况下,我们需要手动提供哈希函数的实现。

五、小结

本文详细介绍了std::hash在C++中的基本用法、自定义类型的哈希函数实现方法、哈希表的性能与冲突处理、哈希值合并以及std::hash的局限性等方面的知识点。通过本文的学习,读者可以更全面、深入地理解C++ STL中哈希函数的实现。

原创文章,作者:小蓝,如若转载,请注明出处:https://www.506064.com/n/186162.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
小蓝小蓝
上一篇 2024-11-27 05:44
下一篇 2024-11-27 05:44

相关推荐

  • Python中引入上一级目录中函数

    Python中经常需要调用其他文件夹中的模块或函数,其中一个常见的操作是引入上一级目录中的函数。在此,我们将从多个角度详细解释如何在Python中引入上一级目录的函数。 一、加入环…

    编程 2025-04-29
  • Python中capitalize函数的使用

    在Python的字符串操作中,capitalize函数常常被用到,这个函数可以使字符串中的第一个单词首字母大写,其余字母小写。在本文中,我们将从以下几个方面对capitalize函…

    编程 2025-04-29
  • Python中set函数的作用

    Python中set函数是一个有用的数据类型,可以被用于许多编程场景中。在这篇文章中,我们将学习Python中set函数的多个方面,从而深入了解这个函数在Python中的用途。 一…

    编程 2025-04-29
  • 三角函数用英语怎么说

    三角函数,即三角比函数,是指在一个锐角三角形中某一角的对边、邻边之比。在数学中,三角函数包括正弦、余弦、正切等,它们在数学、物理、工程和计算机等领域都得到了广泛的应用。 一、正弦函…

    编程 2025-04-29
  • 单片机打印函数

    单片机打印是指通过串口或并口将一些数据打印到终端设备上。在单片机应用中,打印非常重要。正确的打印数据可以让我们知道单片机运行的状态,方便我们进行调试;错误的打印数据可以帮助我们快速…

    编程 2025-04-29
  • Python3定义函数参数类型

    Python是一门动态类型语言,不需要在定义变量时显示的指定变量类型,但是Python3中提供了函数参数类型的声明功能,在函数定义时明确定义参数类型。在函数的形参后面加上冒号(:)…

    编程 2025-04-29
  • Python定义函数判断奇偶数

    本文将从多个方面详细阐述Python定义函数判断奇偶数的方法,并提供完整的代码示例。 一、初步了解Python函数 在介绍Python如何定义函数判断奇偶数之前,我们先来了解一下P…

    编程 2025-04-29
  • Python实现计算阶乘的函数

    本文将介绍如何使用Python定义函数fact(n),计算n的阶乘。 一、什么是阶乘 阶乘指从1乘到指定数之间所有整数的乘积。如:5! = 5 * 4 * 3 * 2 * 1 = …

    编程 2025-04-29
  • 分段函数Python

    本文将从以下几个方面详细阐述Python中的分段函数,包括函数基本定义、调用示例、图像绘制、函数优化和应用实例。 一、函数基本定义 分段函数又称为条件函数,指一条直线段或曲线段,由…

    编程 2025-04-29
  • Python函数名称相同参数不同:多态

    Python是一门面向对象的编程语言,它强烈支持多态性 一、什么是多态多态是面向对象三大特性中的一种,它指的是:相同的函数名称可以有不同的实现方式。也就是说,不同的对象调用同名方法…

    编程 2025-04-29

发表回复

登录后才能评论