从零开始的数据结构教程(八)位运算与状态压缩


🎩 标题一:位运算基础——魔术师的二进制手套

位运算是一种直接操作数字二进制位的运算方式,它高效且巧妙,就像魔术师戴上了二进制手套,能够精准地操控每一个比特。理解位运算是深入学习状态压缩和其他底层优化技巧的基础。

六大核心操作符

假设我们有两个初始数字:x = 5(二进制 0101)和 y = 3(二进制 0011)。

  • x & y按位与 (AND)0001 (1)

    • 规则:只有对应的两个二进制位都为 1 时,结果位才为 1
    • 应用:清零特定位、判断某位是否为 1x & (1 << i))。
  • x | y按位或 (OR)0111 (7)

    • 规则:对应的两个二进制位中只要有一个为 1,结果位就为 1
    • 应用:设置特定位为 1x | (1 << i))。
  • x ^ y按位异或 (XOR)0110 (6)

    • 规则:对应的两个二进制位不同时,结果位为 1
    • 应用:翻转特定位(x ^ (1 << i))、不使用额外变量交换两个数寻找数组中唯一出现的数字
  • ~x按位取反 (NOT)1010 (-6)

    • 规则:将所有二进制位 0 变为 11 变为 0
    • 注意:对于有符号整数,结果是其补码表示,这通常会导致负数。
  • x << 1左移 (Left Shift)1010 (10)

    • 规则:将 x 的所有二进制位向左移动 1 位,低位补 0
    • 应用:相当于乘以 2 1 2^1 21x << n 相当于 x × 2 n x \times 2^n x×2n)。
  • x >> 1右移 (Right Shift)0010 (2)

    • 规则:将 x 的所有二进制位向右移动 1 位,高位补 0(对于无符号数)或补符号位(对于有符号数)。
    • 应用:相当于除以 2 1 2^1 21 并向下取整(x >> n 相当于 x ÷ 2 n x \div 2^n x÷2n)。

高频技巧

  • 判断奇偶

    # 如果 x 的最低位是 1,则 x 是奇数
    x & 1 == 1
    
  • 取最低位的 1 (lowbit)

    • lowbit = x & -x:这个技巧利用了负数的补码表示,能够有效地提取出 x 二进制表示中最右边的 1 及其后面的 0
    • 例如:x = 6 (0110),-x 在补码中是 1010
      6 & -6 (0110 & 1010) → 0010 (2)。
    • 应用:树状数组 (Fenwick Tree)、某些优化算法。
  • 消去最低位的 1

    • x &= x - 1:每次执行这个操作,都会将 x 二进制表示中最右边的 1 变为 0
    • 例如:x = 6 (0110),x - 15 (0101)。
      6 & 5 (0110 & 0101) → 0100 (4)。
    • 应用:计算一个数二进制中 1 的个数 (汉明重量)

🧩 标题二:状态压缩——用数字表示集合

状态压缩是一种巧妙的技巧,它利用二进制位的特性,将一个集合或一组布尔状态压缩成一个整数。每个二进制位代表一个元素的“存在”或“不存在”、“已选”或“未选”。

场景比喻

想象你有一组物品 {A, B, C}。你可以用三位二进制数来表示这些物品的状态:

  • 最低位代表 A (001)
  • 中间位代表 B (010)
  • 最高位代表 C (100)

那么,集合 {A, C} 就可以被表示为二进制 101,其十进制值为 5

集合操作

假设 S = 0b101(表示集合 {A, C})。

  • 添加元素 B:将第 1 位(从右往左数,0-indexed)设置为 1

    S |= (1 << 1) # S → 0b111 (7),表示集合 {A, B, C}
    
  • 移除元素 A:将第 0 位设置为 0

    S &= ~(1 << 0) # S → 0b110 (6),表示集合 {B, C}
    
  • 检查元素 C 是否存在

    if S & (1 << 2): # S 为 0b110,(1 << 2) 为 0b100。 0b110 & 0b100 → 0b100 (非零)print("C 存在")
    

