C++ unordered_set 使用struct或者class

本文介绍如何将unordered_set应用于strcut或者class

先看看其声明

template < class Key,                        // unordered_set::key_type/value_type
           class Hash = hash<Key>,           // unordered_set::hasher
           class Pred = equal_to<Key>,       // unordered_set::key_equal
           class Alloc = allocator<Key>      // unordered_set::allocator_type
           > class unordered_set;

对于没有特殊需求的non-POD的int、string等类型来说,实用默认的模板参数即可,当我们要使用struct或者class等数据结构作为输入时,则需要一些特定的步骤。

假设我们需要为下面的结构体设置unordered_set容器:

struct record{
    string num;
    string file;
    mutable int count;
    record(string n,string f):num(n),file(f),count(1){}
};

1.指定hasher

将作为模板第二个参数

struct record_hash{
    size_t operator()(const struct record& _r) const {
    string tmp=_r.file+_r.num;
    return std::hash<string>()(tmp);
    }
};

2.指定符合hash函数的operator==重载

bool operator==(const struct  record & X,const struct record & Y)
{
    return hash<string>()(X.num+X.file)==hash<string>()(Y.num+Y.file);
    //or
    //return (Y.num==X.num)&&(Y.file==X.file);
}

完成这两步之后,struct recordunordered_set为容器了:

struct record{
    string num;
    string file;
    mutable int count;
    record(string n,string f):num(n),file(f),count(1){}
};
bool operator==(const struct  record & X,const struct record & Y)
{
    //return hash<string>()(X.num+X.file)==hash<string>()(Y.num+Y.file);
    return (Y.num==X.num)&&(Y.file==X.file);
}


struct record_hash{
    size_t operator()(const struct record& _r) const {
    string tmp=_r.file+_r.num;
    return std::hash<string>()(tmp);
    }
};
int main()
{
    unordered_set<record,record_hash> records;
    records.insert(record("zhang","xiao"));
    record r("zhang","xiao");
    auto it = records.find(r);
    cout<<it->num<<" "<<it->file<<endl;
    return 0;
}

参考

https://stackoverflow.com/questions/15869066/inserting-into-unordered-set-with-custom-hash-function

