一篇文章让你熟悉unordered_set及其模拟实现

请添加图片描述

unordered_set的定义

unordered_set 是 C++ 标准库中的一个容器,用于存储唯一的元素,而且不按照任何特定的顺序来组织这些元素。它是基于哈希表实现的,因此可以在平均情况下提供常数时间的插入、删除和查找操作。

以下是使用 unordered_set 定义的基本示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    // 创建一个unordered_set
    std::unordered_set<int> mySet;

    // 向unordered_set中插入元素
    mySet.insert(5);
    mySet.insert(2);
    mySet.insert(8);

    // 查找元素
    if (mySet.find(2) != mySet.end()) {
    
    
        std::cout << "元素 2 存在于unordered_set中" << std::endl;
    }

    // 遍历unordered_set中的元素
    for (const int& value : mySet) {
    
    
        std::cout << value << " ";
    }
    std::cout << std::endl;

    return 0;
}

unordered_set 提供了一种高效的方法来存储和检索不重复的值,但不保留它们的顺序。因此,如果你需要按顺序存储元素,可以考虑使用 std::set 或其他有序容器。

1. unordered_set的模板定义

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;
  1. Key(键/值类型):这是在 unordered_set 中存储的元素类型。例如,如果您想要创建一个存储整数的 unordered_set,则将 Key 设置为 int
  2. Hash(哈希函数类型):这是用于计算元素哈希值的函数对象类型。默认情况下,它是 std::hash<Key>,它提供了针对大多数内置类型的哈希函数。您也可以提供自定义的哈希函数。
  3. Pred(键比较函数类型):这是用于比较元素的函数对象类型。默认情况下,它是 std::equal_to<Key>,它执行标准的等于比较操作。您可以提供自定义的比较函数,以根据自己的需求确定元素是否相等。
  4. Alloc(分配器类型):这是用于分配和释放内存的分配器类型。默认情况下,它是 std::allocator<Key>,用于标准内存管理。您通常只需要在特殊情况下自定义分配器。

2. unordered_set的成员类型

以下别名是unordered_set的成员类型。它们被成员函数广泛用作参数和返回类型
在这里插入图片描述

unordered_set构造函数

empty (1)	
explicit unordered_set ( size_type n = /* see below */,
                         const hasher& hf = hasher(),
                         const key_equal& eql = key_equal(),
                         const allocator_type& alloc = allocator_type() );
explicit unordered_set ( const allocator_type& alloc );

range (2)	
template <class InputIterator>
         unordered_set ( InputIterator first, InputIterator last,
                         size_type n = /* see below */,
                         const hasher& hf = hasher(),
                         const key_equal& eql = key_equal(),
                         const allocator_type& alloc = allocator_type() );

copy (3)	
unordered_set ( const unordered_set& ust );
unordered_set ( const unordered_set& ust, const allocator_type& alloc );

move (4)	
unordered_set ( unordered_set&& ust );
unordered_set ( unordered_set&& ust, const allocator_type& alloc );

initializer list (5)	
unordered_set ( initializer_list<value_type> il,
                size_type n = /* see below */,
                const hasher& hf = hasher(),
                const key_equal& eql = key_equal(),
                const allocator_type& alloc = allocator_type() );
  1. explicit unordered_set(size_type n = /* see below */, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& alloc = allocator_type());
    • 创建一个空的 unordered_set
    • n:哈希表的初始桶数。
    • hf:哈希函数。
    • eql:键的比较函数。
    • alloc:分配器。
  2. template <class InputIterator> unordered_set(InputIterator first, InputIterator last, size_type n = /* see below */, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& alloc = allocator_type());
    • 用迭代器范围 [first, last) 中的元素创建 unordered_set
    • n:哈希表的初始桶数。
    • hf:哈希函数。
    • eql:键的比较函数。
    • alloc:分配器。
  3. unordered_set(const unordered_set& ust);
    • 用另一个 unordered_set 的副本创建一个新的 unordered_set
  4. unordered_set(unordered_set&& ust);
    • 移动构造函数,使用另一个 unordered_set 的内容创建一个新的 unordered_set,并且源 unordered_set 不再包含这些元素。
  5. unordered_set(initializer_list<value_type> il, size_type n = /* see below */, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& alloc = allocator_type());
    • 使用初始化列表 il 创建 unordered_set
    • n:哈希表的初始桶数。
    • hf:哈希函数。
    • eql:键的比较函数。
    • alloc:分配器。

以下是一些使用不同构造函数创建和初始化 std::unordered_set 的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    // 示例 1: 使用默认构造函数创建一个空的 unordered_set
    std::unordered_set<int> mySet1;

    // 示例 2: 使用迭代器范围初始化 unordered_set
    std::unordered_set<int> mySet2({
    
    1, 2, 3, 4, 5}); // 初始化列表
    std::unordered_set<int> mySet3(mySet2.begin(), mySet2.end()); // 从另一个集合复制

    // 示例 3: 使用复制构造函数创建一个新的 unordered_set
    std::unordered_set<int> mySet4(mySet3);

    // 示例 4: 使用移动构造函数创建一个新的 unordered_set
    std::unordered_set<int> mySet5(std::move(mySet4));

    // 示例 5: 使用初始化列表和自定义哈希函数
    std::unordered_set<std::string> mySet6({
    
    "apple", "banana", "cherry"}, 10, std::hash<std::string>(), std::equal_to<std::string>());

    // 遍历并打印 unordered_set 中的元素
    for (const int& value : mySet3) {
    
    
        std::cout << value << " ";
    }
    std::cout << std::endl;

    for (const std::string& fruit : mySet6) {
    
    
        std::cout << fruit << " ";
    }
    std::cout << std::endl;

    return 0;
}

