00 数据结构
1. 数组
2. 链表
3. 栈
4. 队列
5. 树
- 红黑树(自平衡二叉树)
增删改查时间复杂度 O(log n)
二叉搜索树(BST):左子节点 < 父节点 < 右子节点。
- 问题:若插入顺序不当(如从小到大插入),BST 会退化为链表,查找效率从 O(log n) 降为 O(n)。
平衡二叉树(Balanced Binary Tree)
- 核心思想:通过调整树的结构,避免退化为链表,保证左右子树高度差不超过 1。
- 目标:维持操作(插入、删除、查找)的时间复杂度为 O(log n)。
- 常见类型:
- AVL 树:通过旋转(左旋/右旋)保持平衡,高度差 ≤1。
- 红黑树:通过颜色标记和规则约束,近似平衡(高度差 ≤2 倍)。
6. 哈希表
在理想情况下,插入、删除和查找的平均时间复杂度是O(1)。
-
主要:哈希函数:给定一个函数输入,将输入映射到某个值,得出当前元素如何存储(哈希函数示例?)
哈希函数是将键(Key)转换为哈希值(size_t 类型整数)的核心组件。哈希值决定了键值对存储在哈希表的哪个“桶”(bucket)中,每个桶维护一个链表数据结构,解决哈希冲突问题。 -
哈希冲突:不同输入,得到同样哈希值,如何解决?
- 链表存储,元素多了使用树存储(增删改查时间复杂度log(n))。
-
c++ unorder_set 实现
unordered_set是无序的,那么它应该也是基于哈希表的。每个元素本身就是键,不需要关联额外的值。因此,哈希表在这里存储的是单个元素,而不是键值对。当插入一个元素时,计算该元素的哈希值,找到对应的桶,然后将元素存入该桶中。如果发生哈希冲突,比如两个不同的元素有相同的哈希值,那么它们会被放在同一个桶里,通常通过链表连接。 -
c++ unorder_map实现
C++标准库中unordered_set的实现机制。哈希表的基本结构是将键通过哈希函数映射到桶(bucket),然后处理可能的冲突,通常使用链地址法(每个桶是一个链表)或者开放寻址法。对于unordered_map来说,每个键值对被存储在哈希表的某个桶中,键用于计算哈希值,值则与键关联存储。
unordered_map和unordered_set都是基于哈希表,但unordered_map存储的是键值对,而unordered_set只存储键。
/**< Hash 函数实现 unorder_map 示例 **/
// std 库内实现的哈希函数对象模版,在<bits/functional_hash.h> 中定义
namespace std {
template<typename T>
struct hash {
size_t operator()(const T& key) const {
// hash函数实现(若未特化,编译报错)
return __hash__impl<T>::hash(key);
}
};
// 对 int 特化
template<> // 表明是模版特化
struct hash<int> {
size_t operator()(const int& key) const {
return static_cast<size_t>(key);
}
};
};
// unordered_map 实现示例,在<bits/unordered_map.h> 中定义
template<typename Key,
typename Value,
typename Hash = std::hash<key>, // 默认使用 std::hash
typename KeyEqual = std::equal_to<key>>
class unordered_map {
private:
// 桶链表简化版
struct Bucket {
std::list<std::pair<kay, value>> entries;
};
std::vector<Bucket> buckets; // 哈希表桶数组
Hash hasher; // 哈希函数对象
KeyEqual key_eq; // 键相等比较
public:
void insert(const Key& key, const Value& value) {
size_t hash_value = hasher(key);
size_t bucket_idx = hash_value % buckets.size();
// 处理冲突(链表法)
for (auto& entry : buckets[bucket_idx]) {
if (entry.first == key) {
entry.second = value;
return;
}
}
// 保存新元素
buckets[bucket_idx].entries.push_back(std::pair<key, value>);
}
};
对于自定义的键,需要特化hash函数模版或者自定义哈希函数,才能使用unordered_map
补充:c++模版特化
- 在 C++ 中,模板(Template) 允许你编写通用的代码,适用于多种数据类型。但某些类型需要特殊处理,这时就需要 模板特化(Template Specialization) —— 为特定类型定制专门的实现。
- 模板特化是指为特定的数据类型提供一个特定的实现,覆盖通用模板的默认行为。
- 默认情况下可能没有为所有类型实现,特别是用户自定义的类型。当尝试使用一个没有特化
std::hash的自定义类型作为unordered_map或unordered_set的键时,编译器找不到对应的哈希函数,因此会报错。 - 类模板的特化,通常需要重新定义整个类,而不仅仅是某些成员函数。但如果是函数模板的特化,可以只特化某个函数。
- 通过模板特化,你为自定义类型“赋能”,使其无缝融入 C++ 标准库的生态系统。
// 自定义键
/**
为什么需要定义 operator==?
哈希表处理冲突时(不同键映射到同一桶),需要比较键是否真正相等。
**/
class Person {
public:
int age;
std::string name;
// 对于作为键的类,需要重载 ==(用于冲突解决)
bool operator==(const Person& p) const {
return age == p.age && name == p.name;
}
}
// hash 模版特化方式定义哈希函数对象
namespace std {
template<> // 表示这是模板特化
hash<Person> {
size_t operator()(const Person& person) const {
return hash<int>()(person.age) ^ (hash<string>()(person.name) << 1);
}
};
};
std::unordered_map<Persion, int> mp;
// 自定义哈希函数对象
struct HashMy {
size_t operator()(const Person& person) const {
return hash<int>()(person.age) ^( hash<string>()(person.name) << 1);
}
}
// 使用时显示指定哈希函数类型
std::unordered_map<Persion, int, HashMy> mp;
- std::list 双向链表
7. 补充
-
unordered_map中的负载因子和rehashing机制?
-
继承/模版
- 继承:父类实现基本功能,子类对功能进行扩展(继承:基础 -> 扩展)
- 模版:写框架的,对于个别独特的个体,可以通过特化模版,实现模版内功能 (模版特化:框架 -> 个例)
- 如果模版类写的足够详细,可以实现处理不同数据类型的通用类。
- 通俗讲:继承,由粗到细;模版:通用实现(个别特例通过模版特化解决)。

浙公网安备 33010602011771号