经典例题:旅行商问题 (TSP)

旅行商问题是一个 NP 难问题,但对于小规模问题,可以使用状态压缩动态规划来解决。

  • 问题:给定 n 个城市和城市之间的距离,从一个城市出发,访问每个城市一次,最后回到起始城市,求最短的总距离。
def tsp(dist):n = len(dist) # 城市数量# dp[mask][u] 表示:已经访问过的城市集合为 mask,且当前停留在城市 u 的最短路径长度# mask 是一个二进制数,其中第 i 位为 1 表示城市 i 已访问dp = [[float('inf')] * n for _ in range(1 << n)]# 初始化:从城市 0 出发,只访问了城市 0,停留在城市 0,路径长度为 0dp[1][0] = 0 # 1 << 0 是 1 (二进制 00...01),表示只访问了城市 0# 遍历所有可能的状态 (mask)# mask 从 1 开始,到 (1 << n) - 1 (即所有城市都访问过的状态)for mask in range(1, 1 << n):# 遍历当前状态 mask 下,可能停留的城市 ufor u in range(n):# 确保城市 u 在 mask 中 (即城市 u 已经被访问过)if not (mask & (1 << u)):continue # 如果城市 u 不在 mask 中,则跳过# 遍历所有可能的下一个城市 vfor v in range(n):# 如果城市 v 已经在 mask 中 (即城市 v 已经被访问过),则跳过if mask & (1 << v):continue# 如果从城市 u 到城市 v 的距离是有效的 (不是无穷大)if dist[u][v] == float('inf'):continue# 计算新的 mask (包含了城市 v)new_mask = mask | (1 << v)# 更新 dp[new_mask][v]:# 从 dp[mask][u] 加上从 u 到 v 的距离dp[new_mask][v] = min(dp[new_mask][v], dp[mask][u] + dist[u][v])# 最终结果:# 遍历所有“所有城市都访问过”的状态 (mask = (1 << n) - 1)# 找到从某个城市 u 结束,再回到起始城市 0 的最短路径min_total_dist = float('inf')full_mask = (1 << n) - 1 # 所有城市都被访问过的 maskfor u in range(n):if dist[u][0] != float('inf'): # 确保能从 u 返回城市 0min_total_dist = min(min_total_dist, dp[full_mask][u] + dist[u][0])return min_total_dist# 示例:4个城市,距离矩阵
# dist[i][j] 表示从城市 i 到城市 j 的距离
# 注意:如果两个城市之间没有直达路径,可以表示为 float('inf')
# 城市 0 -> 1 -> 2 -> 3 -> 0
# 0-1: 10, 0-2: 15, 0-3: 20
# 1-0: 10, 1-2: 35, 1-3: 25
# 2-0: 15, 2-1: 35, 2-3: 30
# 3-0: 20, 3-1: 25, 3-2: 30
# 这是一个对称矩阵的例子,实际可能不对称
# 距离矩阵示例 (4个城市)
# from math import inf
# distances = [
#     [0, 10, 15, 20],
#     [10, 0, 35, 25],
#     [15, 35, 0, 30],
#     [20, 25, 30, 0]
# ]
# print(tsp(distances)) # 预期输出 80 (0->1->3->2->0 的路径)

⚡ 标题三:位运算实战——布隆过滤器与汉明重量

位运算在实际工程中有很多高效的应用,特别是在数据去重、统计等场景。

