從零開始的數據結構教程(八)位運算與狀態壓縮


🎩 標題一:位運算基礎——魔術師的二進制手套

位運算是一種直接操作數字二進制位的運算方式,它高效且巧妙,就像魔術師戴上了二進制手套,能夠精準地操控每一個比特。理解位運算是深入學習狀態壓縮和其他底層優化技巧的基礎。

六大核心操作符

假設我們有兩個初始數字: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
繁體地址,請注明出處:http://hk.pswp.cn/diannao/85252.shtml
英文地址,請注明出處:http://en.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開發者都經歷過的噩夢。在沒有充分測試覆蓋的情況下,即使是微小的改動也可能導致系統崩潰。單元測試正是解決這一痛點的最佳實踐,它能幫助我們在…