【C++ STL】 容器——unordered_set详解

在C++标准库(STL)中,unordered_set 是一个无序集合,它底层采用哈希表实现,提供快速的查找、插入和删除操作。set 不同,unordered_set 不会自动排序元素,而是依据哈希函数存储元素,因此其操作的时间复杂度通常为 O(1)。

1. unordered_set 的基本特点

  • 底层实现:基于哈希表(通常是哈希桶 + 链表或开放地址法)。

  • 元素唯一性:不允许存储重复元素。

  • 无序存储:元素的存储顺序不固定,不支持索引访问。

  • 时间复杂度:平均情况下,查找、插入、删除操作的时间复杂度为 O(1),最坏情况下可能退化为 O(n)。

  • 哈希函数默认使用 std::hash<T>,但可以自定义哈希函数。后面详细有讲。

  • 默认情况下,unordered_set 没有为 pair<int, int> 提供默认的哈希函数和比较函数。需要自定义哈希函数。这个哈希函数可以适用于任意类型的 pair这个哈希函数可以适用于任意类型的 pair,只要 T1 和 T2 有对应的 std::hash 特化

    // 自定义哈希函数
    struct pair_hash {
        template <class T1, class T2> 
        size_t operator() (const pair<T1, T2>& p) const {
            auto hash1 = hash<T1>{}(p.first);  // 哈希第一个元素
            auto hash2 = hash<T2>{}(p.second); // 哈希第二个元素
            return hash1 ^ (hash2 << 1);       // 组合哈希值
        }
    };
    
    
    //后续
    unordered_set<pair<int, int>, pari_hash> T;

    pair_hash 结构体是一个自定义的哈希函数,用于对 pair 类型的对象进行哈希。这个哈希函数的目的是将 pair 的两个元素(first 和 second)组合成一个哈希值,以便可以将 pair 用作 unordered_set 或 unordered_map 的键。
     

    模板参数:
    
    T1 和 T2 是 pair 的两个元素的类型。
    
    这个哈希函数可以适用于任意类型的 pair。
    
    哈希计算:
    
    hash<T1>{}(p.first):使用 std::hash 对 pair 的第一个元素 p.first 进行哈希。
    
    hash<T2>{}(p.second):使用 std::hash 对 pair 的第二个元素 p.second 进行哈希。
    
    组合哈希值:
    
    hash1 ^ (hash2 << 1):将两个哈希值组合在一起。
    
    hash2 << 1:将 hash2 左移一位,目的是避免 hash1 和 hash2 直接异或时可能导致的冲突。
    
    ^:异或操作,将两个哈希值混合在一起。
    #include <iostream>
    #include <unordered_set>
    #include <utility> // for std::pair
    using namespace std;
    
    struct pair_hash {
        template <class T1, class T2>
        size_t operator() (const pair<T1, T2>& p) const {
            auto hash1 = hash<T1>{}(p.first);
            auto hash2 = hash<T2>{}(p.second);
            return hash1 ^ (hash2 << 1);
        }
    };
    
    int main() {
        unordered_set<pair<int, int>, pair_hash> us;
        us.insert({1, 2});
        us.insert({3, 4});
        us.insert({1, 2}); // 不会插入,因为 {1, 2} 已经存在
    
        for (const auto& p : us) {
            cout << "(" << p.first << ", " << p.second << ")\n";
        }
        return 0;
    }

2. unordered_set 的常见操作

2.1 头文件与基本定义

定义和初始化

unordered_set<int> us1; // 定义一个空的unordered_set,存储int类型的元素
unordered_set<int> us2 = {1, 2, 3, 4, 5}; // 使用初始化列表初始化unordered_set
unordered_set<int> us3(us2); // 使用另一个unordered_set初始化

#include <iostream>
#include <unordered_set>
using namespace std;

int main() {
    unordered_set<int> uset = {10, 20, 30, 40};
    
    // 遍历 unordered_set
    for (int val : uset) {
        cout << val << " ";
    }
    return 0;
}

2.2 插入元素

