如果不是为了应付逐渐内卷的面试算法,很多数据结构在工作中是用不到的。
以下是刷题常用的数据结构 API 总结:
1. vector(动态数组)
#include <vector>
#include <algorithm>
using namespace std;
vector<int> vec;
// 增
vec.push_back(1); // 尾部添加
vec.emplace_back(2); // 尾部原地构造(C++11)
vec.insert(vec.begin(), 0); // 指定位置插入
// 删
vec.pop_back(); // 删除尾部
vec.erase(vec.begin()); // 删除指定位置
vec.erase(vec.begin(), vec.begin() + 2); // 删除范围
vec.clear(); // 清空
// 查
vec[0]; // 随机访问(不检查边界)
vec.at(0); // 随机访问(检查边界)
vec.front(); // 第一个元素
vec.back(); // 最后一个元素
// 容量
vec.size(); // 元素个数
vec.empty(); // 是否为空
vec.capacity(); // 容量
vec.reserve(100); // 预留空间
// 其他
sort(vec.begin(), vec.end()); // 排序
reverse(vec.begin(), vec.end()); // 反转
find(vec.begin(), vec.end(), 5); // 查找
2. unordered_map(哈希表)
#include <unordered_map>
using namespace std;
unordered_map<string, int> map;
// 增/改
map["apple"] = 5; // 键不存在则创建,存在则修改
map.insert({"banana", 3}); // 插入
map.emplace("cherry", 7); // 原地构造
// 删
map.erase("apple"); // 按键删除
map.erase(map.begin()); // 按迭代器删除
map.clear(); // 清空
// 查
map["apple"]; // 访问(键不存在会创建)
map.at("apple"); // 访问(键不存在抛异常)
map.find("apple"); // 返回迭代器,找不到返回 end()
map.count("apple"); // 返回1或0
map.contains("apple"); // C++20,返回 bool
// 遍历
for (auto& [key, value] : map) {
cout << key << ": " << value << endl;
}
// 容量
map.size();
map.empty();
3. unordered_set(哈希集合)
#include <unordered_set>
using namespace std;
unordered_set<int> set;
// 增
set.insert(1);
set.emplace(2);
// 删
set.erase(1);
set.erase(set.begin());
set.clear();
// 查
set.find(1); // 返回迭代器
set.count(1); // 返回1或0
set.contains(1); // C++20
// 遍历
for (int num : set) {
cout << num << endl;
}
// 容量
set.size();
set.empty();
4. map(红黑树,有序映射)
#include <map>
using namespace std;
map<string, int> map;
// API 与 unordered_map 基本相同,但有序
map["apple"] = 5;
map["banana"] = 3;
// 额外功能(利用有序性)
map.lower_bound("b"); // 第一个 >= key 的迭代器
map.upper_bound("b"); // 第一个 > key 的迭代器
// 遍历时按键顺序输出
for (auto& [key, value] : map) {
// 按键升序输出
}
5. set(红黑树,有序集合)
#include <set>
using namespace std;
set<int> set;
// API 与 unordered_set 基本相同,但有序
set.insert(3);
set.insert(1);
set.insert(2);
// 额外功能
set.lower_bound(2); // 第一个 >= value 的迭代器
set.upper_bound(2); // 第一个 > value 的迭代器
// 遍历时按值升序输出
for (int num : set) {
// 1, 2, 3
}
6. priority_queue(优先队列)
#include <queue>
#include <functional>
using namespace std;
// 默认最大堆
priority_queue<int> maxHeap;
显式声明是
priority_queue<int, vector<int>, less<int>> maxHeap;
// 最小堆
priority_queue<int, vector<int>, greater<int>> minHeap;
这里比较费解的是,默认的less是大顶堆,帮助理解的方法是,less第一个参数是当前节点,第二个参数是子节点,如果当前节点比子节点小,那么当前节点下沉,大的节点上浮。
这个priority传参需要的是类,实例化的类(greater函数类是模板,需要传)。
与之相反的是 sort 方法,如果你传参greater函数类,它会默认从大到小排序,你还需要构建实例。比如 priority 传的是 less<int>, 但是你用sort方法,你就需要传递 less<int>()。
// 自定义比较器的堆
auto cmp = [](int a, int b) { return a > b; };
priority_queue<int, vector<int>, decltype(cmp)> customHeap(cmp);
// 操作
maxHeap.push(5); // 插入
maxHeap.emplace(3); // 原地构造
maxHeap.top(); // 访问顶部元素(不删除)
maxHeap.pop(); // 删除顶部元素
maxHeap.size();
maxHeap.empty();
7. stack(栈)
#include <stack>
using namespace std;
stack<int> st;
st.push(1); // 入栈
st.emplace(2); // 原地构造
st.top(); // 访问栈顶
st.pop(); // 出栈
st.size();
st.empty();
8. queue(队列)
#include <queue>
using namespace std;
queue<int> q;
q.push(1); // 入队
q.emplace(2); // 原地构造
q.front(); // 队首
q.back(); // 队尾
q.pop(); // 出队
q.size();
q.empty();
9. deque(双端队列)
#include <deque>
using namespace std;
deque<int> dq;
// 两端操作
dq.push_front(1); // 头部插入
dq.push_back(2); // 尾部插入
dq.pop_front(); // 头部删除
dq.pop_back(); // 尾部删除
dq.front(); // 头部元素
dq.back(); // 尾部元素
// 随机访问
dq[0];
dq.at(0);
dq.size();
dq.empty();
刷题常用模式
1. 遍历模式
// vector 遍历
for (int i = 0; i < vec.size(); i++) {}
for (int num : vec) {}
for (auto it = vec.begin(); it != vec.end(); it++) {}
// map/set 遍历
for (auto& [key, val] : map) {}
for (auto it = map.begin(); it != map.end(); it++) {}
2. 查找模式
// vector 查找
auto it = find(vec.begin(), vec.end(), target);
if (it != vec.end()) {}
// map/set 查找
if (map.find(key) != map.end()) {}
if (map.count(key)) {}
if (map.contains(key)) {} // C++20
3. 堆的常用模式
// Top K 问题
priority_queue<int, vector<int>, greater<int>> minHeap;
for (int num : nums) {
minHeap.push(num);
if (minHeap.size() > k) {
minHeap.pop();
}
}
4. 栈的常用模式
// 括号匹配、单调栈等
stack<int> st;
for (char c : s) {
if (c == '(') {
st.push(c);
} else {
if (st.empty()) return false;
st.pop();
}
}
return st.empty();
这些 API 覆盖了刷题中 90% 以上的使用场景,熟练掌握后可以高效解决各种算法问题。
list
C++ 提供了双链表 std::list,它是一个双向链表容器。
1. 基本用法
#include <iostream>
#include <list>
using namespace std;
int main() {
// 创建双链表
list<int> lst = {1, 2, 3, 4, 5};
// 遍历
for (int num : lst) {
cout << num << " ";
}
cout << endl; // 输出: 1 2 3 4 5
return 0;
}
2. 常用 API
头部操作
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> lst;
// 头部操作
lst.push_front(1); // 头部插入: 1
lst.push_front(2); // 头部插入: 2 -> 1
lst.emplace_front(3); // 头部原地构造: 3 -> 2 -> 1
cout << "头部元素: " << lst.front() << endl; // 3
lst.pop_front(); // 删除头部: 2 -> 1
cout << "删除后头部: " << lst.front() << endl; // 2
return 0;
}
尾部操作
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> lst = {1, 2};
// 尾部操作
lst.push_back(3); // 尾部插入: 1 -> 2 -> 3
lst.emplace_back(4); // 尾部原地构造: 1 -> 2 -> 3 -> 4
cout << "尾部元素: " << lst.back() << endl; // 4
lst.pop_back(); // 删除尾部: 1 -> 2 -> 3
cout << "删除后尾部: " << lst.back() << endl; // 3
return 0;
}
插入和删除
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> lst = {1, 2, 3, 4, 5};
// 在指定位置插入
auto it = lst.begin();
advance(it, 2); // 移动到第三个位置
lst.insert(it, 99); // 1 -> 2 -> 99 -> 3 -> 4 -> 5
// 删除指定位置
it = lst.begin();
advance(it, 3);
lst.erase(it); // 删除 3: 1 -> 2 -> 99 -> 4 -> 5
// 删除指定值
lst.remove(2); // 删除所有 2: 1 -> 99 -> 4 -> 5
// 清空
// lst.clear();
for (int num : lst) {
cout << num << " "; // 1 99 4 5
}
cout << endl;
return 0;
}
3. 迭代器操作
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> lst = {1, 2, 3, 4, 5};
// 双向迭代器
cout << "正向遍历: ";
for (auto it = lst.begin(); it != lst.end(); ++it) {
cout << *it << " ";
}
cout << endl;
cout << "反向遍历: ";
for (auto it = lst.rbegin(); it != lst.rend(); ++it) {
cout << *it << " ";
}
cout << endl;
return 0;
}
4. 特殊操作(链表特有)
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> lst1 = {1, 2, 3};
list<int> lst2 = {4, 5, 6};
// 拼接(转移元素,不拷贝)
lst1.splice(lst1.end(), lst2); // lst1: 1->2->3->4->5->6, lst2 为空
cout << "拼接后 lst1: ";
for (int num : lst1) cout << num << " "; // 1 2 3 4 5 6
cout << endl;
cout << "拼接后 lst2 大小: " << lst2.size() << endl; // 0
// 排序
list<int> lst3 = {3, 1, 4, 1, 5, 9, 2};
lst3.sort();
cout << "排序后: ";
for (int num : lst3) cout << num << " "; // 1 1 2 3 4 5 9
cout << endl;
// 去重(需要先排序)
lst3.unique();
cout << "去重后: ";
for (int num : lst3) cout << num << " "; // 1 2 3 4 5 9
cout << endl;
// 反转
lst3.reverse();
cout << "反转后: ";
for (int num : lst3) cout << num << " "; // 9 5 4 3 2 1
cout << endl;
return 0;
}
6. 实际应用:LRU Cache
#include <iostream>
#include <list>
#include <unordered_map>
using namespace std;
class LRUCache {
private:
int capacity;
list<pair<int, int>> cache; // (key, value) 双链表
unordered_map<int, list<pair<int, int>>::iterator> keyToNode;
public:
LRUCache(int cap) : capacity(cap) {}
int get(int key) {
if (keyToNode.find(key) == keyToNode.end()) {
return -1;
}
// 移动到头部(最近使用)
auto it = keyToNode[key];
int value = it->second;
cache.erase(it);
cache.push_front({key, value});
keyToNode[key] = cache.begin();
return value;
}
void put(int key, int value) {
if (keyToNode.find(key) != keyToNode.end()) {
// 键已存在,删除旧位置
cache.erase(keyToNode[key]);
} else if (cache.size() == capacity) {
// 删除最久未使用的(尾部)
int lruKey = cache.back().first;
keyToNode.erase(lruKey);
cache.pop_back();
}
// 插入到头部
cache.push_front({key, value});
keyToNode[key] = cache.begin();
}
void print() {
cout << "LRU Cache: ";
for (auto& [k, v] : cache) {
cout << "(" << k << "," << v << ") ";
}
cout << endl;
}
};
int main() {
LRUCache cache(2);
cache.put(1, 1);
cache.put(2, 2);
cache.print(); // (2,2) (1,1)
cout << "get(1): " << cache.get(1) << endl; // 1
cache.print(); // (1,1) (2,2)
cache.put(3, 3); // 删除 key 2
cache.print(); // (3,3) (1,1)
cout << "get(2): " << cache.get(2) << endl; // -1
return 0;
}
7. 常用 API 总结
| 操作 | 方法 | 时间复杂度 |
|---|---|---|
| 头部操作 | push_front(), emplace_front() | O(1) |
pop_front() | O(1) | |
front() | O(1) | |
| 尾部操作 | push_back(), emplace_back() | O(1) |
pop_back() | O(1) | |
back() | O(1) | |
| 插入删除 | insert() | O(1) |
erase() | O(1) | |
remove() | O(n) | |
| 容量 | size() | O(1) |
empty() | O(1) | |
| 特殊操作 | splice() | O(1) |
sort() | O(n log n) | |
reverse() | O(n) | |
merge() | O(n) |
总结
std::list是 C++ 的标准双链表实现- 优点:任意位置插入删除 O(1),不需要连续内存
- 缺点:不支持随机访问,访问元素需要遍历
- 适用场景:频繁的插入删除,LRU Cache,需要稳定迭代器
在刷题中,std::list 常用于需要频繁在中间插入删除的场景,或者实现 LRU Cache 等数据结构。