算法-位圖與底層運算邏輯

文章目錄

    • 1. 位圖的理論基礎
    • 2. 完整版位圖實現
    • 3. 底層的運算邏輯-位運算

1. 位圖的理論基礎

首先我們要理解什么是位圖, 位圖的一些作用是什么
位圖法就是bitmap的縮寫。所謂bitmap,就是用每一位來存放某種狀態,適用于大規模數據,但數據狀態又不是很多的情況。通常是用來判斷某個數據存不存在的。在cpp中的STL中有一個bitset容器,其實就是位圖法。其實就是一種為了減少空間而存在存儲數字的一種數據結構
其實學習了位圖之后, 我更愿意稱之為"位映射"
我們基礎的位圖結構包含下面的幾種方法

  1. add方法(向位圖中添加該數字)
  2. remove方法(在位圖中刪除該數字)
  3. reverse/flip方法(在位圖中將該位的數字狀態進行翻轉)
  4. contains方法(檢查位圖中是否有這個數字)

下面是位圖存儲原理的分析
在這里插入圖片描述

位圖可以存儲 0 ~ n 范圍內的數字, 上面線段表示每一個整數的范圍, 一個整數有32個比特位, 所以一個整數可以存儲32個整數的狀態, 比如第一個整數存儲數據的范圍是[0,31],第二個是[32,63], 以此類推…, 記住這里面有一個小點就是我們a / b向上取整的代碼實現是 (a + b - 1) / b
下面是基礎版本位圖的實現