这个示例演示了如何使用不同的构造函数来创建和初始化 std::unordered_set,包括使用默认构造函数、迭代器范围、复制构造函数、移动构造函数和初始化列表。还演示了如何自定义哈希函数和比较函数。

unordered_set赋值运算符重载

copy (1)	
unordered_set& operator= ( const unordered_set& ust );
move (2)	
unordered_set& operator= ( unordered_set&& ust );
initializer list (3)	
unordered_set& operator= ( intitializer_list<value_type> il );

这是关于 std::unordered_set 的不同赋值运算符重载:

  1. unordered_set& operator= ( const unordered_set& ust );
    • 复制赋值运算符。用另一个 unordered_set 的内容替换当前的 unordered_set 内容。
  2. unordered_set& operator= ( unordered_set&& ust );
    • 移动赋值运算符。使用另一个 unordered_set 的内容来替换当前的 unordered_set 内容,并且源 unordered_set 不再包含这些元素。
  3. unordered_set& operator= ( initializer_list<value_type> il );
    • 初始化列表赋值运算符。用初始化列表中的元素来替换当前 unordered_set 的内容。

这些赋值运算符允许您以不同的方式将元素从一个 std::unordered_set 赋值给另一个,包括复制、移动和使用初始化列表。这有助于管理和更新 unordered_set 的内容。

以下是使用不同类型的赋值运算符重载的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    // 示例 1: 复制赋值运算符
    std::unordered_set<int> set1 = {
    
    1, 2, 3};
    std::unordered_set<int> set2;
    set2 = set1; // 复制 set1 到 set2

    std::cout << "set2 after copy assignment: ";
    for (const int& value : set2) {
    
    
        std::cout << value << " ";
    }
    std::cout << std::endl;

    // 示例 2: 移动赋值运算符
    std::unordered_set<std::string> set3 = {
    
    "apple", "banana", "cherry"};
    std::unordered_set<std::string> set4;
    set4 = std::move(set3); // 移动 set3 到 set4

    std::cout << "set3 after move assignment: ";
    for (const std::string& fruit : set3) {
    
    
        std::cout << fruit << " "; // set3 不再包含元素
    }
    std::cout << std::endl;

    std::cout << "set4 after move assignment: ";
    for (const std::string& fruit : set4) {
    
    
        std::cout << fruit << " "; // set4 包含被移动的元素
    }
    std::cout << std::endl;

    // 示例 3: 初始化列表赋值运算符
    std::unordered_set<char> set5;
    set5 = {
    
    'a', 'b', 'c'}; // 使用初始化列表赋值

    std::cout << "set5 after initializer list assignment: ";
    for (const char& letter : set5) {
    
    
        std::cout << letter << " ";
    }
    std::cout << std::endl;

    return 0;
}

这个示例演示了不同类型的赋值运算符重载的用法,包括复制、移动和使用初始化列表赋值。注意,移动赋值后源 unordered_set 不再包含元素。

unordered_set容量函数(Capacity)

  1. bool empty() const noexcept;
    • 这个函数返回一个布尔值,指示集合是否为空。如果集合为空,它将返回 true;否则,返回 false
    • const noexcept 表示这是一个不会抛出异常的常量成员函数。
  2. size_type size() const noexcept;
    • 这个函数返回集合中元素的数量,也就是集合的大小。
    • const noexcept 表示这是一个不会抛出异常的常量成员函数。
    • 返回的类型 size_type 是一个无符号整数类型,通常是 std::size_t
  3. size_type max_size() const noexcept;
    • 这个函数返回集合可能包含的最大元素数量。
    • const noexcept 表示这是一个不会抛出异常的常量成员函数。
    • 返回的类型 size_type 是一个无符号整数类型,通常是 std::size_t

这些函数允许您在使用 std::unordered_set 时查询集合的状态,例如检查是否为空、获取集合的大小以及了解集合可能包含的最大元素数量。由于它们都是常量成员函数并且不会抛出异常,因此可以在安全的情况下调用它们。

以下是使用 std::unordered_set 的成员函数 empty(), size(), 和 max_size() 的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet;

    // 检查集合是否为空
    if (mySet.empty()) {
    
    
        std::cout << "集合为空" << std::endl;
    } else {
    
    
        std::cout << "集合不为空" << std::endl;
    }

    // 添加元素到集合
    mySet.insert(1);
    mySet.insert(2);
    mySet.insert(3);

    // 获取集合的大小
    std::cout << "集合的大小为: " << mySet.size() << std::endl;

    // 获取集合可能包含的最大元素数量
    std::cout << "集合可能包含的最大元素数量为: " << mySet.max_size() << std::endl;

    return 0;
}

这个示例演示了如何使用 empty() 函数来检查集合是否为空,使用 size() 函数来获取集合的大小,以及使用 max_size() 函数来获取集合可能包含的最大元素数量。根据示例中的操作,程序将输出相应的信息。

unordered_set迭代器(Iterators)

1. begin()

container iterator (1)	
      iterator begin() noexcept;
	  const_iterator begin() const noexcept;
bucket iterator (2)	
      local_iterator begin ( size_type n );
	  const_local_iterator begin ( size_type n ) const;
  1. iterator begin() noexcept;
    • 非常量版本的 begin() 函数返回指向集合中第一个元素的迭代器。您可以使用这个迭代器来遍历集合的元素。
    • const noexcept 表示这是一个不会抛出异常的非常量成员函数。
  2. const_iterator begin() const noexcept;
    • 常量版本的 begin() 函数返回指向集合中第一个元素的常量迭代器。这个常量迭代器只允许读取元素,不能修改它们。
    • const noexcept 表示这是一个不会抛出异常的常量成员函数。
  3. local_iterator begin(size_type n);
    • 这个函数用于获取指向桶 n 中第一个局部元素的迭代器。在哈希表中,元素被分布到不同的桶中,local_iterator 允许您在特定桶中遍历元素。
    • size_type n 参数指定桶的索引。
  4. const_local_iterator begin(size_type n) const;
    • 这是常量版本的 begin(size_type n) 函数,返回指向桶 n 中第一个局部元素的常量迭代器。