布隆过滤器 (Bloom Filter) 原理

  • 概念:一种空间效率极高的概率型数据结构,用于判断一个元素是否可能存在于一个集合中。它允许一定程度的误判(“存在”可能实际上不存在),但绝不会误判(“不存在”就一定不存在)。

  • 工作方式

    1. 初始化一个所有位都为 0位数组(或称位图)。
    2. 添加元素时,通过多个独立的哈希函数将元素映射到位数组中的多个位置,并将这些位置的位设置为 1
    3. 查询元素时,再次通过这些哈希函数计算出对应的位位置。如果所有这些位置的位都为 1,则认为该元素可能存在;只要有一个位为 0,则该元素一定不存在。
  • 应用场景

    • 海量数据去重:如网页爬虫判断 URL 是否已爬取。
    • 快速判断黑名单:如垃圾邮件过滤、防止缓存穿透。

汉明重量 (Hamming Weight) / 位计数

  • 问题:计算一个无符号整数的二进制表示中 1 的个数(LeetCode 191)。
def hammingWeight(n: int) -> int:count = 0# 循环直到 n 变为 0while n:# 每次执行 n &= (n - 1) 都会消去 n 最右边的 1n &= (n - 1)count += 1return count# 示例
# print(hammingWeight(11)) # 11 (00001011) -> 3
# print(hammingWeight(6))  # 6 (00000110) -> 2
  • 应用场景
    • 特征去重:为用户行为、内容等生成二进制指纹,通过汉明距离(两个数字二进制位不同的数量)判断相似度。
    • 数据压缩密码学等领域。

🃏 标题四:状态压缩 DP——扑克牌游戏策略

状态压缩不仅可以用于表示集合,还可以用于表示游戏或其他问题的状态,进而结合动态规划解决一些博弈论问题或复杂搜索问题。

例题:翻转游戏 (LeetCode 293)

  • 问题:给定一个只包含 '+''-' 的字符串 s。你可以进行任意次操作:选择两个连续的 ++ 并将它们翻转成 --。无法进行任何操作的人输。假设你是先手,判断你是否能赢。
def canWin(s: str) -> bool:memo = {} # 使用备忘录存储已计算过的字符串状态,避免重复计算# dfs 函数:判断在当前字符串 s 的状态下,先手玩家能否赢def dfs(current_s):if current_s in memo: # 如果当前状态已经计算过,直接返回结果return memo[current_s]# 遍历所有可能的翻转操作for i in range(len(current_s) - 1):if current_s[i:i+2] == "++": # 找到可以翻转的 "++"# 尝试进行翻转,生成新状态next_s = current_s[:i] + "--" + current_s[i+2:]# 递归调用 dfs(next_s),判断对手在 next_s 状态下能否赢# 如果对手不能赢 (即 `not dfs(next_s)` 为 True),# 那么当前玩家就能赢if not dfs(next_s):memo[current_s] = True # 标记当前状态为可赢return True # 找到一个赢的路径,立即返回# 如果遍历完所有可能的翻转操作,都没有找到能赢的路径memo[current_s] = False # 标记当前状态为不可赢return Falsereturn dfs(s) # 从初始字符串开始判断
  • 状态压缩优化:如果字符串长度较小,可以将字符串 s 转换为一个二进制数(例如,'+'1'-'0),这样就可以用整数来作为 memo 的键,进一步提高效率和空间利用率。这种问题通常被称为“状态压缩 DP”或“记忆化搜索”。

📊 总结表:位运算魔法手册

技巧代码实现示例应用场景
集合表示`mask = (1 << i)(1 << j)`
快速幂x <<= 1 代替 x *= 2数值计算加速,在底层库中常见
交换变量a ^= b; b ^= a; a ^= b不使用额外空间交换两个变量的值
找不同数字xor = reduce(lambda x,y:x^y, nums)LeetCode 136 (只出现一次的数字)
判断奇偶x & 1 == 1效率高于 x % 2 != 0
消去最低位1n &= (n - 1)计算汉明重量 (二进制中 1 的个数)
获取最低位1lowbit = x & -x树状数组 (Fenwick Tree) 实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/85252.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GraalVM加持下的Quarkus极速启动

