Java千問:Java位運算經典應用(四)

接上篇

七、判斷某數是不是2的N次冪

我們知道,10的0次冪是1,1次冪是10,2次冪是100...仔細觀察一下這些數,你就會發現一個規律,那就是:這些數字當中,開頭是1,後面N位上的數字全部是0。這是我們用十進制表示數字所得到的一個規律。同理,如果用二進制表示數字的話,那麼對於2的N次冪也有相同的規律。用二進制表示2的0次冪爲1,2的1次冪爲10,2的2次冪爲100...規律很明顯,也1開頭,後面N位都是0。
我們利用這個規律,就可以判斷一個數字是不是2的N次冪。具體實現辦法也很簡單,假設一個數字爲a,我們用”a減去1的值”與”a自身”做一個按位與運算,如果運算結果爲0,那麼說明a就是2的N次冪。爲什麼呢?就是因爲2的N次冪減1之後,開頭的那個1會因爲“退位”變成0,而後面的0都會變成1,這樣,與原來的a做按位與的運算,恰好是1和0相對,運算結果自然爲0。
我們可以用一個具體的數字來證明,比如說2的3次方,也就是數字8,如果用二進制來表示的話就是”1000”,而8-1=7,7用二進制來表示就是”0111”,這兩個二進制串的每一個位恰好是0和1相對,如果它們做按位與的運算,其結果恰好爲0。我們根據這個運算結果是否爲0就能判斷出某個數是不是2的N次冪。完整的判斷代碼如下:

public static void main(String[] args)  {
    int a1 = 5;
    int a2 = 8;
    boolean f1 = (a1&(a1-1))==0;
    boolean f2 = (a2&(a2-1))==0;
    System.out.println(a1+"是2的N次冪:"+f1);
    System.out.println(a2+"是2的N次冪:"+f2);
}

八、求一個集合的所有子集

所謂集合的子集,就是一個集合的部分元素所形成的集合。子集中有兩種特殊情況,分別是空集和該集合自身。
一個包含n個元素的集合,恰好可以用一個n位的二進制數來表示它的每個元素有沒有出現在子集中。0表示沒出現,1表示出現。比如說一個集合{a,b,c},我們就可以用一個3位的二進制數來表示每個元素是否出現。“000”表示所有元素都沒有出現,所形成的子集就是{}(空集),”001”表示a、b兩個元素未出現,元素c出現,由此形成的子集爲{c},以此類推,“010”所表示的子集爲{b},”011”所表示的子集爲{b,c},”100”所表示的子集爲{a},”101”所表示的子集爲{a,c},”110”所表示的子集爲{a,b},”111”所表示的子集爲{a,b,c}。
通過以上列舉,可以看出:任意一個3位的二進制數s,都可以表示集合{a,b,c}的一個子集。現在我們做一個一般性推理,看看任意一個集合總共可以形成多少個子集。根據我們所學過的排列組合的知識,可以得知:假設集合的元素個數爲n,可以形成2的n次方個子集。而2的n次方,用位運算的方式也可以表示爲”1<<n”(一定要記住它哦,在程序中會出現)。
現在的關鍵問題是,如何根據s的值計算它所表示的子集中有哪幾個元素呢?我們可以根據s當中”1”出現的位置來計算。接下來的問題就變成了:如何確定s中”1”出現的位置?我們可以設置一個初始值爲“001”的二進制數x,並且逐位向左移動1的位置,並且與s做按位與操作,通過觀察運算結果是否爲0來判斷s中”1”所出現的位置。
小夥伴們可能沒有弄明白其中的原理,我們用具體的數字舉例。假如s的值爲”101”,x的初始值爲“001”。
第1次測試:x左移0位得到1,s&1=1,結果不爲0,說明s從右數的第1位上不爲0,對應位置的”c”會出現在子集中。
第2次測試:x左移1位得到2,s&2=0,結果爲0,說明s從右數的第2位上爲0,對應位置的”b”不會出現在子集中。
第3次測試:x左移2位得到4,s&4=4,結果不爲0,說明s從右數的第3位上不爲0,對應位置的”a”會出現在子集中。
經過3次測試,說明”101”所代表的子集爲{a,c}。由此可以推廣爲:依次把s的值設置爲“000”,”001”,”010”...”111”,s取某一個值的時候,我們分別讓x左移0位,1位和2位進行測試。
這裏需要思考一下:爲什麼讓x最多左移2位呢?就是因爲我們現在討論的集合{a,b,c}僅包含3個元素,如果用數組來表示的話,該數組下標最大值就是2。如果集合元素數量爲n,則x最多左移n-1位。求子集的完整代碼如下:

public static void main(String[] args)  {
    String[] set = {"a","b","c"};//以數組set表示一個集合
    int n = set.length;//以n表示集合元素個數
    for(int s=0;s<(1<<n);s++){//以s表示二進制數,以1<<n表示2的n次方
        System.out.print("{");//先打一個左括號
        for(int offset=0;offset<s;offset++){//offset表示x左移的位數
             if((s&(1<<offset))!=0){//判斷下標爲offset的元素是否出現在子集中
                  System.out.print(set[offset]+" ");
             }
        }
        System.out.println("}");//子集元素打印之後再打一個右括號
    }
}

這段程序的運行結果如下:
Java千問:Java位運算經典應用(四)

運行結果可能有點出乎小夥伴們的預料。很多人認爲元素”c”位於集合的最右邊,按照程序運行的順序,輸出空集之後,第一個被輸出的元素應該是”c”,但實際第一個輸出的元素是”a”,這是爲什麼呢?就是因爲:我們在排列集合的子集時,是把最右邊的”c”當成第一個元素的,而程序實際運行的時候,是從左向右打印數組元素,也就是把最右邊的”a”當成了第一個元素。其實無論從左向右數,還是從右向左數,所有的子集都會被列舉出來。

至此,我們通過4篇文章介紹了位運算的8個經典應用。當然,位運算還在其他很多場合下有着非常廣泛的應用,希望小夥伴們不要認爲位運算只在應付面試的時候才能用到,掌握了位運算的技巧可以使得我們在開過程中發揮出事半功倍的效果。
(全文完)
如想系統學習Java編程,歡迎觀看我在本站的視頻課程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章