核心思路是先用unordered_map统计频次,再用sort按频次降序、同频时ASCII升序排序;比较函数须满足严格弱序,避免未定义行为。

用 std::sort 配合自定义比较函数实现频率排序
核心思路是先统计每个字符出现次数,再按频次降序排列;注意相同频次时需稳定处理(比如按 ASCII 升序),否则 std::sort 可能因相等比较返回 true 导致未定义行为。
实操建议:
- 用
std::unordered_map<char int></char>统计频次,O(n) 时间 - 把字符串转为
std::vector<char></char>或直接对原std::string调用std::sort - 比较函数必须满足严格弱序:若频次不同,按频次降序;频次相同时,按字符升序(避免
a == b && b == c但a != c)
示例关键片段:
std::string s = "tree";
std::unordered_map<char, int> freq;
for (char c : s) freq[c]++;
std::sort(s.begin(), s.end(), [&freq](char a, char b) {
if (freq[a] != freq[b]) return freq[a] > freq[b];
return a < b;
});
// 结果可能是 "eert" 或 "eetr"(取决于原始顺序和 sort 稳定性,但频次顺序确定)
为什么不能直接用 std::map 按频次建索引再拼接
常见误解是“先按频次分组,再从高到低拼字符串”,看似直观,但会丢失同一频次内字符的原始相对顺序,且手动拼接容易出错。
立即学习“C++免费学习笔记(深入)”;
问题点:
-
std::map<int std::vector>></int>的 key 是频次,但多个字符可能同频,vector里字符插入顺序不保证 ASCII 序,输出可能不稳定 - 如果原串有重复字符(如 "aabbcc"),每种字符只应出现一次在分组中,但拼接时得按每个字符的总频次重复——容易漏乘或错位
- 无法处理空格、控制符等非打印字符,除非显式允许
char全范围映射
更安全的做法仍是基于原字符串做原地排序,而非重建。
遇到中文或 UTF-8 多字节字符怎么办
C++ 标准库的 char 和 std::string 对 UTF-8 是透明的,即一个汉字占多个 char,直接按 char 排会拆开字节,结果完全错误。
必须规避的坑:
- 不要对 UTF-8 编码的
std::string直接用上述方法——freq[c]中的c是单个字节,不是逻辑字符 - 若输入含中文,需先用 ICU、utf8cpp 或手动解析 UTF-8 字节序列,提取 Unicode 码点(
char32_t),再统计和排序 - 简单场景下可限定输入为 ASCII,加断言或预检:
std::all_of(s.begin(), s.end(), [](char c) { return (c & 0x80) == 0; })
性能敏感时要注意 freq 查表的开销
在比较函数里反复调用 freq[a] 和 freq[b],每次都是哈希查找,平均 O(1),但常数不小;对长字符串(如 MB 级)+ 高频排序,可能成瓶颈。
优化方式:
- 预生成索引数组:遍历一次字符串,构建
std::vector<:pair int>></:pair>,再按int排序,最后展开成字符串 - 用
std::array<int></int>替代std::unordered_map(仅限纯 ASCII),查表变 O(1) 无冲突 - 避免在比较函数中做任何额外分配或字符串操作——它会被调用 O(n log n) 次
真正卡住的往往不是算法复杂度,而是比较函数里一次没注意的隐式构造或边界检查。


















