【資料結構與演算法】位運算

gonghr發表於2021-08-02

位運算子

& :與
| :或
^ :異或
~ :非(取反)
>> << :右移(補符號位),左移(補0)
>>> :右移(0補充高位)
對於int型,1<<35與1<<3是相同的,而左邊的運算元是long型時需要對右側運算元模64

image

異或:
可以理解為不進位加法:1+1=0,0+0=0,1+0=1
性質:
1、交換律:a ^ b = b ^ a
2、結合律:(a ^ b) ^ c = a ^ (b ^ c)
3、對於任何數x,都有x ^ x = 0,x ^ 0 = x
4、自反性:a ^ b ^ b = a ^ 0 = a,連續和同一個因子做異或運算,最終結果為自己

應用

判斷奇偶數

x & 1 = 1 x是奇數
x & 1 = 0 x是偶數

核心是判斷二進位制數最後一位是1還是0

交換兩個整數變數的值

使用前提:a,b不相等

a = a^b;
b = a^b;
a = a^b;

不用判斷語句求整數絕對值

(num^(num>>31))+(num>>>31)

11111111 11111111 11111111 11110111:num
11111111 11111111 11111111 11111111:num>>31
00000000 00000000 00000000 00001000:num^(num>>31)
00000000 00000000 00000000 00000001:num>>>31
00000000 00000000 00000000 00001001:num^(num>>31))+(num>>>31

正數顯然滿足
負數的相反數是原碼含符號位取反加一

消除最低位的1

x&(x-1) :可以使二進位制數x的最低位的1變成0;

例題

找出唯一成對的數

1-1000這1000個數放在含有1001個元素的陣列中,只有唯一的一個元素值重複,其它均只出現一次。每個陣列元素只能訪問一次,設計一個演算法,將它找出來;不用輔助儲存空間,能否設計一個演算法實現?

核心:x^x=0

可以強行配湊消去其他元素:這1001個元素連續異或,再異或上1-1000這1000個數。
其他元素都出現2次,異或後變成0.而唯一的一個元素值出現3次,連續異或後值不變。

public static void main(String[] args) {
        int []arr = new int[]{1,2,3,4,4,5};
        int ans = 0;
        for(int i=1;i<=5;i++){
            ans^=i;
        }
        for(int i=0;i<6;i++){
            ans^=arr[i];
        }
        System.out.println(ans);
}

找出落單的數

一個陣列裡除了某一個數字之外,其他的數字都出現了兩次。請寫程式找出這個只出現一次的數字。

public static void main(String[] args) {
        int []arr = new int[]{1,1,3,4,4};
        int ans = 0;
        for(int i=0;i<arr.length;i++){
            ans^=arr[i];
        }
        System.out.println(ans);
}

二進位制中1的個數

請實現一個函式,輸入一個整數,輸出該整數二進位制表示中1的個數。
例:9的二進位制表示為1001,有2位是1

法一:
該二進位制數不斷無符號右移並與1做與運算,結果為1則count++,直到該數變為0

public static void main(String[] args) {
        int n=-1;
        int count = 0;
        while (n!=0){
            int tmp = n&1;
            if(tmp == 1) count++;
            n = n >>> 1 ;
        }
        System.out.println(count);
    }   //32

法二:
每次使該二進位制數的一個為1的二進位制位變成0,count++,直到該數變成0;
x&(x-1) :可以使二進位制數的最低位的1變成0;

public static void main(String[] args) {
        int n=-1;
        int count = 0;
        while (n!=0){
            n = n&(n-1);
            count++;
        }
        System.out.println(count);
    }

是不是2的整數次方

用一條語句判斷一個整數是不是2的整數次方

也就是一個整數的二進位制形式中為1的二進位制位是不是隻有一個。聯絡上一題,也就是count是不是為1

public static void main(String[] args) {
        int n=65536;
        System.out.println((n&(n-1))==0);
}

整數的二進位制奇偶位交換

先取出偶數位和奇數位,偶數結果右移一位,奇數結果左移一位,最後異或
abababab abababab abababab abababab
a0a0a0a0 a0a0a0a0 a0a0a0a0 a0a0a0a0(ou)
0b0b0b0b 0b0b0b0b 0b0b0b0b 0b0b0b0b(ji)
0a0a0a0a 0a0a0a0a 0a0a0a0a 0a0a0a0a(ou>>>1)
b0b0b0b0 b0b0b0b0 b0b0b0b0 b0b0b0b0(ji<<1)
babababa babababa babababa babababa

public static void main(String[] args) {
    int a = 0b01000000_00000000_00000000_00000000;
    System.out.println(a);
    int b = m(a);
    System.out.println(b);  //0b10000000_00000000_00000000_00000000
  }

  private static int m(int i) {
    int ou = i & 0xaaaaaaaa;//和1010 1010 1010 。。。。做與運算取出偶數位
    int ji = i & 0x55555555;//和0101 0101 0101 。。。。做與運算取出奇數位
    return (ou >>> 1) ^ (ji << 1); // 連起來
  }

0~1直接的浮點實數的二進位制表示

給定一個介於0和1之間的實數,(如0.625),型別為double,列印它的二進位制表示(0.101,因為小數點後的二進位制分別表示0.5,0.25.0.125......) 。
如果該數字無法精確地用32位以內的二進位制表示,則列印“ERROR”

public static void main(String[] args) {
    double num = 0.625;
    StringBuilder sb = new StringBuilder("0.");
    while (num > 0) {
      //乘2:挪整
      double r = num * 2;
      //判斷整數部分
      if (r >= 1) {
        sb.append("1");
        //消掉整數部分
        num = r - 1;
      } else {
        sb.append("0");
        num = r;
      }

      if (sb.length() > 34) {
        System.out.println("ERROR");
        return;
      }

    }
    System.out.println(sb.toString());
  }

出現k次與出現1次

陣列中只有一個數出現了1次,其他的數都出現了k次,請輸出只出現了1次的數。

2個相同的2進位制數做不進位加法,結果為0
10個相同的10進位制數做不進位加法,結果為O
k個相同的k進位制數做不進位加法,結果為0

public static void main(String[] args) {
    int[] arr = {2, 2, 2, 9, 7, 7, 7, 3, 3, 3, 6, 6, 6, 0, 0, 0};
    int len = arr.length;
    char[][] kRadix = new char[len][];
    int k = 3;

    int maxLen = 0;
    //轉成k進位制字元陣列
    //對於每個數字
    for (int i = 0; i < len; i++) {
      //求每個數字的三進位制字串並翻轉,然後轉為字元陣列
      kRadix[i] = new StringBuilder(Integer.toString(arr[i], k)).reverse().toString().toCharArray();
      if (kRadix[i].length > maxLen)
        maxLen = kRadix[i].length;
    }
    //不進位加法
    int[] resArr = new int[maxLen];
    for (int i = 0; i < len; i++) {
      //  不進位加法
      for (int j = 0; j < maxLen; j++) {
        if (j >= kRadix[i].length)
          resArr[j] += 0;
        else
          resArr[j] += (kRadix[i][j] - '0');
      }
    }

    int res = 0;
    for (int i = 0; i < maxLen; i++) {
      res += (resArr[i] % k) * (int) (Math.pow(k, i));// 8%3=2,
    }
    System.out.println(res);
  }

相關文章