W
e
l
c
o
m
e
: )

位运算的使用场景

1 性能优化的乘除与奇偶判断

对于 2 的幂次的乘除,位移运算比普通乘除法快数十倍(虽然现代编译器会优化,但位运算语义更明确)。

x = 100

# 乘以 2 的 n 次方 → 左移
fast_mul_8 = x << 3  # 相当于 100 * 8 = 800

# 除以 2 的 n 次方(向下取整)→ 右移
fast_div_4 = x >> 2  # 相当于 100 // 4 = 25

偶数二进制最低位一定为 0,奇数数二进制最低位一定为 1。在判断奇偶数时可以用 & 运算来判断,执行效率高。

  • 可读性 vs 性能:在非瓶颈代码中,x % 2 == 0 比 x & 1 更直观。仅在循环极深或底层库中使用位运算优化。
# 奇偶判断(比 x % 2 == 0 更快)
is_even = (x & 1) == 0
is_odd = (x & 1) == 1

[!note] 原理
一个二进制数:01010
在二进制表示中,最低位(第 0 位)对应的是 $2^0=1$。

  • 如果一个整数的最低位是 0,那么它就可以写成 $2×k$ 的形式,因此是偶数;
  • 如果最低位是 1,那么它就可以写成 $2×k+1$ 的形式,因此是奇数。

所以,通过检查最低位就能直接判断奇偶性。

2 权限管理与多状态标志位

用一个整数的不同二进制位表示 开启/关闭 状态,可大幅节省内存并提高检查速度(常见于 Linux 文件权限、Django 模型权限)。

# 定义权限常量(2 的幂,互不干扰)
READ   = 0b100   # 4
WRITE  = 0b010   # 2
EXEC   = 0b001   # 1

# 用户权限:读 + 写
user_perm = READ | WRITE  # 6 (0b110)

# 检查是否有写权限
if user_perm & WRITE:
    print("拥有写权限")

# 添加执行权限
user_perm |= EXEC          # 变为 7 (0b111)

# 移除写权限
user_perm &= ~WRITE        # 变为 5 (0b101)

# 切换(反转)执行权限
user_perm ^= EXEC

3 哈希算法、加密与数据混淆

异或(XOR)是很多加密算法(如 AES 部分步骤、流密码)和哈希(如 FNV-1a)的核心操作,利用其 自反性a ^ b ^ b == a)实现可逆变换。

按位异或(XOR)详解

4 位图(Bitmap)与布隆过滤器

用一个 bit 表示一个元素是否存在,实现极致的空间压缩(10 亿个标志位仅需约 119 MB)。

# 简易位图:记录用户 ID 是否签到
class BitMap:
    def __init__(self, max_val):
        self.bits = bytearray((max_val >> 3) + 1)

    def set(self, num):
        self.bits[num >> 3] |= (1 << (num & 7))

    def test(self, num):
        return bool(self.bits[num >> 3] & (1 << (num & 7)))


bm = BitMap(1000000)
bm.set(88888)
print(bm.test(88888))  # True
print(bm.test(99999))  # False

5 算法竞赛与 LeetCode 中的数学技巧

许多算法题的 最优解 依赖于位运算性质,例如找单身数字、计算汉明重量等。

# ==========找 “只出现一次的数字”==========
l = [1, 2, 1, 3, 2]

# 方法一
res = 0
for num in l:
    res ^= num

print(res)

# 方法二
res1 = reduce(lambda x, y: x ^ y, l)
print(res1)

5.1 计算二进制中 1 的个数(汉明重量)

核心技巧:x &= x - 1

这个方法的核心在于:每次执行该操作,都会把当前二进制数最右边(最低位)的一个 1 变成 0

数学原理与证明

假设任意一个二进制数 x,其最低位的 1 位于第 k 位(从右往左数,最右为第 0 位)。那么 x 可以表示为:

x = ... 1 [k个0]

即:从第 0 位到第 k-1 位全是 0,第 k 位是 1,更高位任意。

