【C++】unordered系列

简介: `unordered`容器是C++11及其后续版本中STL的一部分,包括`unordered_map`和`unordered_set`,基于哈希表实现,支持快速查找、插入和删除操作。`unordered_map`存储键值对,`unordered_set`存储唯一元素,两者均提供高效的存取性能,特别适合处理大数据量的应用场景。这些容器通过哈希函数将数据映射到特定位置,虽然存在哈希冲突问题,但可通过开放定址法、链地址法等策略有效解决。

前言:

在C++11及以后的标准中,unordered容器是标准模板库(STL)的一部分,提供了高效的数据结构选项,适用于需要快速查找和插入操作的场景。

unordered通常与关联容器一起使用,特别是unordered_mapunordered_set。这些容器提供了基于哈希表的实现,它们提供了与map和set相似的接口,但在查找、插入和删除操作上通常具有更高的性能,尤其是在处理大量数据时。

  • unordered_map是一个关联容器,它存储键值对,并根据键的哈希值进行排序,以实现快速的查找操作。
  • unordered_set则存储唯一的元素,并使用哈希表来管理这些元素,以便快速检查一个元素是否存在于集合中。

unordered_map的接口说明

注: unordered_mapunordered_set接口相似就只介绍一个接口。

unordered_map的接口说明

函数声明 功能介绍
unordered_map 构造不同格式的unordered_map对象

unordered_map的容量

函数声明 功能介绍
bool empty()const 检测unordered_map是否为空
size_t size() const 获取unordered_map的有效元素个数

unordered_map的迭代器

函数声明 功能介绍
begin 返回unordered_map第一个元素的迭代器
end 返回unordered_map最后一个元素下一个位置的迭代器
cbegin 返回unordered_map第一个元素的const迭代器
cend 返回unordered_map最后一个元素下一个位置的const迭代器

unordered_map的元素访问

函数声明 功能介绍
operator[] 返回与key对应的value,没有一个默认值

unordered_map的查询

函数声明 功能介绍
iterator find(const K& key) 返回key在哈希桶中的位置
size_t count(const K& key) 返回哈希桶中关键码为key的键值对的个数

unordered_map的修改操作

函数声明 功能介绍
insert 向容器中插入键值对
erase 删除容器中的键值对
void clear() 清空容器中有效元素个数
void swap(unordered_map&) 交换两个容器中的元素

unordered_map的桶操作

函数声明 功能介绍
size_t bucket_count()const 返回哈希桶中桶的总个数
size_t bucket_size(size_t n)const 返回n号桶中有效元素的总个数
size_t bucket(const K& key) 返回元素key所在的桶号

哈希

  1. 哈希(Hash)是一种将任意大小的数据映射为固定大小值的函数,通常用于数据的快速查找和存储。
  2. 哈希表(Hash Table)是基于哈希函数的一种数据结构,它通过计算关键字的哈希值来直接访问存储位置,从而实现常数时间复杂度的查找性能。
  • 在这里面的经过一系列的处理,得到的结果就映射到对应的位置,方便查找,加快了查找的速度。但是也会引发一系列的问题(哈希冲突)。

哈希函数

向上面的通过一系列的计算的过程就是哈希函数,有点类似于数学上的函数,一个数经过对应关系会得到两者的映射的关系。

常见的哈希函数

  1. 直接定址法:这种方法通过一个简单的线性函数计算哈希地址,公式为 Hash(Key) = A * Key + B。这种方法简单且分布均匀,但需要提前知道键值的分布情况。
  2. 除留余数法:这是一种常用的哈希函数,通过取关键字除以一个质数后的余数作为哈希地址,公式为 Hash(Key) = Key % p,其中 p 是一个不大于哈希表大小且最接近或等于哈希表大小的质数。
  3. 乘法取余法:这种方法通过将关键字乘以一个固定的数(通常是一个接近2的数),然后取结果的整数部分并进行取模运算来计算哈希地址。这种方法适用于处理字符串等非整数类型的键值。
  4. 平方取中法:这种方法适用于不清楚键值分布的情况,通过计算关键字平方后的中间几位数作为哈希地址。
  5. 折叠法:折叠法将键值分割成几部分,然后将这些部分叠加求和,并取后几位作为散列地址,适用于键值位数较多的情况。
  6. 标准库中的std::hash:C++标准库提供了std::hash模板,它为内置数据类型和一些标准库类型提供了默认的哈希函数实现。对于自定义类型,可以通过特化std::hash模板来提供自定义的哈希函数。

哈希冲突

哈希冲突是指不同的输入值通过哈希函数计算得到了相同的哈希值的现象。

  1. 在哈希表等数据结构中,哈希冲突是不可避免的,因为哈希函数的输出范围通常远小于可能的输入值范围。

