tags: C++ STL categories: C++
写在前面
写一下刷题中常用的 C++ 算法库函数 sort() 以及优先队列等需要自己定制比较规则的方法.
#include <algorithm>
utils
输出
template <typename T> // nd-array can use this
ostream& operator<<(ostream& os, const vector<T>& v) {
for (auto i : v) os << i << " ";
return os << '\n';
}
void print_arr(int arr[], size_t n) { // C-style array need length
for (int i{}; i < n; ++i) cout << arr[i] << " ";
cout << '\n';
}
sort() 篇
一般排序
STL 容器
void t1() {
vector<int> v{1, 32, 9, 8, 6, 3};
sort(v.begin(), v.end());
cout << v;
sort(v.rbegin(), v.rend());
cout << v;
// 1 3 6 8 9 32
// 32 9 8 6 3 1
}
C 数组
总体来说还是很方便的, 传入首地址和尾地址, 算是迭代器的向下兼容了
void t2() {
int arr[]{1, 9, 3, 32, 8, 6};
auto n = sizeof(arr) / sizeof(arr[0]);
sort(arr, arr + n); // 顺序(增序)
print_arr(arr, n);
sort(arr, arr + n, greater<>()); // 逆序
print_arr(arr, n);
// 1 3 6 8 9 32
// 32 9 8 6 3 1
}
指定比较规则
这里直接用 auto-lambda, 好像是 C++17 的新语法.
void t3() {
vector<int> v{1, 32, 9, 8, 6, 3};
sort(v.begin(), v.end(),
[](const auto& lhs, const auto& rhs) { return lhs % 4 < rhs % 4; });
cout << v; // 32 8 1 9 6 3
}
双键比较(多用于二维数组, 较常用)
这里举一个最基本的例子, 就是先比较第一个值, 第一个值如果相等, 才比较第二个值.
void t4() {
vector<vector<int>> vv{
{1, 2}, {2, 9}, {2, 3}, {0, 8}, {1, 1} //
};
sort(vv.begin(), vv.end(), [](const auto& lhs, const auto& rhs) {
return lhs[0] < rhs[0] || (lhs[0] == rhs[0] && lhs[1] < rhs[1]);
});
cout << vv;
// 0 8
// 1 1
// 1 2
// 2 3
// 2 9
}
特定结构比较
有时候上面的 lambda 指定比较规则还是略显不足, 这时候就要用仿函数(函数对象)了:
以两个题为例:
class Solution {
public:
string customSortString(string order, string s) {
int val[26]{};
for (int i{}; i < order.size(); ++i) val[order[i] - 'a'] = i;
sort(s.begin(), s.end(),
[&](char& l, char& r) { return val[l - 'a'] < val[r - 'a']; });
return s;
}
};
其实我一开始想到的是哈希计数然后重建字符串, 后来发现可以原地排序.
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
int rank[1001];
memset(rank, -1, sizeof(rank));
for (int i = 0; i < arr2.size(); ++i) rank[arr2[i]] = i;
auto mycmp = [&](int x) {
// 0 表示在 arr2 比较规则中的键,
// 1 表示之后要按升序放在末尾的键
return ~rank[x] ? pair{0, rank[x]} : pair{1, x};
};
sort(arr1.begin(), arr1.end(),
[&](int x, int y) { return mycmp(x) < mycmp(y); });
return arr1;
}
};
事实上这两个题都是计数排序的题, 如果用默认排序函数的话反而不是很快.
优先队列
这里有两种写法, 第一种比较传统, 就是直接写struct
, 里面套operator()
, 这里推荐第二种写法, 就是decltype(cmp)
写法, 看着就舒服.
方法一: 函数对象
struct cmp {
using pii = pair<int, int>;
bool operator()(const pii &lhs, const pii &rhs) const {
return lhs.first < rhs.first; // 这里就是举个例子, 实际上这种简单的比较可以直接用默认比较函数
}
};
void t1() {
priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> pq;
pq.emplace(1, 3);
pq.emplace(4, 2);
pq.emplace(6, 2);
for (; !pq.empty(); pq.pop()) {
auto [k, v] = pq.top();
cout << k << " : " << v << endl;
}
// 6 : 2
// 4 : 2
// 1 : 3
}
题外话: 关于 C++ pair对象比较函数的默认规则:
using pii = pair<int, int>;
void t1() {
vector<pii> v{
{1, 2}, {1, 1}, {3, 2}, {3, 0}, {2, 2} //
};
sort(v.begin(), v.end());
for (auto [p1, p2] : v) cout << p1 << " : " << p2 << endl;
// 1 : 1
// 1 : 2
// 2 : 2
// 3 : 0
// 3 : 2
}
说明 pair 这样的对象在默认比较规则下就是从小到大, 若第一键相同, 则第二键从小到大.
方法二: lambda 函数
注意第二种写法需要构造时候传入比较函数作为参数.
void t2() {
auto cmp = [](const auto &lhs, const auto &rhs) {
return lhs.first < rhs.first; // lambda 比较规则
};
// 算是 decltype 的一个妙用了
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(
cmp);
pq.emplace(1, 3);
pq.emplace(4, 2);
pq.emplace(6, 2);
for (; !pq.empty(); pq.pop()) {
auto [k, v] = pq.top();
cout << k << " : " << v << endl;
}
// 6 : 2
// 4 : 2
// 1 : 3
}
总结
可以说是各有千秋, 不过我还是喜欢 lambda!