C++中利用哈希表实现快速的匹配和查找 :std::unordered_map 或 std::unordered_set 的区别

C++中利用哈希表实现快速的匹配和查找

    • 快速匹配和检索元素的常见方法
    • 哈希表的特点
    • std::unordered_map 或 std::unordered_set 的区别
    • 哈希表unordered_map,如何用多个元素来验证是否存在,
        • 1. 使用 pair:
        • 2. 使用 tuple:
        • 3. 使用自定义的结构体或者类:
      • 数据参考

快速匹配和检索元素的常见方法

  1. 哈希表:
    如前所述,哈希表(例如 std::unordered_map 或 std::unordered_set)是一种非常快速的数据结构,可以在平均 O(1) 的时间内完成匹配和检索。哈希表非常适合用于需要频繁查找的情况。
  2. 排序数组 + 二分查找:
    如果你的数据是静态的(即在初始化后不会再改变),那么你可以将数据存储在一个数组中,并对数组进行排序。然后,你可以使用二分查找算法在 O(logN) 的时间内查找数据。
  3. Trie 树:
    Trie 树(或称前缀树)是一种用于存储字符串的数据结构,它可以快速地查询和匹配字符串前缀。Trie 树是一种多叉树,每个节点代表一个字符,从根到某一节点的路径表示一个字符串。
  4. 平衡查找树:
    例如 AVL 树或红黑树(std::map 和 std::set 的底层实现)。它们能在 O(logN) 的时间内完成插入、删除和查找操作。
  5. 使用标准库函数:
    C++ 的 库提供了一系列算法,如 std::find、std::lower_bound、std::upper_bound 等,可以用于各种数据结构的查找和匹配。

以上哪种方法最适合你,取决于你的具体需求,如数据的大小、是否动态变化、是数值还是字符串、查找的频率等。

哈希表的特点

哈希表是一种基于哈希函数的数据结构,能够以接近常数时间(O(1))对数据进行插入、查找和删除操作。

以 std::unordered_map 为例,这是 C++ 中常见的一种哈希表实现。

插入:

当你向 std::unordered_map 中插入一个元素时,这个元素首先会被哈希函数转化为一个哈希值,这个哈希值决定了元素在哈希表中的位置。因此,不需要遍历整个表就可以找到元素应该插入的位置,所以插入的时间复杂度是 O(1)。

查找:
当你查找一个元素时,这个元素同样会被哈希函数转化为一个哈希值,然后直接在哈希表的这个位置查找元素。所以查找的时间复杂度同样是 O(1)。

删除:
和查找类似,你可以快速定位到要删除的元素,因此删除操作的时间复杂度也是 O(1)。

然而,实际上,这种 O(1) 的时间复杂度是平均时间复杂度。在最坏的情况下,如果多个元素哈希到了同一个位置(这种情况称为哈希冲突),这些元素会以链表的形式存储,此时的查找、插入和删除操作的时间复杂度会退化为 O(N)
但是,如果哈希函数选得好,哈希冲突的概率会很低,所以在实际使用中,哈希表的操作效率通常都接近 O(1)

如果你有一个需要频繁执行查找操作的应用,那么哈希表可能会是一个很好的选择。

std::unordered_map 或 std::unordered_set 的区别

std::unordered_map 和 std::unordered_set 都是 C++ 标准库中的哈希表实现,它们都能在平均 O(1) 时间复杂度内进行插入、查找和删除操作。不过,它们有一些主要的区别:

  • 存储类型:

std::unordered_map 是一个关联容器,每个元素都是一个键值对,键和值可以是不同的类型。键在 unordered_map 中是唯一的。你可以用键来快速找到对应的值。这种数据结构适合于需要建立对象之间一对一映射关系的情况。

std::unordered_map<std::string, int> um;
um["apple"] = 1;
um["banana"] = 2;
int apple_value = um["apple"];  // apple_value is 1

相反,std::unordered_set 只存储单个元素,而不是键值对。它通常用于检查元素是否存在,或者确保元素的唯一性。

std::unordered_set<int> us;
us.insert(1);
us.insert(2);
bool exists = us.find(1) != us.end();  // exists is true

  • 使用场景:
    如果你只关心某个元素是否存在,或者想要去除重复元素,那么 std::unordered_set 就足够了。但如果你需要存储额外的信息(即每个键对应一个值),或者需要通过某个属性(键)来查找某个元素(值),那么 std::unordered_map 更合适。

在内部实现上,std::unordered_map 和 std::unordered_set 都使用哈希表。键或元素的哈希值决定了它在哈希表中的位置。如果两个键或元素的哈希值相同,就会发生哈希冲突,需要用链表等方法解决。

哈希表unordered_map,如何用多个元素来验证是否存在,

如果你需要使用多个元素作为键来检查其是否存在于 unordered_map 中,你可以使用 pair 或者 tuple,或者自定义的结构体或者类作为 unordered_map 的键。不过要注意,你需要自定义相应的哈希函数。以下是一些例子:

1. 使用 pair:
#include 

struct pair_hash {
    template <class T1, class T2>
    std::size_t operator () (const std::pair<T1,T2> &p) const {
        auto h1 = std::hash<T1>{}(p.first);
        auto h2 = std::hash<T2>{}(p.second); 

        // Mainly for demonstration purposes, i.e. works but is overly simple
        // In the real world, use sth. like boost.hash_combine
        return h1 ^ h2;  
    }
};

std::unordered_map<std::pair<int, int>, int, pair_hash> um;

2. 使用 tuple:
#include 

struct tuple_hash {
    template <class T1, class T2, class T3>
    std::size_t operator () (const std::tuple<T1,T2,T3> &t) const {
        auto h1 = std::hash<T1>{}(std::get<0>(t));
        auto h2 = std::hash<T2>{}(std::get<1>(t)); 
        auto h3 = std::hash<T3>{}(std::get<2>(t));

        return h1 ^ h2 ^ h3;  
    }
};

std::unordered_map<std::tuple<int, int, int>, int, tuple_hash> um;

3. 使用自定义的结构体或者类:
#include 

struct my_key {
    int x;
    int y;
    bool operator==(const my_key &other) const {
        return x == other.x && y == other.y;
    }
};

struct my_key_hash {
    std::size_t operator()(const my_key& k) const {
        return std::hash<int>()(k.x) ^ std::hash<int>()(k.y);
    }
};

std::unordered_map<my_key, int, my_key_hash> um;

数据参考

来源 :ChatGPT-4

你可能感兴趣的:(C++从入门到实践,c++,散列表,数据结构)