2. end()

container iterator (1)	
      iterator end() noexcept;
	  const_iterator end() const noexcept;
bucket iterator (2)	
      local_iterator end (size_type n);
	  const_local_iterator end (size_type n) const;
  1. iterator end() noexcept;
    • 非常量版本的 end() 函数返回指向集合末尾的迭代器。它通常用于与 begin() 配合来遍历整个集合的元素。
    • const noexcept 表示这是一个不会抛出异常的非常量成员函数。
  2. const_iterator end() const noexcept;
    • 常量版本的 end() 函数返回指向集合末尾的常量迭代器。这个常量迭代器只允许读取元素,不能修改它们。
    • const noexcept 表示这是一个不会抛出异常的常量成员函数。
  3. local_iterator end(size_type n);
    • 这个函数用于获取指向桶 n 的结束局部元素的迭代器。在哈希表中,元素被分布到不同的桶中,local_iterator 允许您在特定桶中遍历元素。
    • size_type n 参数指定桶的索引。
  4. const_local_iterator end(size_type n) const;
    • 这是常量版本的 end(size_type n) 函数,返回指向桶 n 的结束局部元素的常量迭代器。

3. cbegin()

container iterator (1)	
const_iterator cbegin() const noexcept;
bucket iterator (2)	
const_local_iterator cbegin ( size_type n ) const;
  1. const_iterator cbegin() const noexcept;
    • 这个函数返回指向集合中第一个元素的常量迭代器。与 begin() 类似,但这是一个常量成员函数,只允许读取元素而不能修改它们。
    • const noexcept 表示这是一个不会抛出异常的常量成员函数。
  2. const_local_iterator cbegin(size_type n) const;
    • 这个函数用于获取指向桶 n 中第一个局部元素的常量迭代器。在哈希表中,元素被分布到不同的桶中,const_local_iterator 允许您在特定桶中遍历元素。
    • size_type n 参数指定桶的索引

4. cend()

container iterator (1)	
const_iterator cend() const noexcept;
bucket iterator (2)	
const_local_iterator cend ( size_type n ) const;
  1. const_iterator cend() const noexcept;
    • 这个函数返回指向集合末尾的常量迭代器。与 end() 类似,但这是一个常量成员函数,只允许读取元素而不能修改它们。
    • const noexcept 表示这是一个不会抛出异常的常量成员函数。
  2. const_local_iterator cend(size_type n) const;
    • 这个函数用于获取指向桶 n 的常量局部结束元素的迭代器。在哈希表中,元素被分布到不同的桶中,const_local_iterator 允许您在特定桶中遍历元素。
    • size_type n 参数指定桶的索引。

unordered_set元素查找函数

1. find

iterator find ( const key_type& k );
const_iterator find ( const key_type& k ) const;

std::unordered_set 提供了 find() 成员函数,用于查找指定键值是否存在于集合中,并返回一个迭代器,指向包含该键值的元素(如果存在)或指向 end()(如果不存在)。

以下是 std::unordered_setfind() 函数的两个重载形式:

  1. iterator find(const key_type& k);
    • 这个版本的 find() 函数返回一个迭代器,指向包含键值 k 的元素(如果存在),否则返回指向 end() 的迭代器。
    • 如果要对找到的元素进行修改,可以使用此迭代器。
  2. const_iterator find(const key_type& k) const;
    • 这是常量版本的 find() 函数,返回一个常量迭代器,用于查找包含键值 k 的元素(如果存在),否则返回指向 end() 的常量迭代器。
    • 如果您只需要查找元素而不打算修改它,可以使用此常量迭代器。

这些 find() 函数是在 std::unordered_set 中查找特定键值的常见方式,它们允许你快速确定某个键是否存在于集合中,并让你访问该元素(如果存在)。

以下是使用 std::unordered_setfind() 函数的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet = {
    
    1, 2, 3, 4, 5};

    // 查找特定键值
    int keyToFind = 3;
    auto it = mySet.find(keyToFind);

    // 检查元素是否存在
    if (it != mySet.end()) {
    
    
        std::cout << "键值 " << keyToFind << " 存在于集合中,对应的元素是 " << *it << std::endl;
    } else {
    
    
        std::cout << "键值 " << keyToFind << " 不存在于集合中" << std::endl;
    }

    // 尝试查找一个不存在的键值
    int nonExistentKey = 6;
    auto nonExistentIt = mySet.find(nonExistentKey);

    if (nonExistentIt != mySet.end()) {
    
    
        std::cout << "键值 " << nonExistentKey << " 存在于集合中,对应的元素是 " << *nonExistentIt << std::endl;
    } else {
    
    
        std::cout << "键值 " << nonExistentKey << " 不存在于集合中" << std::endl;
    }

    return 0;
}

在这个示例中,我们使用 find() 函数查找特定的键值(3 和 6),并根据查找结果输出相应的信息。如果键值存在于集合中,find() 返回指向该元素的迭代器,否则返回指向 end() 的迭代器。

2. count

size_type count ( const key_type& k ) const;

std::unordered_set 提供了 count() 成员函数,用于计算指定键值在集合中的出现次数。这个函数接受一个键值作为参数,并返回一个表示键值在集合中出现的次数的整数。