可以使用insert方法向unordered_set中插入元素。由于unordered_set中的元素是唯一的,如果插入的元素已经存在,则插入操作不会生效。

unordered_set<int> uset;
uset.insert(10);
uset.insert(20);
uset.insert(10); // 这个插入操作不会生效,因为10已经存在

2.3 查找元素

可以使用find方法查找unordered_set中的元素。如果元素存在,find会返回指向该元素的迭代器;如果元素不存在,则返回end()迭代器.

if (uset.find(20) != uset.end()) {
    cout << "Found 20";
} else {
    cout << "Not Found";
}

2.4 删除元素

可以使用erase方法删除unordered_set中的元素。erase方法可以接受一个值或一个迭代器作为参数。

unordered_set<int> us = {1, 2, 3, 4, 5};
us.erase(3); // 删除值为3的元素
us.erase(us.begin()); // 删除第一个元素

2.5 遍历 unordered_set

可以使用迭代器 it 遍历unordered_set中的所有元素。由于unordered_set是无序的,遍历时元素的顺序是不确定的。

unordered_set<int> us = {5, 3, 1, 4, 2};
for (auto it = us.begin(); it != us.end(); ++it) {
    cout << *it << " ";
}
// 输出顺序可能为: 1 2 3 4 5 或其他顺序

2.6其他常用操作

  • size():返回unordered_set中元素的数量。

  • empty():判断unordered_set是否为空。

  • clear():清空unordered_set中的所有元素。

 

unordered_set<int> us = {1, 2, 3};
cout << "Size: " << us.size() << endl; // 输出: 3
us.clear();
cout << "Is empty: " << us.empty() << endl; // 输出: 1 (true)

2.7 唯一性

unordered_set中的元素是唯一的,插入重复的元素不会生效。如果需要存储重复的元素,可以使用unordered_multiset。 

unordered_multiset<int> ums = {1, 2, 2, 3, 3, 3};
for (auto it = ums.begin(); it != ums.end(); ++it) {
    cout << *it << " ";
}
// 输出: 1 2 2 3 3 3

 2.8 无序性

unordered_set中的元素存储顺序是无序的,不会按照任何特定的顺序排列。如果需要有序的集合,可以使用set

set<int> s = {5, 3, 1, 4, 2};
for (auto it = s.begin(); it != s.end(); ++it) {
    cout << *it << " ";
}
// 输出: 1 2 3 4 5

 

3. 自定义哈希函数

如果 unordered_set 存储自定义类型,需要提供哈希函数和相等性比较函数。例如:

struct Point {
    int x, y;
    bool operator==(const Point &p) const {
        return x == p.x && y == p.y;
    }
};

struct HashPoint {
    size_t operator()(const Point &p) const {
        return hash<int>()(p.x) ^ (hash<int>()(p.y) << 1);
    }
};

unordered_set<Point, HashPoint> pointSet;

示例:可以定义一个结构体,并重载operator()来指定哈希函数。

struct MyHash {
    size_t operator()(int key) const {
        return key % 10; // 简单的哈希函数
    }
};

unordered_set<int, MyHash> us = {1, 2, 3, 4, 5};
for (auto it = us.begin(); it != us.end(); ++it) {
    cout << *it << " ";
}
// 输出顺序可能为: 1 2 3 4 5 或其他顺序

 

4. 应用场景

  • 去重:快速去除重复元素。

  • 高效查找:O(1) 复杂度的元素查找。

  • 集合操作:可以用于实现集合的并、交、差运算。

5. 总结

unordered_set 是 STL 中高效的无序集合容器,适用于需要高效查找的场景。

由于unordered_set底层使用哈希表实现,因此它的插入、删除和查找操作的平均时间复杂度都是O(1)。这使得unordered_set在处理需要频繁查找和插入的场景时非常高效。然而,unordered_set的内存开销相对较大,因为它需要维护哈希表的结构。

它的时间复杂度通常为 O(1),但由于哈希冲突可能退化到 O(n),因此选择合适的哈希函数至关重要。

在实际开发中,unordered_set可以用于需要快速查找和去重的场景,例如统计单词出现的次数、维护唯一的元素集合等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值