我們對於位運算可能既陌生又熟悉。知道其運算方法運算過程,但不能運用好它。
首先,我們還是回顧一下Java中位運算都包含那些操作:
一、與運算(&)
運算法則:將二進制數進行按位與運算。0&0=0;0&1=0;1&1=1 ;
如:0011 & 0010 = 0010;
二、或運算(|)
運算法則:將二進制數進行按位或運算。0|0 =0;1|0 = 1; 1|1=1
如:0011 & 0010 = 0011;
三、異或運算(^)
運算法則:將二進制數進行按位異或。 0^0 = 0; 1^1=0;1^0=1;
如:0011 ^ 0010 = 0001;
四、非運算(~)
運算法則:將二進制數進行按位“取反”操作。 ~0 = 1;~1 = 0;
如:~0011 = 1100
五、左移(有符號左移,無符號左移)、右移
參考上一篇:https://www.cnblogs.com/funmans/p/11179189.html
第六條:“>>”、“>>>”、“<<”都是什么鬼?
首先 >> 、>>> 都是Java中的右移操作,“>>”是有符號右移,“>>>”是無符號右移。顧名思義,有符號須得在右移過程中保持符號,所以,有符號右移,若是正數則在高位補0,反之補1,無符號右移不管正負都補0.
特別注意點:對於char、byte、short等類型。在運算時候會先進行int類型的轉換。都知道int類型4個字節,32位。所以,如果說右移的位數超過了32,那也是沒有用滴。Java采用了取余數的方式來保證右移位數的有效性,即 n>>m相當於 n>>(m%32); 右移相當於做除法操作
“<<”是什么呢?它表示左移操作。那么怎么不見“<<<”這個東西呢?因為左移操作沒有有符號與無符號左移。左移操作的過程就是移除最高位,然后在末尾補0就OK。比如:4,二進制100,當然運算時是3位的,
即 0000 0000 0000 0000 0000 0000 0000 0100,如果進行<<3操作,也就是左移三位,去掉頭上三個0,后面再補上三個:0000 0000 0000 0000 0000 0000 0010 0000,換算成十進制就是32,所以左移也就是乘法運算。同樣的,如果你的左移位數大於32位,其實際左移位數位 對32 取余數的值
好了以上就是基本但位運算法則,接下來我們開始循序漸進的進入第一步:
1、給定一個數我們如何知道這個數M的二進制數的第 N 位是 1 還是 0 呢?
方法:通過與運算以及位移運算,通過將 1 進行左移 n位再進行跟原數據進行與操作。由於我們左移完成的這個數只有第N位是1,其他位置全都是 0 ,所以結果如果等於 0,那么給定數的第N位為0 ,否則為1;
result = M & (1<<N) == 0 ? 0 : 1 ;
2、給定一個數,我們如何去改變這個數二進制表示中具體某一位的值?
這個首先分兩種情況:
將指定數M的二進制數第N為設置為1:
方法:通過或運算以及位移運算,通過將1 進行左移 N位,在進行與 M進行或運算,由於 0|0 = 0;0|1 = 1;所以,低位是不會改變的。
result = M | (1<<N);
將指定數M的二進制數第N位設置為0:
方法:通過與運算以及位移運算,將1 進行左移N位再取反或-1,然后與上M
result = M & ~(1<<N) 或者 result = M & ((1<<M) -1);
接下來我們進入一個簡單的算法階段:
問題:給定一個整型數組,求這個數組中的第二大元素?
可能很多同學第一眼看到這個有點懵,一想平時都是求數組中的最大的那個,怎么求第二大呢,略一思索后,得到一個方案:我先對這個數組進行排序,然后還不是想取第幾大就取第幾大。OK,當然排序是來解決這個問題是可以的,但是確實有點問題復雜化了。
實際上我們可能只需要對數組進行一次遍歷操作就OK了。(PS:當然肯定還有復雜度更低的方法,這里只是做一個簡單舉例說明)
/** * 求第二大元素 * @param data * @return */ public int max(int[] data){ int temp = data[0]; int max = data[0]; for (int i = 1; i < data.length; i++) { if(data[i]>temp){ max = temp; temp = data[i]; } else if(data[i] > max && data[i] != temp){ max = data[i]; } } return max; }
OK,上面的代碼如果只是考慮求取第二大元素,當然也能勉強使用。但是我們寫代碼肯定是要考慮通用性的,那么如果問題換成求取整型數組中的第N大元素或者第N小問題,又該如何呢?
既然本篇將到了位運算,當然需要用到了,基本思路如下:
1、獲取元素最大最小值
2、初始化一個二進制數空間用其每一位來標記數組中的一個數,存在(1)、不存在(0)
3、通過遍歷數組進行二進制數每一位的的賦值
4、通過對二進制數求第N個不為0的位數,來獲取第N大或第N小
/** * 求第N小 * @param data * @param index * @return */ public int minN(int[] data,int index){ int max,min; max = min = data[0]; //求最大最小值 for (int datum : data) { max = Math.max(max,datum); min = Math.min(min,datum); } //初始化標記空間 int bitSpace = 1<<(max - min +1); //遍歷數組進行空間標記.標記過程就是將bitSpace中的響應位置值設置為1 for (int datum : data) { bitSpace |= 1<<(datum - min); } //獲取bitSpace的index位置值 for (int i = 0; i < max-min+1; i++) { if((bitSpace & 1<<i) != 0){ index --; } if(index <= 0){ return i + min; } } return 0; }
以上程序就利用了二進制數據的的位運算來進行編程,利用二進制的 0 1,表示某個數據的存在與不存在。利用位運算進行二進制位的賦值、取值操作。
當然針對上述算法來說,還是又很多缺陷的。
比如:上述不能夠針對同一值進行多次標記。
總結:數字的位運算操作在算法中一般的使用場景都是用來作為標記使用。針對上一問題,如果需要針對同一值進行多次標記,則可將bitSpace換成數組(所占空間更大),但思想不變;再比如大名鼎鼎的布隆過濾器,也是標記。
》》》如有不當之處,歡迎批評指正。