以下是 std::unordered_setcount() 函数的示例用法:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet = {
    
    1, 2, 2, 3, 4, 5, 5, 5};

    // 计算指定键值的出现次数
    int keyToCount = 2;
    std::unordered_set<int>::size_type occurrences = mySet.count(keyToCount);

    std::cout << "键值 " << keyToCount << " 在集合中出现了 " << occurrences << " 次" << std::endl;

    // 计算一个不存在的键值的出现次数
    int nonExistentKey = 6;
    std::unordered_set<int>::size_type nonExistentOccurrences = mySet.count(nonExistentKey);

    std::cout << "键值 " << nonExistentKey << " 在集合中出现了 " << nonExistentOccurrences << " 次" << std::endl;

    return 0;
}

在这个示例中,我们使用 count() 函数来计算指定键值在集合中的出现次数。对于存在的键值(例如2),count() 将返回键值在集合中的实际出现次数,而对于不存在的键值(例如6),count() 将返回0。这个函数对于统计元素出现的次数非常有用。

3. equal_range

pair<iterator,iterator>
   equal_range ( const key_type& k );
pair<const_iterator,const_iterator>
   equal_range ( const key_type& k ) const;

std::unordered_set 提供了 equal_range() 成员函数,用于查找与指定键值相等的元素范围。这个函数返回一个 std::pair,包含两个迭代器,分别指向范围的开始和结束。