当我们计算 x - 1 时:

  • 最低位的 1(第 k 位)因为不够减,需要向高位借位,所以它变成了 0
  • 原本右侧的 k 个 0 因为借位的关系,全部变成了 1
  • 更高位保持不变。

对比图例:

二进制状态 高位部分 第 k 位 低位部分 (k 个位)
x …保持不变… 1 0 0 … 0
x-1 …保持不变… 0 1 1 … 1

接下来执行 x & (x - 1) (按位与运算):

  • 高位部分:与自身相与,保持不变。
  • 第 k 位1 & 0 = 0
  • 低位部分0 & 1 = 0

结论x & (x - 1) 的结果等于将 x 的最低有效 1 置为 0,其余位完全不变。

执行过程演示

以数字 x = 42 (二进制 101010)为例:

循环轮次 当前值 (x) 二进制 操作 x & (x-1) 的计算过程 结果 (新 x) 计数器变化
初始 101010 (42) 0
第 1 次 101010 101010
& 101001
---------
101000
101000 (40) 1
第 2 次 101000 101000
& 100111
---------
100000
100000 (32) 2
第 3 次 100000 100000
& 011111
---------
000000
000000 (0) 3

循环结束,共执行 3 次,恰好等于 42 二进制中 1 的个数。

代码实现与复杂度

def hamming_weight(n: int) -> int:
    count = 0
    while n:
        n &= n - 1
        count += 1
    return count
  • 时间复杂度:O(k),其中 k 是二进制中 1 的个数。对于稀疏的二进制数(如 0b100000000),只循环 1 次,远优于逐位右移 O(log n) 的方法。
  • 现代 Python 最佳实践:Python 3.8+ 可直接调用内置方法 n.bit_count(),由 C 语言底层实现,速度极快。

5.2 判断是否为 2 的整数次幂

核心技巧:n > 0 and (n & (n - 1)) == 0

数学原理

一个数是 2 的整数次幂(即 $2^k$),其二进制表示具有独一无二的结构:

  • 结构1 后面紧跟 k 个 0
  • 举例1 $(2^0)$ = 12 $(2^1)$ = 104 $(2^2)$ = 1008 $(2^3)$ = 1000

基于上一节推导的 x - 1 的性质:

  • 对于 $2^k$(二进制 100..),减去 1 后,那个唯一的 1 变成 0,右侧所有的 0 变成 1
  • 例如 8 (1000) - 1 = 7 (0111)。

此时将两者进行 & 运算:

  1000  (8)
& 0111  (7)
-----------
  0000  (0)

结论:对于任何 2 的幂次方,n & (n - 1) 的结果一定为 0

边界条件与反例

  • 为什么需要 n > 0
    如果 n = 00 & (-1) 在 Python 中结果是 0,会误判为 2 的幂次,但 0 不是 2 的幂。

  • 为什么非 2 的幂次结果一定不为 0?
    如果 n 不是 2 的幂次,其二进制至少包含 两个不同的 1。执行 n & (n-1) 只能消除 最低位的 1,至少还会剩下一个其他的 1,结果必然大于 0。

执行过程对比演示

数值 (n) 二进制表示 n - 1 二进制表示 n & (n - 1) 结果 是否为 2 的幂?
8 1000 0111 0000 (0) ✅ 
12 1100 1011 1000 (8) ❌ 否
0 0000 -1 (全 1) 0000 (0) ❌ 否 (需显式排除)
-8 补码复杂 补码复杂 非零 ❌ 否

代码实现

def is_power_of_two(n: int) -> bool:
    # 必须确保 n 为正整数,并且符合位运算特性
    return n > 0 and (n & (n - 1)) == 0


# 测试
print(is_power_of_two(16))   # True
print(is_power_of_two(18))   # False
print(is_power_of_two(0))    # False
print(is_power_of_two(1))    # True  (2^0)
posted @ 2026-04-18 00:15  挖掘鱼  阅读(34)  评论(0)    收藏  举报