Brian Kernighan 算法
·
一、算法背景
Brian Kernighan 算法是一种高效计算二进制数中 1 的个数(即汉明权重,Hamming Weight)的技巧。相比于逐位检查的常规方法,它只遍历值为 1 的位,而不是所有位,因此在二进制中 1 较少时尤为高效。
二、核心原理
算法的关键在于一个非常巧妙的位运算恒等式:n & (n - 1) 的结果是将 n 的二进制表示中最低位的 1 变为 0。
为什么?
-
对于任意非零整数
n,n - 1会:-
将最右侧的 1 变为 0
-
将该位右边的所有 0 变为 1
-
左侧高位保持不变
-
例如:n = 10100(20)
text
n = 10100 n - 1 = 10011 n & (n-1) = 10000 ← 最低位的 1 被清除了
因此,每次执行 n &= n - 1,就能消除一个 1,循环次数正好等于 1 的个数。
三、代码实现
int countBits(int n) {
int count = 0;
while (n != 0) {
n &= (n - 1);
count++;
}
return count;
}
四、复杂度分析
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(k) | k 为二进制中 1 的个数,远优于固定循环 32/64 次 |
| 空间复杂度 | O(1) | 只使用常数个变量 |
五、边界情况与注意事项
1. 负数的处理
在 Java 中,有符号整数使用补码表示。Kernighan 算法使用 n & (n - 1) 操作,对负数同样有效,因为:
-
n - 1在补码下仍然会将最低位的 1 变为 0,该位右侧的 0 变为 1 -
&操作后,该最低位的 1 被清零,高位保持不变 -
循环
while (n != 0)会在所有 1 被清除后结束(包括符号位) -
// Java 中直接使用 int(包括负数)完全没问题 public static int countBits(int n) { int count = 0; while (n != 0) { n &= (n - 1); count++; } return count; } System.out.println(countBits(-1)); // 补码全1 → 32 System.out.println(countBits(-13)); // 补码中1的个数 → 30
2. n = 0 的情况
循环条件 while (n != 0) 为 false,直接返回 0,结果正确。
System.out.println(countBits(0)); // 输出 0
六、相关位运算技巧
| 表达式 | 效果 | 常见用途 |
|---|---|---|
n & -n |
提取最低位的 1(lowbit) | 树状数组(Fenwick Tree) |
n ^ (n - 1) |
生成从最低位到第一个 1 的掩码 | 位运算扩展 |
n | (n - 1) |
将最低位的 1 之后所有位变为 1 | 位域合并 |
七、示例演示
以 n = 13(二进制 1101)为例:
| 步骤 | 当前 n | n-1 | n & (n-1) | 消除的 1 | 计数 |
|---|---|---|---|---|---|
| 初始 | 1101 | — | — | — | 0 |
| ① | 1101 | 1100 | 1100 | 最低位 | 1 |
| ② | 1100 | 1011 | 1000 | 第二位 | 2 |
| ③ | 1000 | 0111 | 0000 | 最高位 | 3 |
最终 count = 3,即 1101 中有 3 个 1。
八、其他统计方法对比
| 方法 | 循环次数 | 优点 | 缺点 |
|---|---|---|---|
| 逐位检查 | 固定 32/64 次 | 简单直观 | 即使 1 很少也要循环所有位 |
| Kernighan | 等于 1 的个数 | 高效,位数越稀疏越快 | 仍然依赖循环次数 |
| 查表法(分块) | 固定次数(如 4 次查表) | 速度极快 | 需要额外内存 |
内置指令(__builtin_popcount) |
O(1) 硬件级 | 最快 | 非跨平台 |
更多推荐


所有评论(0)