以下是 std::unordered_setequal_range() 函数的两个不同重载的示例用法:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet = {
    
    1, 2, 2, 3, 4, 5, 5, 5};

    // 查找与指定键值相等的元素范围
    int keyToFind = 2;
    auto range = mySet.equal_range(keyToFind);

    std::cout << "元素范围,键值 " << keyToFind << ": ";
    for (auto it = range.first; it != range.second; ++it) {
    
    
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 尝试查找一个不存在的键值的元素范围
    int nonExistentKey = 6;
    auto nonExistentRange = mySet.equal_range(nonExistentKey);

    std::cout << "元素范围,键值 " << nonExistentKey << ": ";
    for (auto it = nonExistentRange.first; it != nonExistentRange.second; ++it) {
    
    
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们使用 equal_range() 函数来查找与指定键值相等的元素范围。对于存在的键值(例如2),equal_range() 返回一个包含范围的 std::pair,并使用迭代器遍历该范围。对于不存在的键值(例如6),equal_range() 返回一个范围,但这个范围为空,因此迭代器范围为相等的 begin()end(),不会有元素遍历。这个函数对于查找集合中具有相同键值的元素非常有用。

unordered_set修饰符函数

1. emplace

template <class... Args>
pair <iterator,bool> emplace ( Args&&... args );

emplace() 成员函数,用于通过构造元素并插入集合中来有效地添加新元素。这个函数返回一个 std::pair,其中包含一个迭代器和一个布尔值。

以下是 std::unordered_setemplace() 函数的示例用法:

#include <iostream>
#include <unordered_set>
#include <string>

int main() {
    
    
    std::unordered_set<std::string> mySet;

    // 使用 emplace() 插入新元素
    auto result1 = mySet.emplace("apple");
    auto result2 = mySet.emplace("banana");

    // 检查插入结果
    if (result1.second) {
    
    
        std::cout << "插入成功: " << *result1.first << std::endl;
    } else {
    
    
        std::cout << "插入失败: " << *result1.first << " 已存在" << std::endl;
    }

    if (result2.second) {
    
    
        std::cout << "插入成功: " << *result2.first << std::endl;
    } else {
    
    
        std::cout << "插入失败: " << *result2.first << " 已存在" << std::endl;
    }

    return 0;
}

在这个示例中,我们使用 emplace() 函数向 std::unordered_set 中插入新元素。该函数允许您通过传递构造元素所需的参数来创建元素,而不需要显式地创建对象然后插入。emplace() 返回一个 std::pair,其中的 first 成员是一个迭代器,指向插入的元素(或已存在的元素),而 second 成员是一个布尔值,指示插入是否成功。如果元素已存在,则 emplace() 返回已存在的元素的迭代器和 false,否则返回新插入的元素的迭代器和 true

2. emplace_hint

template <class... Args>
iterator emplace_hint ( const_iterator position, Args&&... args );

emplace_hint() 成员函数,用于在指定位置的提示下(hint)插入新元素。这个函数接受一个常量迭代器 position 作为提示,以及用于构造新元素的参数。

以下是 std::unordered_setemplace_hint() 函数的示例用法:

#include <iostream>
#include <unordered_set>
#include <string>

int main() {
    
    
    std::unordered_set<std::string> mySet = {
    
    "apple", "banana"};

    // 使用 emplace_hint() 在提示位置插入新元素
    std::string hint = "cherry";
    auto hintIterator = mySet.find(hint); // 获取提示位置的迭代器
    if (hintIterator != mySet.end()) {
    
    
        mySet.emplace_hint(hintIterator, "grape");
    } else {
    
    
        std::cout << "提示位置不存在" << std::endl;
    }

    // 输出集合中的元素
    for (const std::string& fruit : mySet) {
    
    
        std::cout << fruit << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们首先获取了一个提示位置的迭代器,然后使用 emplace_hint() 函数在提示位置插入新元素。emplace_hint() 接受一个迭代器 position 作为提示位置,并在此位置之前插入新元素。如果提示位置不存在,可以采取相应的处理措施,如示例中所示。

3. insert

(1)	pair<iterator,bool> insert ( const value_type& val );
(2)	pair<iterator,bool> insert ( value_type&& val );
(3)	iterator insert ( const_iterator hint, const value_type& val );
(4)	iterator insert ( const_iterator hint, value_type&& val );
(5)	template <class InputIterator>
    void insert ( InputIterator first, InputIterator last );
(6)	void insert ( initializer_list<value_type> il );

insert() 成员函数,用于将元素插入集合中。以下是这些 insert() 函数的不同形式:

  1. (1) pair<iterator, bool> insert(const value_type& val);
    • 这个函数将一个值 val 插入到集合中。
    • 如果成功插入了元素,它返回一个包含插入的元素的迭代器和 truestd::pair
    • 如果元素已存在于集合中,它返回一个指向现有元素的迭代器和 false
  2. (2) pair<iterator, bool> insert(value_type&& val);
    • 这个函数用于将一个右值引用 val 插入到集合中。
    • 类似于 (1),如果成功插入了元素,它返回一个包含插入的元素的迭代器和 true
    • 如果元素已存在于集合中,它返回一个指向现有元素的迭代器和 false
  3. (3) iterator insert(const_iterator hint, const value_type& val);
    • 这个函数将一个值 val 插入到集合中,并使用提示位置 hint 来优化插入操作。
    • 返回一个迭代器,指向插入的元素或现有元素(如果元素已存在)。
  4. (4) iterator insert(const_iterator hint, value_type&& val);
    • 这是 (3) 的右值引用版本,类似于 (3),它使用提示位置 hint 来优化插入操作。
  5. (5) template <class InputIterator> void insert(InputIterator first, InputIterator last);
    • 这个函数接受一个迭代器范围 [first, last),并将范围内的元素插入到集合中。
  6. (6) void insert(initializer_list<value_type> il);
    • 这个函数接受一个初始化列表,并将其中的元素插入到集合中。

这些不同形式的 insert() 函数允许您以多种方式将元素插入到 std::unordered_set 中,包括单个元素的插入、使用提示位置的插入、范围插入和初始化列表插入。插入时会检查元素是否已经存在,如果存在,则不会重复插入相同的元素。

以下是一些使用不同形式的 insert() 函数来将元素插入 std::unordered_set 中的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet;

    // (1) 插入单个元素
    auto result1 = mySet.insert(1);
    auto result2 = mySet.insert(2);

    if (result1.second) {
    
    
        std::cout << "成功插入元素 " << *result1.first << std::endl;
    } else {
    
    
        std::cout << "元素 " << *result1.first << " 已存在" << std::endl;
    }

    if (result2.second) {
    
    
        std::cout << "成功插入元素 " << *result2.first << std::endl;
    } else {
    
    
        std::cout << "元素 " << *result2.first << " 已存在" << std::endl;
    }

    // (3) 使用提示位置插入元素
    std::unordered_set<int>::iterator hint = mySet.find(3);
    if (hint != mySet.end()) {
    
    
        mySet.insert(hint, 4);
    } else {
    
    
        std::cout << "提示位置不存在" << std::endl;
    }

    // (5) 范围插入元素
    std::unordered_set<int> anotherSet = {
    
    5, 6, 7};
    mySet.insert(anotherSet.begin(), anotherSet.end());

    // (6) 使用初始化列表插入元素
    mySet.insert({
    
    8, 9, 10});

    // 输出集合中的元素
    for (const int& element : mySet) {
    
    
        std::cout << element << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们使用不同形式的 insert() 函数将元素插入到 std::unordered_set 中。程序输出了每次插入的结果,如果元素已存在,则显示相应的消息。最后,我们遍历集合以查看插入的元素。这演示了如何使用不同的 insert() 函数来操作 std::unordered_set

4. erase

by position (1)	
iterator erase ( const_iterator position );
by key (2)	
size_type erase ( const key_type& k );
range (3)	
iterator erase ( const_iterator first, const_iterator last );

以下是使用不同形式的 erase() 函数从 std::unordered_set 中删除元素的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet = {
    
    1, 2, 3, 4, 5};

    // (1) 通过位置删除元素
    std::unordered_set<int>::const_iterator position = mySet.find(3);
    if (position != mySet.end()) {
    
    
        mySet.erase(position);
    }

    // (2) 通过键值删除元素
    int keyToRemove = 5;
    size_t elementsRemoved = mySet.erase(keyToRemove);
    std::cout << "删除了 " << elementsRemoved << " 个键值为 " << keyToRemove << " 的元素" << std::endl;

    // (3) 通过范围删除元素
    std::unordered_set<int>::const_iterator first = mySet.begin();
    std::unordered_set<int>::const_iterator last = mySet.end();
    mySet.erase(first, last);

    // 输出剩余的元素
    for (const int& element : mySet) {
    
    
        std::cout << element << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们使用不同形式的 erase() 函数删除 std::unordered_set 中的元素。首先,我们通过位置、键值和范围删除了元素,然后输出了删除操作之后的集合内容。这演示了如何使用 erase() 函数来删除元素。

5. clear

void clear() noexcept;

clear() 成员函数,用于清空集合,即删除集合中的所有元素,使其变为空集合。这个函数没有参数,不会抛出异常,并且具有 noexcept 保证,表示它不会引发异常。

以下是使用 clear() 函数的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet = {
    
    1, 2, 3, 4, 5};

    // 输出原始集合内容
    std::cout << "原始集合内容:";
    for (const int& element : mySet) {
    
    
        std::cout << " " << element;
    }
    std::cout << std::endl;

    // 清空集合
    mySet.clear();

    // 输出清空后的集合内容
    std::cout << "清空后的集合内容:";
    for (const int& element : mySet) {
    
    
        std::cout << " " << element;
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们首先输出原始集合的内容,然后使用 clear() 函数清空集合。最后,我们再次输出集合的内容,可以看到清空操作使集合变为空集合。这是一种快速清空集合的方法。

6. swap

void swap ( unordered_set& ust );

swap() 成员函数,用于交换两个集合的内容,即将一个集合的元素和另一个集合的元素互换。

以下是使用 swap() 函数的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> set1 = {
    
    1, 2, 3};
    std::unordered_set<int> set2 = {
    
    4, 5, 6};

    // 输出交换前的集合内容
    std::cout << "交换前 set1:";
    for (const int& element : set1) {
    
    
        std::cout << " " << element;
    }
    std::cout << std::endl;

    std::cout << "交换前 set2:";
    for (const int& element : set2) {
    
    
        std::cout << " " << element;
    }
    std::cout << std::endl;

    // 使用 swap() 函数交换两个集合的内容
    set1.swap(set2);

    // 输出交换后的集合内容
    std::cout << "交换后 set1:";
    for (const int& element : set1) {
    
    
        std::cout << " " << element;
    }
    std::cout << std::endl;

    std::cout << "交换后 set2:";
    for (const int& element : set2) {
    
    
        std::cout << " " << element;
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们首先输出了交换前两个集合的内容,然后使用 swap() 函数交换了它们的内容,最后输出了交换后两个集合的内容。可以看到,集合的内容已经互换。这个函数非常有用,可以用于高效地交换两个集合的内容,而无需复制元素。

unordered_set桶函数

1. bucket_count

size_type bucket_count() const noexcept;

bucket_count() 成员函数,用于获取哈希表中的桶的数量。哈希表是用来实现 std::unordered_set 的底层数据结构,它将元素分布到不同的桶中,以提高查找性能。

以下是使用 bucket_count() 函数的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet = {
    
    1, 2, 3, 4, 5};

    // 获取桶的数量
    std::unordered_set<int>::size_type bucketCount = mySet.bucket_count();

    std::cout << "哈希表中的桶的数量为: " << bucketCount << std::endl;

    return 0;
}

在这个示例中,我们使用 bucket_count() 函数来获取哈希表中的桶的数量,并将其输出到控制台。这个值可以帮助你了解哈希表的大小和性能。请注意,桶的数量可以随着哈希表的调整而变化,以保持性能。

2. max_bucket_count

size_type max_bucket_count() const noexcept;

max_bucket_count() 成员函数,用于获取哈希表支持的最大桶的数量。这个值通常受到实现限制,取决于计算机的硬件和操作系统。

以下是使用 max_bucket_count() 函数的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet = {
    
    1, 2, 3, 4, 5};

    // 获取支持的最大桶的数量
    std::unordered_set<int>::size_type maxBucketCount = mySet.max_bucket_count();

    std::cout << "哈希表支持的最大桶的数量为: " << maxBucketCount << std::endl;

    return 0;
}

在这个示例中,我们使用 max_bucket_count() 函数来获取哈希表支持的最大桶的数量,并将其输出到控制台。这个值是由标准库实现定义的,通常是一个大整数,代表了哈希表可以容纳的最大桶数。这有助于你了解哈希表的容量限制。

3. bucket_size

size_type bucket_size ( size_type n ) const;

bucket_size(size_type n) 成员函数,用于获取指定桶中的元素数量。您需要传递一个桶的索引 n,然后这个函数将返回该桶中元素的数量。

以下是使用 bucket_size() 函数的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet = {
    
    1, 2, 3, 4, 5};

    // 获取第一个桶中的元素数量
    std::unordered_set<int>::size_type bucketSize = mySet.bucket_size(0);

    std::cout << "第一个桶中的元素数量为: " << bucketSize << std::endl;

    return 0;
}

在这个示例中,我们使用 bucket_size() 函数来获取第一个桶中的元素数量,并将其输出到控制台。这可以帮助您了解特定桶中的元素数量,有助于调试和了解哈希表的分布情况。请注意,不同桶中的元素数量可能不同。

4. bucket

size_type bucket ( const key_type& k ) const;

bucket(const key_type& k) 成员函数,用于获取与特定键值 k 相关联的桶的索引。这个函数返回一个 size_type 值,表示键值 k 对应的桶的索引。

以下是使用 bucket() 函数的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet = {
    
    1, 2, 3, 4, 5};

    int keyToFind = 3;

    // 获取键值 3 对应的桶的索引
    std::unordered_set<int>::size_type bucketIndex = mySet.bucket(keyToFind);

    std::cout << "键值 " << keyToFind << " 对应的桶的索引为: " << bucketIndex << std::endl;

    return 0;
}

在这个示例中,我们使用 bucket() 函数来获取键值 3 对应的桶的索引,并将其输出到控制台。这个函数可以帮助您了解特定键值在哈希表中的分布情况,以及它位于哪个桶中。不同键值可能位于不同的桶中。

unordered_set哈希策略函数

1. load_factor

float load_factor() const noexcept;

load_factor() 成员函数,用于获取当前哈希表的负载因子。负载因子是哈希表中元素数量与桶数量的比率。它可以用来衡量哈希表的填充程度,通常用于判断何时需要重新调整哈希表的大小以保持性能。

load_factor() 返回一个 float 值,表示当前的负载因子。

以下是使用 load_factor() 函数的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet = {
    
    1, 2, 3, 4, 5};

    // 获取当前哈希表的负载因子
    float currentLoadFactor = mySet.load_factor();

    std::cout << "当前哈希表的负载因子为: " << currentLoadFactor << std::endl;

    return 0;
}

在这个示例中,我们使用 load_factor() 函数来获取当前哈希表的负载因子,并将其输出到控制台。负载因子可以帮助你监测哈希表的填充程度,根据需要调整哈希表的大小以保持性能。通常,负载因子小于某个阈值时,会触发哈希表的重新调整大小操作。

2. max_load_factor

  1. (1) float max_load_factor() const noexcept;
    • 这个函数用于获取当前哈希表的最大负载因子。最大负载因子是哈希表可以容忍的最大填充程度。默认情况下,它通常是一个较小的浮点数(例如0.75)。
    • 这个函数返回一个 float 值,表示当前哈希表的最大负载因子。
  2. (2) void max_load_factor(float z);
    • 这个函数用于设置哈希表的最大负载因子为指定的值 z
    • 你可以使用这个函数来调整哈希表的最大负载因子,以控制在何时触发哈希表的重新调整大小操作。

以下是使用这两个函数的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet = {
    
    1, 2, 3, 4, 5};

    // 获取当前哈希表的最大负载因子
    float currentMaxLoadFactor = mySet.max_load_factor();
    std::cout << "当前哈希表的最大负载因子为: " << currentMaxLoadFactor << std::endl;

    // 设置新的最大负载因子
    float newMaxLoadFactor = 0.6;
    mySet.max_load_factor(newMaxLoadFactor);

    // 再次获取最大负载因子,检查是否已更新
    float updatedMaxLoadFactor = mySet.max_load_factor();
    std::cout << "更新后的最大负载因子为: " << updatedMaxLoadFactor << std::endl;

    return 0;
}

在这个示例中,我们首先使用 max_load_factor() 函数获取当前的最大负载因子,然后使用 max_load_factor() 函数来设置新的最大负载因子。这允许你根据需要调整哈希表的填充程度,以控制何时触发哈希表的重新调整大小操作。

3. rehash

rehash(size_type n) 成员函数,用于重新调整哈希表的桶数量,以容纳指定数量的桶。这可以用于手动调整哈希表的大小以提高性能或满足特定需求。

以下是使用 rehash() 函数的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet = {
    
    1, 2, 3, 4, 5};

    // 获取当前哈希表的桶数量
    std::unordered_set<int>::size_type currentBucketCount = mySet.bucket_count();
    std::cout << "当前哈希表的桶数量为: " << currentBucketCount << std::endl;

    // 手动调整哈希表的桶数量
    std::unordered_set<int>::size_type newBucketCount = 10;
    mySet.rehash(newBucketCount);

    // 再次获取桶数量,检查是否已更新
    std::unordered_set<int>::size_type updatedBucketCount = mySet.bucket_count();
    std::cout << "更新后的哈希表的桶数量为: " << updatedBucketCount << std::endl;

    return 0;
}

在这个示例中,我们首先使用 bucket_count() 函数获取当前的桶数量,然后使用 rehash() 函数来手动调整哈希表的桶数量。这允许你根据需要控制哈希表的大小,以满足性能或内存需求。调整哈希表的桶数量可能需要重新分布元素,因此可能会影响性能

4. reserve

reserve(size_type n) 成员函数,用于预留足够的桶空间以容纳至少 n 个元素,以提高性能。这个函数的参数 n 表示预留的元素数量,但不是桶的数量。

以下是使用 reserve() 函数的示例:

#include <iostream>
#include <unordered_set>

int main() {
    
    
    std::unordered_set<int> mySet = {
    
    1, 2, 3, 4, 5};

    // 获取当前哈希表的桶数量
    std::unordered_set<int>::size_type currentBucketCount = mySet.bucket_count();
    std::cout << "当前哈希表的桶数量为: " << currentBucketCount << std::endl;

    // 预留足够的桶空间以容纳至少 10 个元素
    std::unordered_set<int>::size_type reserveCount = 10;
    mySet.reserve(reserveCount);

    // 再次获取桶数量,检查是否已更新
    std::unordered_set<int>::size_type updatedBucketCount = mySet.bucket_count();
    std::cout << "更新后的哈希表的桶数量为: " << updatedBucketCount << std::endl;

    return 0;
}

在这个示例中,我们首先使用 bucket_count() 函数获取当前的桶数量,然后使用 reserve() 函数来预留足够的桶空间,以容纳至少 10 个元素。这有助于提高插入和查找操作的性能,因为哈希表不需要频繁地重新调整大小。请注意,桶的数量可能大于指定的元素数量,以确保性能。

unordered_set开散列形式模拟实现

和我上一篇unordered_map博客是用的同一段源代码,模拟实现也类似

HashTable.h全部代码

#pragma once
#include<iostream>
#include<utility>
#include<vector>
#include<string>
using namespace std;
template<class K>
struct HashFunc
{
    
    
	size_t operator()(const K& key)
	{
    
    
		return (size_t)key;
	}
};

template<>
struct HashFunc<string>
{
    
    
	size_t operator()(const string& key)
	{
    
    
		size_t val = 0;
		for (auto ch : key)
		{
    
    
			val *= 131;
			val += ch;
		}

		return val;
	}
};
namespace Bucket
{
    
    
	template<class T>
	struct HashNode
	{
    
    
		T _data;
		HashNode<T>* _next;

		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{
    
    }
	};

	// 前置声明
	template<class K, class T, class Hash, class KeyOfT>
	class HashTable;

	template<class K, class T, class Hash, class KeyOfT>
	struct __HashIterator
	{
    
    
		typedef HashNode<T> Node;
		typedef HashTable<K, T, Hash, KeyOfT> HT;
		typedef __HashIterator<K, T, Hash, KeyOfT> Self;

		Node* _node;
		HT* _pht;

		__HashIterator(Node* node, HT* pht)
			:_node(node)
			, _pht(pht)
		{
    
    }

		T& operator*()
		{
    
    
			return _node->_data;
		}

		T* operator->()
		{
    
    
			return &_node->_data;
		}

		Self& operator++()
		{
    
    
			if (_node->_next)
			{
    
    
				// 当前桶中迭代
				_node = _node->_next;
			}
			else
			{
    
    
				// 找下一个桶
				Hash hash;
				KeyOfT kot;
				size_t i = hash(kot(_node->_data)) % _pht->_tables.size();
				++i;
				for (; i < _pht->_tables.size(); ++i)
				{
    
    
					if (_pht->_tables[i])
					{
    
    
						_node = _pht->_tables[i];
						break;
					}
				}

				// 说明后面没有有数据的桶了
				if (i == _pht->_tables.size())
				{
    
    
					_node = nullptr;
				}
			}

			return *this;
		}

		bool operator!=(const Self& s) const
		{
    
    
			return _node != s._node;
		}

		bool operator==(const Self& s) const
		{
    
    
			return _node == s._node;
		}
	};

	template<class K, class T, class Hash, class KeyOfT>
	class HashTable
	{
    
    
		typedef HashNode<T> Node;

		template<class K, class T, class Hash, class KeyOfT>
		friend struct __HashIterator;
	public:
		typedef __HashIterator<K, T, Hash, KeyOfT> iterator;

		iterator begin()
		{
    
    
			for (size_t i = 0; i < _tables.size(); ++i)
			{
    
    
				if (_tables[i])
				{
    
    
					return iterator(_tables[i], this);
				}
			}

			return end();
		}

		iterator end()
		{
    
    
			return iterator(nullptr, this);
		}

		~HashTable()
		{
    
    
			for (size_t i = 0; i < _tables.size(); ++i)
			{
    
    
				Node* cur = _tables[i];
				while (cur)
				{
    
    
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}

		inline size_t __stl_next_prime(size_t n)
		{
    
    
			static const size_t __stl_num_primes = 28;
			static const size_t __stl_prime_list[__stl_num_primes] =
			{
    
    
				53, 97, 193, 389, 769,
				1543, 3079, 6151, 12289, 24593,
				49157, 98317, 196613, 393241, 786433,
				1572869, 3145739, 6291469, 12582917, 25165843,
				50331653, 100663319, 201326611, 402653189, 805306457,
				1610612741, 3221225473, 4294967291
			};

			for (size_t i = 0; i < __stl_num_primes; ++i)
			{
    
    
				if (__stl_prime_list[i] > n)
				{
    
    
					return __stl_prime_list[i];
				}
			}

			return -1;
		}

		pair<iterator, bool> Insert(const T& data)
		{
    
    
			Hash hash;
			KeyOfT kot;

			// 去重
			iterator ret = Find(kot(data));
			if (ret != end())
			{
    
    
				return make_pair(ret, false);
			}

			// 负载因子到1就扩容
			if (_size == _tables.size())
			{
    
    
				vector<Node*> newTables;
				newTables.resize(__stl_next_prime(_tables.size()), nullptr);
				// 旧表中节点移动映射新表
				for (size_t i = 0; i < _tables.size(); ++i)
				{
    
    
					Node* cur = _tables[i];
					while (cur)
					{
    
    
						Node* next = cur->_next;

						size_t hashi = hash(kot(cur->_data)) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}

					_tables[i] = nullptr;
				}

				_tables.swap(newTables);
			}

			size_t hashi = hash(kot(data)) % _tables.size();
			// 头插
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_size;

			return make_pair(iterator(newnode, this), true);
		}

		iterator Find(const K& key)
		{
    
    
			if (_tables.size() == 0)
			{
    
    
				return end();
			}

			Hash hash;
			KeyOfT kot;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
    
    
				if (kot(cur->_data) == key)
				{
    
    
					return iterator(cur, this);
				}

				cur = cur->_next;
			}

			return end();
		}

		bool Erase(const K& key)
		{
    
    
			if (_tables.size() == 0)
			{
    
    
				return false;
			}

			Hash hash;
			KeyOfT kot;
			size_t hashi = hash(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
    
    
				if (kot(cur->_data) == key)
				{
    
    
					// 1、头删
					// 2、中间删
					if (prev == nullptr)
					{
    
    
						_tables[hashi] = cur->_next;
					}
					else
					{
    
    
						prev->_next = cur->_next;
					}

					delete cur;
					--_size;

					return true;
				}

				prev = cur;
				cur = cur->_next;
			}

			return false;
		}

		size_t Size()
		{
    
    
			return _size;
		}

		// 表的长度
		size_t TablesSize()
		{
    
    
			return _tables.size();
		}

		// 桶的个数
		size_t BucketNum()
		{
    
    
			size_t num = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
    
    
				if (_tables[i])
				{
    
    
					++num;
				}
			}

			return num;
		}

		size_t MaxBucketLenth()
		{
    
    
			size_t maxLen = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
    
    
				size_t len = 0;
				Node* cur = _tables[i];
				while (cur)
				{
    
    
					++len;
					cur = cur->_next;
				}

				if (len > maxLen)
				{
    
    
					maxLen = len;
				}
			}

			return maxLen;
		}

	private:
		vector<Node*> _tables;
		size_t _size = 0; // 存储有效数据个数
	};
};

利用哈希表封装实现unordered_set

#pragma once

#include "HashTable.h"

namespace yulao
{
    
    
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
    
    
		struct SetKeyOfT
		{
    
    
			const K& operator()(const K& key)
			{
    
    
				return key;
			}
		};
	public:
		typedef typename Bucket::HashTable<K, K, Hash, SetKeyOfT>::iterator iterator;

		iterator begin()
		{
    
    
			return _ht.begin();
		}

		iterator end()
		{
    
    
			return _ht.end();
		}

		pair<iterator, bool> insert(const K& key)
		{
    
    
			return _ht.Insert(key);
		}
	private:
		Bucket::HashTable<K, K, Hash, SetKeyOfT> _ht;
	};
}

猜你喜欢

转载自blog.csdn.net/kingxzq/article/details/133707310
今日推荐