/*** 位圖是一種在指定的連續的范圍上替代哈希表來存儲數字數據的一種數據結構* 構造方法是傳入一個數字n, 可以實現從 [0 , n - 1]范圍上數字的查詢(n個數字)* 數字所需的位置在 set[n / 32] , 數字的指定的位數在 n % 32 (從最右側開始,起始為0)*/
class BitsSet {int[] set;public BitsSet(int n) {//容量需要進行向上的取整, 可以用數學工具類中的ceiling方法進行向上的取整, 也可以用這個表達式// a / b 向上取整的結果是 (a + b - 1) / bthis.set = new int[(n + 31) / 32];}/*** add方法*/public void add(int n) {set[n / 32] = set[n / 32] | (1 << (n % 32));}/*** remove方法(思考為什么用取反不用異或)*/public void remove(int n) {set[n / 32] = set[n / 32] & (~(1 << (n % 32)));}/*** reverse/flip方法(如果有這個數字就remove,如果沒有就add)*/public void reverse(int n) {set[n / 32] = set[n / 32] ^ (1 << (n % 32));}/*** contains方法(檢查是不是有這個數字)*/public boolean contains(int n) {return ((set[n / 32] >>> (n % 32)) & 1) == 1;}
}

2. 完整版位圖實現

在這里插入圖片描述

上面這個leetcode對位圖的完整版的實現, 我們來解析一下這個位圖的具體方法, 其中fix方法就是基礎版本位圖的add方法, unfix其實就是remove方法, flip是一個反轉的方法, 可能很多人覺得真的要將位圖中的所有的元素的狀態都進行翻轉, 其實沒有必要, 而且如果全部都進行反轉是十分消耗資源的, 我們直接采用一種假翻轉的狀態, 即定義一個布爾類型變量reverse來判斷位圖的元素是否進行了翻轉, 反轉之前我們的0代表不存在, 1代表存在, 翻轉之后我們的1代表不存在, 0代表存在, 我們基本屬性有set(位圖的主體), one(1的個數), zero(0的個數), size(元素數量), reverse(是否進行翻轉), 這里很有意思, 我們實現的filp方法直接把 reverse = !reverse , zero跟one 的數值交換即可, 就已經實現了我們的交換的目的, 其實這是一種假交換, 代碼如下圖所示


/*** 自己實現一個完整版本的位圖*/
class Bitset {//基本的數據集合private int[] set;//數據的個數private int size;//1的個數(注意在我們這里不是真實的二進制1的個數)private int one;//0的個數(注意在我們這里不是真實的二進制0的個數)private int zero;//判斷該bits是否進行了翻轉private boolean reverse;public Bitset(int size) {this.size = size;set = new int[(size + 31) / 32];zero = size;one = 0;reverse = false;}public void fix(int idx) {int index = idx / 32;int bit = idx % 32;if (!reverse) {//說明沒有進行翻轉, 此時0表示不存在1表示存在if ((set[index] & (1 << bit)) == 0) {one++;zero--;set[index] = set[index] | (1 << bit);}} else {//此時說明已經進行了翻轉操作if ((set[index] & (1 << bit)) != 0) {one++;zero--;set[index] = set[index] & (~(1 << bit));}}}public void unfix(int idx) {int index = idx / 32;int bit = idx % 32;if (!reverse) {//此時說明沒有發生翻轉, 此時0表示不存在1表示存在if ((set[index] & (1 << bit)) != 0) {one--;zero++;set[index] = set[index] & (~(1 << bit));}} else {if ((set[index] & (1 << bit)) == 0) {one--;zero++;set[index] = set[index] | (1 << bit);}}}public void flip() {reverse = !reverse;zero = zero ^ one;one = zero ^ one;zero = zero ^ one;}public boolean all() {return size == one;}public boolean one() {return one > 0;}public int count() {return one;}//其實就是重寫一下toString方法@Overridepublic String toString() {StringBuilder sbd = new StringBuilder();int index = 0;while (index < size) {int num = set[index / 32];for (int j = 0; j < 32 && index < size; j++) {if (!reverse) {if (((num >>> j) & 1) == 1) {sbd.append(1);} else {sbd.append(0);}index++;} else {if (((num >>> j) & 1) == 1) {sbd.append(0);} else {sbd.append(1);}index++;}}}return sbd.toString();}
}

3. 底層的運算邏輯-位運算

其實計算機的底層實現加減乘除的時候是沒有 + - * / 這些符號的區分的, 其實底層的運算的邏輯都是使用位運算拼接出來的…

3.1 加法

首先闡釋一下加法的運算的原理就是 加法的結果 = 無進位相加的結果( ^ ) + 進位信息( & 與 << ),當進位信息為 0 的時候, 那個無盡為相加的結果, 也就是異或運算的結果就是答案

在這里插入圖片描述

代碼實現如下(不懂得看我們位運算的基礎中關于異或運算的理解)

//因為是進位信息, 所以獲得完了之后要左移一位
public static int add(int a, int b) {int ans = a;while (b != 0) {//ans更新為無盡無進位相加的結果ans = a ^ b;//b更新為進位信息b = (a & b) << 1;a = ans;}return ans;}

3.2 減法

減法得實現更加得簡答, 就是把我們的 a - b ? a + (-b), 轉換為加法進行操作, 代碼實現如下

/*** 生成一個數相反數的方法* 之前我們學過的那個 Brain Kernighan算法中有一個就是 -n == (~n + 1)* 所以計算相反數其實就是 add(~n , 1)*/public static int neg(int n) {return add(~n, 1);}/*** 減法的運算結果其實就是把 減法轉換為加法 比如 a - b = a + (-b);*/public static int sub(int a, int b) {return add(a, neg(b));}

3.3 乘法

乘法的計算方式本質上是類比的我們小學的時候學習的豎式乘法
也就是說, 乘法的本質實現還是依賴的加法, 代碼實現如下

 /*** 乘法的計算方式本質上是類比的我們小學的時候學習的豎式乘法* 也就是說, 乘法的本質實現還是依賴的加法*/public static int mul(int a, int b) {int ans = 0;while (b != 0) {if ((b & 1) == 1) {ans = add(ans, a);}//這里的b一定要是無符號右移(為了避開負數)b = b >>> 1;a = a << 1;}return ans;}

3.4 除法

首先介紹得除法的基本邏輯

位運算實現除法(基礎的邏輯, 但是不完備)
這個是最特殊的位運算的題目, 因為我們要考慮除數與被除數的正負關系(全部都先轉化為正數進行運算)
由于整數的第 31 位是符號位, 所以我們不進行考慮(全部處理為非負), 從30進行考慮
除法的基本邏輯就是 判斷一個數里面是否包含 2^i 次方
x >= y * (2^i), 也就是x的i位是1, 反之就是0, 然后讓 x - y * (2^i) ; 重復此過程直至判斷到最后一位(0位)
所以代碼的基本邏輯就是 x >= y << i ; 但是左移可能會溢出, 所以我們改為右移 ==> (x >> i) >= y;
還有一點就是注意這里一定要先把 a b 賦值給 x y 再進行操作, 否則可能會導致后續的 a b 值發生改變影響結果判斷
代碼實現如下

 public static int div(int a, int b) {//先把 a , b 處理為非負的int x = a < 0 ? neg(a) : a;int y = b < 0 ? neg(b) : b;int ans = 0;for (int i = 30; i >= 0; i = sub(i, 1)) {if ((x >> i) >= y) {ans = ans | (1 << i);//這一步為什么不會溢出其實我暫時也沒懂, 先記下來吧x = sub(x, y << i);}}//注意這里的異或運算也可以作用與布爾類型, 其本質就是0 / 1進行的異或運算return a < 0 ^ b < 0 ? neg(ans) : ans;}

下面的這個才是除法的正確邏輯, 相關注釋在代碼中體現

下面這個才是相對完備的邏輯, 對一些特殊情況進行了處理, 因為除數與被除數有可能沒有相反數(整數最小值越界)

public static final int MIN = Integer.MIN_VALUE;public static int divide(int dividend, int divisor) {//同時是最小值if (dividend == MIN && divisor == MIN) {return -1;}//同時都不是最小值(基本的除法邏輯)if (dividend != MIN && divisor != MIN) {return div(dividend, divisor);}//代碼執行到這里就說明二者中必有一個是最小值, 另一個不是, 此時需要判斷除數是不是-1, 判斷會不會越界if(divisor == neg(1)){return Integer.MAX_VALUE;}if(divisor == MIN){return 0;}//代碼走到這里就只剩下了一種情況, 就是divisor不是-1, dividend是最小值//此時你直接運算取反肯定會溢出, 所以此時進行一些變換操作, 此時(a + b)/(a - b)不會溢出//1. b為正數 ,  a / b == (a + b - b) / b ==> ((a + b) / b) - 1;//2. b為負數 ,  a / b == (a - b + b) / b ==> ((a - b) / b) + 1;dividend = add(dividend, divisor < 0 ? neg(divisor) : divisor);int ans = div(dividend,divisor);int offset = divisor > 0 ? neg(1) : 1;return add(ans,offset);}

上面除法的一些標準來源于leetcode29計算除法
在這里插入圖片描述
謝謝觀看

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/39286.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/39286.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/39286.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python+Pytest+Allure+Yaml+Pymysql+Jenkins+GitLab接口自動化測試框架詳解

PythonPytestAllureYaml接口自動化測試框架詳解 編撰人&#xff1a;CesareCheung 更新時間&#xff1a;2024.06.20 一、技術棧 PythonPytestAllureYamlJenkinsGitLab 版本要求&#xff1a;Python3.7.0,Pytest7.4.4,Allure2.18.1,PyYaml6.0 二、環境配置 安裝python3.7&…

Python 類與對象:深入理解與應用

在 Python 中&#xff0c;類是一種抽象數據類型&#xff0c;用于描述具有相同屬性和方法的對象集合。類通過屬性&#xff08;變量&#xff09;和方法&#xff08;函數&#xff09;來定義對象的行為。對象是類的實例化結果&#xff0c;它可以具備類定義的所有特性。Python 中的類…

ROS2 RQT

1. RQT是什么 RQT是一個GUI框架&#xff0c;通過插件的方式實現了各種各樣的界面工具。 強行解讀下&#xff1a;RQT就像插座&#xff0c;任何電器只要符合插座的型號就可以插上去工作。 2.選擇插件 這里我們可以選擇現有的幾個RQT插件來試一試&#xff0c;可以看到和話題、參…

金蝶云星空字段之間連續觸發值更新

文章目錄 金蝶云星空字段之間連續觸發值更新場景說明具體需求&#xff1a;解決方案 金蝶云星空字段之間連續觸發值更新 場景說明 字段A配置了字段B的計算公式&#xff0c;字段B配置了自動C的計算公式&#xff0c;修改A的時候&#xff0c;觸發了B的重算&#xff0c;但是C觸發不…

【云原生】Kubernetes----ETCD數據的備份與恢復

目錄 引言 一、ETCD數據備份 &#xff08;一&#xff09;確定備份策略 &#xff08;二&#xff09;使用etcdctl工具進行備份 1.安裝etcdctl命令 2.設置ETCDCTL_API環境變量 &#xff08;三&#xff09;執行備份 二、數據還原 &#xff08;一&#xff09;創建新資源 &…

XMind2TestCase:高效測試用例設計工具

XMind2TestCase&#xff1a;高效測試用例設計工具 引言傳統測試用例設計的問題1. Excel表格的局限性2. 傳統測試管理工具的不足3. 自研測試管理工具的挑戰 思維導圖在測試用例設計中的應用思維導圖的優勢思維導圖的挑戰 簡介安裝使用方式命令行調用使用Web界面 使用示例XMind文…

廣州自閉癥機構哪家好

在廣州&#xff0c;眾多的自閉癥康復機構中&#xff0c;星貝育園自閉癥兒童康復學校以其獨特的優勢脫穎而出。 一、專業的師資團隊 我們擁有一支經驗豐富、專業素養極高的師資隊伍。每位老師都經過嚴格的專業培訓&#xff0c;深入了解自閉癥兒童的特點和需求。他們不僅…

蒼穹外賣項目 常用注解 + 動態sql

常用注解 常見的注解解析方法有兩種&#xff1a; 編譯期直接掃描&#xff1a;編譯器在編譯 Java 代碼的時候掃描對應的注解并處理&#xff0c;比如某個方法使用Override 注解&#xff0c;編譯器在編譯的時候就會檢測當前的方法是否重寫了父類對應的方法。運行期通過反射處理&…

SAP_ABAP相關日語單詞

基本概念 1. プログラミング言語 (プログラミングげんご, Puroguramingu gengo) - 編程語言 2. 開発 (かいはつ, Kaihatsu) - 開發 3. システム (システム, Shisutemu) - 系統 4. モジュール (モジュール, Mojūru) - 模塊 5. トランザクションコード (トランザクションコード,…

探索旅游卡項目的八大黃金賽道,你離月入十幾萬僅一步之遙!

作為旅游卡項目的推廣精英&#xff0c;我深知在這個充滿機遇與挑戰的時代&#xff0c;選擇正確的賽道至關重要。今天&#xff0c;我將從定位、內容、產品、流量、變現這五個核心維度出發&#xff0c;為你揭秘旅游卡項目的八大熱門方向。如果你正對旅游充滿熱情&#xff0c;或擁…

【基于R語言群體遺傳學】-3-計算等位基因頻率

書接上文&#xff0c;我們講完了哈代溫伯格基因型頻率&#xff0c;也使用數據進行了擬合&#xff0c;那么接下來就是考慮一些計算的問題&#xff1a; 【基于R語言群體遺傳學】-1-哈代溫伯格基因型比例-CSDN博客 【基于R語言群體遺傳學】-2-模擬基因型&#xff08;simulating …

【leetcode--最小棧】

設計一個支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常數時間內檢索到最小元素的棧。 實現 MinStack 類: MinStack() 初始化堆棧對象。void push(int val) 將元素val推入堆棧。void pop() 刪除堆棧頂部的元素。int top() 獲取堆棧頂部的元素。int get…

TextInput是用于在用戶界面中輸入文本的控件,通常應用于表單、搜索框等需要用戶輸入文字的場景

TextInput是用于在用戶界面中輸入文本的控件&#xff0c;通常應用于表單、搜索框等需要用戶輸入文字的場景。以下是對TextInput的詳細解釋&#xff0c;涵蓋其各個方面的功能和屬性。 基本屬性 text 描述&#xff1a;TextInput中當前顯示的文本。用法&#xff1a;text: "示…

WebKey備受矚目的Web3.0新敘事,硬件與加密生態完美融合特性成為數字世界的新入口

在當今迅速發展的科技領域&#xff0c;Web3.0正在引領一場顛覆性的變革。而作為這一變革的先鋒&#xff0c;WebKey無疑是備受矚目的創新項目。它不僅代表了一種全新的技術趨勢&#xff0c;更是數字世界中硬件與加密生態完美融合的典范。 硬件與加密生態的完美融合 WebKey的核心…

Java基礎面試題(簡單版):

1.java的8個基本數據類型? 整型: byte(占用1個字節) short(占用2個字節) int(占用4個字節) long(占用8個字節) 浮點型: float(占用4個字節)、double(占用8個字節) 字符型: char 布爾型: boolean 2.ArrayList和LinkedList的區別? 可以說ArrayList和LinkedList除了是同屬于集合…

【QT】輸入類控件

目錄 Line Edit 核心屬性 核心信號 正則表達式 示例&#xff1a;使用正則表達式驗證輸入框內容 示例&#xff1a;切換輸入框密碼模式下的顯示狀態 Text Edit 核心屬性 核心信號 示例&#xff1a;獲取多行輸入框的內容同步顯示到label 示例&#xff1a;獲取文本的選…

三生隨記——眉筆詭事

在一個被遺忘的古鎮上&#xff0c;流傳著一個關于眉筆的詭異傳說。這個古鎮坐落在群山的環抱中&#xff0c;鮮少有人知曉它的存在。而在這片土地上&#xff0c;卻有著一件被視為詛咒之源的眉筆。 眉筆的來歷無人知曉&#xff0c;只知它在一夜之間出現在鎮上的古董店中。那支眉筆…

一文講懂npm link

前言 在本地開發npm模塊的時候&#xff0c;我們可以使用npm link命令&#xff0c;將npm 模塊鏈接到對應的運行項目中去&#xff0c;方便地對模塊進行調試和測試 用法 包鏈接是一個兩步過程&#xff1a; 1.為依賴項創建全局軟鏈npm link。一個符號鏈接&#xff0c;簡稱軟鏈&a…

0702_ARM5

練習&#xff1a;使用usart4 main.c #include "uart4.h"int main() {// 初始化 UART4hal_uart4_init();while (1) {// 發送一個字符串//hal_put_char( hal_get_char());hal_put_string(hal_get_string());}return 0; } usart4.c #include "uart4.h"//**…

c# 操作mysql的幫助類

MySqlHelper 的靜態類&#xff0c;其中包含了一些用于執行 MySQL 數據庫操作的方法。這些方法包括執行存儲過程、插入、更新、刪除操作以及執行數據庫事務查詢操作等。 該類中的方法主要有&#xff1a; ExecuteNonQuery 方法&#xff1a;用于執行存儲過程、插入、更新、刪除操…