1. 引言 1.1 Quarkus与云原生时代的挑战 随着云原生架构的普及,传统Java应用在部署效率、资源消耗和冷启动性能方面逐渐暴露出短板。Spring Boot等框架虽然功能强大,但在Serverless、边缘计算等场景下表现乏力。 Quarkus 是 Red Hat 推出的一个专为云原生设计的 Java/Kotl…

vue3 el-input type=“textarea“ 字体样式 及高度设置

在Vue 3中&#xff0c;如果你使用的是Element Plus库中的<el-input>组件作为文本域&#xff08;type"textarea"&#xff09;&#xff0c;你可以通过几种方式来设置字体样式和高度。 1. 直接在<el-input>组件上使用style属性 你可以直接在<el-input&…

Matlab中gcb、gcbh、gcs的区别

gcb&#xff1a;返回当前选中模块的完整路径名&#xff08;字符串&#xff09; gcbh&#xff1a;返回当前选中模块的句柄&#xff08;数值标识符&#xff09; gcs&#xff1a;返回当前打开或选中的子系统或顶层模型路径&#xff08;字符串&#xff09;

大语言模型的技术原理与应用前景:从Transformer到ChatGPT

目录 摘要 1. 引言 2. Transformer架构核心原理 2.1 自注意力机制 2.2 位置编码 2.3 前馈神经网络 3. 从GPT到ChatGPT的演进 3.1 GPT系列模型架构 3.2 训练流程优化 4. 应用场景与案例分析 4.1 代码生成 4.2 文本摘要 4.3 问答系统 5. 挑战与未来方向 5.1 当前技…

Flink Table API 编程入门实践

Flink Table API 编程入门实践 前言 Apache Flink 是目前大数据实时计算领域的明星产品&#xff0c;Flink Table API 则为开发者提供了声明式、类似 SQL 的数据处理能力&#xff0c;兼具 SQL 的易用性与编程 API 的灵活性。本文将带你快速了解 Flink Table API 的基本用法&am…

Android之ListView

1&#xff1a;简单列表(ArrayAdapter) 1&#xff1a;运行的结果&#xff1a; 2&#xff1a;首先在MyListView里面创建一个按钮&#xff0c;点击的时候进行跳转。 这里让我吃惊的是&#xff0c;Button里面可以直接设置onClick .java里面的方法。 也即是点击这个按钮之后就会去…

Python(十四)

1.type函数和init_subclass_ init_subclass_ 2.元类 类就是用来创建对象的模版&#xff0c;类是由type创造而来的&#xff0c;元类就是创建类的模版&#xff0c;type可以用来创造类&#xff0c;因为type本身就是一个元类&#xff0c;使用元类来创造类&#xff0c;元类之间也有…

当前用户的Git全局配置情况:git config --global --list

通过config命令可以查询当前用户的全局配置情况。这些配置项定义了 Git 在全局范围内的行为&#xff0c;包括如何处理大文件、SSL 证书验证以及提交时的用户信息。 git config --global --list http.sslVerifyfalse 这个配置项禁用了 SSL 证书验证。这在与自签名证书的 Git 服…

负载均衡群集---Haproxy

目录 一、HAproxy 一、概念 二、核心作用 三、主要功能特性 四、应用场景 五、优势与特点 二、 案例分析 1. 案例概述 2. 案例前置知识点 &#xff08;1&#xff09;HTTP 请求 &#xff08;2&#xff09;负载均衡常用调度算法 &#xff08;3&#xff09;常见的 web …

html5视频播放器和微信小程序如何实现视频的自动播放功能

在HTML5中实现视频自动播放需设置autoplay和muted属性&#xff08;浏览器策略要求静音才能自动播放&#xff09;&#xff0c;并可添加loop循环播放、playsinline同层播放等优化属性。微信小程序通过<video>组件的autoplay属性实现自动播放&#xff0c;同时支持全屏按钮、…

OpenHarmony定制系统组合按键(一)

