字符熵是衡量字符串中字符分布不确定性的信息论指标,公式为 h = -Σ p_i log₂(p_i),其中 p_i 为第 i 个字符出现的概率;计算分三步:统计字符频次→计算概率→代入公式累加,需跳过零概率项以避免 nan。

什么是字符熵?直接看公式和实现逻辑
字符熵是信息论里的概念,衡量字符串中字符分布的不确定性。公式是 H = -Σ p_i <em> log2(p_i)</em>,其中 p_i 是第 i 个字符在字符串中出现的概率。注意:对数底为 2,结果单位是 bit;概率为 0 的项跳过(因为 0 log2(0) 定义为 0)。
计算分三步:统计频次 → 算概率 → 套公式累加。C++ 没有内置熵函数,得自己写,核心依赖 std::map<char int></char> 或 std::unordered_map 统计,再用 std::log2(需 #include <cmath></cmath>)。
用 std::unordered_map 统计频次最稳妥
std::unordered_map 查找和插入平均 O(1),比 std::map(O(log n))更适合长字符串;且能正确处理空格、换行、控制字符等所有 char 值(包括负值,但要注意:若用 char 作 key,在有符号平台可能出错)。
- 字符类型建议统一转成
unsigned char再转int,避免因char符号扩展导致 map 键错乱 - 空字符串返回 0.0,单字符字符串熵也为 0.0(确定性最高)
- 不区分大小写?得提前用
std::tolower转换,否则 'A' 和 'a' 算两个字符
std::unordered_map<int int> freq;
for (unsigned char c : s) {
freq[static_cast<int>(c)]++;
}
</int></int>
log2(0) 会触发 NaN,必须显式跳过零概率项
C++ 中 std::log2(0.0) 返回 -inf,再乘 0 得 NaN,后续累加就全毁了。不能靠“概率为 0 就不进循环”来躲——因为频次非零,但除法后若总长为 0 才真出问题;更常见的是频次非零但浮点精度下 p_i 极小,log2(p_i) 极大负值,乘法后数值溢出。
- 必须对每个
freq[i]判断是否 > 0,再算p_i = static_cast<double>(freq[i]) / s.size()</double> - 再判断
p_i > 0.0才代入公式,否则跳过 - 用
std::numeric_limits<double>::epsilon()</double>做阈值没必要,直接比 0.0 更清晰安全
double entropy = 0.0;
size_t len = s.size();
for (const auto& pair : freq) {
double p = static_cast<double>(pair.second) / len;
if (p > 0.0) {
entropy -= p * std::log2(p);
}
}
</double>
中文字符或 UTF-8 字符串不能直接用 char 遍历
UTF-8 下一个汉字占 3 字节,按 char 拆会得到三个非法码点,频次统计完全错误。C++20 之前没原生 UTF-8 字符串迭代支持,硬解需要先识别 UTF-8 头字节(0xC0–0xF4),再拼完整码点。
- 如果输入确定是 ASCII(纯英文、数字、标点),
char遍历安全 - 若可能含中文,要么改用
std::u8string+ 第三方库(如 utf8cpp)解码为 Unicode 码点,要么把整个 UTF-8 字节序列当“字节熵”算(即统计每个字节值 0–255 的分布)——这仍是合法定义,只是物理意义变成“字节层不确定性”而非“字符层” - 混用宽字符(
std::wstring)和char更容易出编码转换 bug,不推荐临时切换
实际项目里,多数场景要的是字节熵(比如检测加密数据、压缩率估算),这时直接按 unsigned char 统计反而更鲁棒。
熵计算本身不难,难在边界:空串、单字节、非 ASCII 编码、浮点精度坍塌、字符类型符号性——这些地方一漏,结果就不可信。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











