位運算的操作與算法


在上一次的博客中,我們實現了使用位操作去實現四則運算。實現整數的加減乘除。這次我們將討論位運算在算法中的一些妙用。

位運算可以進行的騷操作

在這里我將使用題目進行示例

題1:找出唯一成對的數

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

這個題目有兩個要注意的點

  1. 數的范圍是1-1000,這個是確定的
  2. 不能使用輔助儲存空間
  3. 只有一個數字g重復

那么我們應該怎么去解決這個題目呢?在這里我們既然講了位運算,那么肯定是使用|&^等來解決這些問題。

首先我們得知道:

A ^ A = 0 , A ^ 0 = A

那么我們可以想想,假如我們將題目中的數組與 1~1000進行異或操作那么剩下的值就是那一個重復的值。

​ 簡單的來個示例,假如數組是[1,2,3,4,3]

1 ^ 2 ^ 3 ^ 4 ^ 3 ^ 1 ^ 2 ^ 3 ^ 4 = 1 ^ 1 ^ 2 ^ 2 ^ 3 ^ 3 ^ 4 ^ 4 ^ 3 = 0 ^ 3 = 3

import java.util.Arrays;
import java.util.Random;
public class SameWord {
    public static void main(String[] args) {
        // 不重復的數字有1000個
        int N = 1000;
        // 數組的容量為10,其中有一個為重復的
        int[] arry = new int[N + 1];

        for (int i = 0; i < N; i++) {
            arry[i] = i + 1;
        }
        Random random = new Random();
        // 產生1~N的隨機數
        int same = random.nextInt(N)+1;
        int position = random.nextInt(N);
        // 將重復的值隨機調換位置
        arry[N] = arry[position];
        arry[position] = same;
        // 前面一部分就是為了產生1001大小的數組,其中有一個是重復的
                
        // 進行異或操作 【1^2^3^4……】
        int x = 0;
        for (int i = 0; i < N; i++) {
            x = (x ^ (i+1));
        }
        
        // 對數組進行異或操作
        int y = 0;
        for (int i = 0; i < N + 1; i++) {
            y = (arry[i] ^ y);
        }
        // 打印重復的值
        System.out.println(x^y);
    }
}

題2:找出單個值

一個數組里除了某一個數字之外,其他的數字都出現了兩次。請寫程序找出這個只出現一次的數字。

emm,假如弄懂了上面一個題目,這個題目就輕而易舉了

public void getSingle(){
    int[] a = {1,2,3,2,1,3,4};

    int single = 0;
    for (int i : a) {
        single = single^i;
    }
    System.out.println(single);
}

題三:找出1的個數

請實現一個函數,輸入一個整數,輸出該數二進制表示中1的個數

例:9的二進制表示為1001,有2位是1

這個題目挺簡單的。有2個方向可以去解決

  1. 通過移位獲得1的個數

1001 & 1 = 1 , 1001 >> 1 = 100,100 & 1 = 0

public void getNum(){
    int n = 255;
    int count = 0;
    while(n!=0){
        if((n & 1) == 1){
            count ++;
        }
        n = n>>1;
    }
    System.out.println("個數是:"+count);
}

​ 這種解法其實有一定問題的,因為如果去移動負數的話就會涼涼,陷入死循環(負數右移,最左邊的那個1會一直存在)。那么我們怎么解決這個方法呢?既然我們不能移動n,那么我們可以移動相與的那個數啊

1001 & 1 = 1, 1<<1 = 10,1001&10 = 0

public void getNum2(){
    int n = 222;
    int flag = 1;
    int count = 0;
    
    while(flag >=1){
        // 這個地方不是n&flag == 1了
        if((n&flag) > 0){
            count ++;
        }
        flag = flag << 1;
    }
    System.out.println("個數是:"+count);
}

我們可以去考慮下這個的時間復雜度。實際上,無論你要求解的數值有多小,它都要循環32次(因為int為4個字節,需要循環32次)。

  1. 最高效的解法

    這邊有個規律:n&(n-1)能夠將n的最右邊的1去掉。

    那么根據這個規律,如果我們將右邊的1去掉,去掉的次數也就是二進制中1的個數

    public void getNum3(){
        int n = 233;
        int count = 0;
        while(n>0){
            count ++;
            n = (n -1)&n;
        }
        System.out.println("個數是:"+count);
    }

題四:保證不溢出地取整數平均值

求平均值我們一般是使用相加來進行操作的,但是如果值比較大呢,造成溢出怎么辦?實際上我們知道溢出就是因為進位造成的,那么我們就可以使用位來解決這個方法。

10 二進制 1010
14 二進制 1110
公共部分: 1010
不同部分的和: 0100
不同部分除以2:0010
平均數 = 1010(相同部分) + 0010(不同部分的平均數) = 1100
因此二者平均數為12

以上的操作我們可以用位運算來替代:

公共部分 = a & b
不同部分的平均值 = (a ^ b) >> 1
平均值 = 公共部分 + 不同部分的平均值 = (a & b) + ((a ^ b) >> 1)

public void aver(){
    int a = 10;
    int b = 220;
    int averNum = (a&b) + ((a^b)>>1);
    System.out.println("平均值是:"+averNum);
}

題五:高低位交換

給出一個16位的無符號整數。稱這個二進制數的前8位為“高位”,后8位為“低位”。現在寫一程序將它的高低位交換。例如,數34520用二進制表示為:
10000110 11011000
將它的高低位進行交換,我們得到了一個新的二進制數:
11011000 10000110
它即是十進制的55430

A | 0 = A

在這個題目(以34520為例)中我們可以先將 10000110 11011000 >> 8右移動8位得到A = 00000000 1000011010000110 11011000 << 8得到B = 11011000 00000000,然后A | B = 11011000 10000110


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM