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