<think>嗯,用户想了解C++unordered_set的实现原理,特别是数据结构和哈希表相关的部分。首先,我需要回忆一下STL中的unordered_set是怎么设计的。记得unordered_set是基于哈希表的,而哈希表通常由数组和链表组成,也就是开链法处理冲突。不过C++11之后可能有变化,比如使用桶(buckets)和链表或者红黑树?或者还是保持原来的结构? 根据用户提供的引用内容,引用[1]提到哈希表结构,unordered_set存储的是键的列表,每个桶里是键。而unordered_map是键值对的列表。这说明unordered_set的每个桶里存储的是键,而不是键值对。那哈希表的结构应该是一个数组,每个数组元素是一个桶,桶里可能是链表或者其它结构来存放键。 引用[2]提到当键是结构体类型时,需要传递一个仿函数给哈希表,用来计算哈希值和比较相等。这说明unordered_set的模板参数需要处理哈希函数和相等比较,默认情况下可能使用std::hash和std::equal_to。如果键类型不支持默认的哈希,用户需要自己提供。 引用[3]和[4]提到了unordered_map的初始化方法,可能和unordered_set类似,比如使用列表初始化、迭代器初始化等。不过用户的问题是unordered_set的实现,所以需要集中在这方面。 哈希表的基本原理是将键通过哈希函数映射到桶中,处理冲突的方法通常是链地址法,也就是每个桶是一个链表。当插入元素时,计算哈希值找到对应的桶,然后在链表中查找是否已存在该键,如果没有就插入。查找和删除操作类似。 另外,哈希表的负载因子(元素数量/桶数量)超过阈值时会触发重新哈希,即增加桶的数量,重新分配所有元素,这会影响性能但能保持操作的平均时间复杂度。 还需要考虑模板参数,比如unordered_set的模板参数包括键类型、哈希函数类型、键相等比较类型和分配器。默认情况下,哈希函数是std::hash<Key>,相等比较是std::equal_to<Key>。 可能的结构示意图:一个动态数组,每个元素指向一个链表头节点,每个节点包含键和指向下一个节点的指针。插入时,计算哈希值确定桶索引,遍历链表检查是否存在,不存在则添加到链表头部或尾部。 当多个键哈希到同一个桶时,链表可能会变长,影响查找效率,因此当负载因子超过最大值时,会进行rehash,扩大桶的数量,减少冲突。 总结unordered_set的实现要点:基于哈希表,使用链地址法处理冲突,每个桶存储键的链表,动态调整桶的数量,模板参数允许自定义哈希和比较函数。 现在需要将这些内容组织成结构化的回答,分点说明数据结构、哈希表结构、冲突解决、动态扩展、模板参数等部分,并引用用户提供的参考文献。同时生成相关问题,如和unordered_map的区别,如何自定义哈希函数,时间复杂度等。</think>### C++ unordered_set 实现原理详解 #### 1. **数据结构基础** `unordered_set` 是 C++ 标准模板库(STL)中基于**哈希表**实现的容器,其核心结构包括: - **哈希函数**:将键(Key)映射到哈希表的桶(bucket)索引,例如默认使用 `std::hash<Key>`[^2]。 - **桶数组**:动态数组存储多个链表头指针,每个桶对应一个哈希值范围。例如,若哈希函数将键映射到索引 `i`,则键存储在 `bucket[i]` 对应的链表中[^1]。 #### 2. **哈希表结构** - **存储单元**:每个桶中存储的是**键的链表**(而非键值对,这与 `unordered_map` 不同)。 - **冲突解决**:采用**链地址法**(Separate Chaining)。若多个键哈希到同一桶,则以链表形式存储(见图1)。 ```cpp // 示例:桶结构示意图 bucket[0] → Key1 → Key2 → nullptr bucket[1] → Key3 → nullptr ... ``` #### 3. **动态扩展机制** - **负载因子**(Load Factor):定义为 $\frac{\text{元素数量}}{\text{桶数量}}$。当负载因子超过阈值(默认 `1.0`)时,触发**重新哈希(Rehash)**: 1. 创建新的更大的桶数组(通常翻倍)。 2. 重新计算所有键的哈希值并分配到新桶中[^3]。 - 时间复杂度:插入操作均摊为 $O(1)$,最坏情况为 $O(n)$(所有键哈希冲突)[^4]。 #### 4. **模板参数与自定义支持** `unordered_set` 的模板参数包括: ```cpp template< class Key, class Hash = std::hash<Key>, // 哈希函数 class KeyEqual = std::equal_to<Key>, // 键相等比较 class Allocator = std::allocator<Key> > class unordered_set; ``` - **自定义哈希函数**:若键为自定义类型,需提供仿函数实现哈希计算,例如: ```cpp struct MyHash { size_t operator()(const MyClass& obj) const { return std::hash<int>()(obj.id); // 基于成员变量生成哈希值 } }; std::unordered_set<MyClass, MyHash> my_set; ``` #### 5. **核心操作流程** - **插入元素**: 1. 计算键的哈希值,确定桶索引。 2. 遍历桶链表,若键不存在则插入链表末尾。 - **查找元素**: 1. 计算哈希值定位桶。 2. 遍历链表,使用 `KeyEqual` 比较键是否相等。 --- ### § 相关问题 § 1. **`unordered_set` 和 `unordered_map` 在数据结构上有何区别?** - `unordered_set` 存储键的链表,而 `unordered_map` 存储键值对的链表。 2. **如何为自定义类型实现 `unordered_set` 的哈希函数?** - 需定义仿函数并传入模板参数,同时可能需要重载 `operator==`[^2]。 3. **哈希表扩容时的时间复杂度如何?** - 扩容需要重新哈希所有元素,时间复杂度为 $O(n)$,但均摊后插入仍为 $O(1)$。 4. **如何优化 `unordered_set` 的性能?** - 预分配足够桶数量(如 `reserve()`),或调整最大负载因子(`max_load_factor()`)。 --- ### 附图说明 **图1:哈希表示意图** ``` 桶数组索引: 0 1 2 3 ↓ ↓ ↓ ↓ K1 K4 K5 nullptr ↓ K2 ↓ K3 ``` (每个箭头表示链表节点,K 为键)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值