解决方法

  • 开放定址法(闭散列):当发生冲突时,会在哈希表中寻找下一个空位置来存放新元素。常见的开放定址法有线性探测和二次探测。线性探测是从冲突位置开始,依次向后查找空位置;二次探测则是通过更复杂的数学公式来确定下一个探测位置。
  • 再哈希法:使用一个或多个备用哈希函数来处理冲突,将冲突的元素重新映射到哈希表的其他位置。
  • 链地址法(开散列):在哈希表的每个存储位置不是存储元素本身,而是存储一个指向元素的指针,冲突的元素会被添加到一个链表中。这种方法可以很好地处理大量冲突,但可能会导致链表过长,影响性能。
  • 建立公共溢出区:为所有可能的哈希冲突预留一个或多个区域,冲突的元素被存储在这些区域中。

注:降低哈希冲突是提高效率的一个很好的方法

简单哈希

  • 在数组里面存储结构体
  • 定义哈希要有存在的状态定义了枚举
template<class K,class V>
struct Hashdata
{
   
    pair<K, V> _kv;
    state _state = EMPTY;
};
enum state
{
   
    EXIST,
    EMPTY,
    DELETE
};
AI 代码解读

Hash结构

  1. 两个模板参数K V结构
  2. 最后是存储hash的取值,针对整形、浮点型、字符串。进行仿函数的重写
template<class K,class V,class HashFunc = Defaulthashfunc<K>>
class Hash
{
   
public:

private:
    vector<Hashdata<K, V>> _table;
    size_t _n = 0;
};
AI 代码解读

仿函数

  1. 在函数的内容的不确定的时候进行返回。
  2. 针对string字符串的直接进行特模板化。
  3. 针对26字母有不同的组合,要进行字符串的哈希化处理,目的是针对哈希冲突 (本次采用 BKDR算法)参考:字符串哈希算法
template<class K>
struct Defaulthashfunc
{
   
    size_t operator()(const K& key)
    {
   
        return (size_t)key;
    }
};

template<>
struct Defaulthashfunc<string>
{
   
    size_t operator()(const string& str)
    {
   
        size_t hash = 0;
        for (auto e : str)
        {
   
            hash *= 131;
            hash += e;
        }
        return hash;
    }
};
AI 代码解读

插入

  • 将hash表直接扩容,进行哈希计算
  • 进行哈希扩容 : 不建议直接将哈希表填写满,不利于哈数的查找,将哈希的负载因子(已经存储的数据比总空间的大小)设置到0.7到0.8之间即可。
  • 扩容: 重新定义哈希表(扩容后),进行数据的重新插入,进行交换
  • 进行哈希的冲突的解决
    bool insert(const pair<K, V>& kv)
    {
   
        size_t hashi = 0;
        HashFunc hf;
        hashi = hf(kv.first) % _table.size();

        if ((_n * 10 / _table.size()) >= 7)
        {
   
            size_t newsize = _table.size() * 2;
            Hash<K, V, HashFunc> newhash;
            newhash._table.resize(newsize);
            for (int i = 0; i < _table.size(); i++)
            {
   
                if (_table[i]._state == EXIST)
                {
   
                    newhash.insert(_table[i]._kv);
                }
            }

            _table.swap(newhash._table);
        }

        //线性探测
        while (_table[hashi]._state == EXIST)
        {
   
            ++hashi;
            hashi %= _table.size();
        }

        _table[hashi]._kv = kv;
        _table[hashi]._state = EXIST;
        ++_n;

        return true;
    }
AI 代码解读

开放定址法(闭散列)

发生冲突会在哈希表的另外一个空位置寻找。

//线性探测
while (_table[hashi]._state == EXIST)
{
   
    ++hashi;
    hashi %= _table.size();
}

_table[hashi]._kv = kv;
_table[hashi]._state = EXIST;
++_n;

return true;
}
AI 代码解读

查找、删除

查找

返回结构体的指针利于后面的额删除

Hashdata<const K, V>* find(const K& key)
{
   
    HashFunc hf;
    size_t hashi = hf(key) % _table.size();
    while (_table[hashi]._state != EMPTY)
    {
   
        if (_table[hashi]._state == EXIST
            && _table[hashi]._kv.first == key)
        {
   
            return (Hashdata<const K,V>*) & _table[hashi];
        }
        ++hashi;
        hashi %= _table.size();
    }
    return nullptr;
}
AI 代码解读

删除

在删除的时候直接进行状态的修改

bool erase(const K& key)
{
   
    Hashdata<const K, V>* ret = find(key);

    if (ret)
    {
   
        ret->_state = DELETE;
        --_n;
        return true;
    }
    return false;

}
AI 代码解读
目录
打赏
0
3
3
0
36
分享
相关文章
【C++】哈希unordered系列容器的模拟实现
unordered系列容器的底层原理及其模拟实现
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
45 0
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
112 0
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
114 12
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问