一、开发环境 系统版本&#xff1a;OpenHarmony 4.0.10.13 设备平台&#xff1a;rk3568 SDK版本&#xff1a;fullSDK 4.0.10.13 DevEco Studio版本&#xff1a;4.1.0.400 二、需求背景 定制OpenHarmony 系统组合按键功能&#xff0c;例如仿Android Power VOL_Up组合键实现截…

相机定屏问题分析四:【cameraserver 最大request buffer超标】后置视频模式预览定屏闪退至桌面

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:相机定屏问题分析三:【配流ConfigStream失败】外屏打开相机视频照片人像来回切换后,相机页面卡死,点击没反应9055522 这一篇我们开始讲: 相机定屏问题分析四:【cameraserver 最大request buffer超…

从 PyTorch 到 TensorFlow Lite:模型训练与推理

一、方案介绍 研发阶段&#xff1a;利用 PyTorch 的动态图特性进行快速原型验证&#xff0c;快速迭代模型设计。 灵活性与易用性&#xff1a;PyTorch 是一个非常灵活且易于使用的深度学习框架&#xff0c;特别适合研究和实验。其动态计算图特性使得模型的构建和调试变得更加直…

4.2.5 Spark SQL 分区自动推断

在本节实战中&#xff0c;我们学习了Spark SQL的分区自动推断功能&#xff0c;这是一种提升查询性能的有效手段。通过创建具有不同分区的目录结构&#xff0c;并在这些目录中放置JSON文件&#xff0c;我们模拟了一个分区表的环境。使用Spark SQL读取这些数据时&#xff0c;Spar…

数据结构:导论

目录 什么是“第一性原理”&#xff1f; 什么是“数据结构”&#xff1f; 数据结构解决的根本问题是什么&#xff1f; 数据结构的两大分类 数据结构的基本操作 数据结构与算法的关系 学习数据结构的底层目标 什么是“第一性原理”&#xff1f; 在正式进入数据结构之前&…

汽车制造场景下Profibus转Profinet网关核心功能与应用解析

在当今工业自动化的浪潮中&#xff0c;各种通讯协议层出不穷&#xff0c;而其中PROFIBUS与PROFINET作为两种主流的工业通信标准&#xff0c;它们之间的转换需求日益增长。特别是对于那些希望实现老旧设备与现代化网络无缝对接的企业来说&#xff0c;一个高效、稳定的网关产品显…

qt ubuntu 20.04 交叉编译

一、交叉编译环境搭建 1.下载交叉编译工具链&#xff1a;https://developer.arm.com/downloads/-/gnu-a 可以根据自己需要下载对应版本&#xff0c;当前最新版本是10.3, 笔者使用10.3编译后的glibc.so版本太高&#xff08;glibc_2.3.3, glibc_2.3.4, glibc_2.3.5&#xff09;…

在Babylon.js中创建3D文字:简单而强大的方法

引言 在3D场景中添加文字是许多WebGL项目的常见需求。Babylon.js提供了多种创建3D文字的方法&#xff0c;其中使用TextBlock结合平面网格是一种简单而高效的方式。本文将介绍如何使用Babylon.js的GUI系统在3D空间中创建美观的文字效果。 方法概述 Babylon.js的GUI系统允许我…

油桃TV v20250519 一款电视端应用网站聚合TV播放器 支持安卓4.1

油桃TV v20250519 一款电视端应用网站聚合TV播放器 支持安卓4.1 应用简介&#xff1a; 油桃TV是一款开源电视端应用网站聚合浏览器&#xff0c;它把大家常见需求的一些网站都整合到了这个应用上&#xff0c;并进行了电视端…

Perl单元测试实战指南:从Test::Class入门到精通的完整方案

阅读原文 前言:为什么Perl开发者需要重视单元测试? "这段代码昨天还能运行,今天就出问题了!"——这可能是每位Perl开发者都经历过的噩梦。在没有充分测试覆盖的情况下,即使是微小的改动也可能导致系统崩溃。单元测试正是解决这一痛点的最佳实践,它能帮助我们在…