位運算的操做與算法

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

位運算能夠進行的騷操做

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

題1:找出惟一成對的數

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

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

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

那麼咱們應該怎麼去解決這個題目呢?在這裏咱們既然講了位運算,那麼確定是使用|&^等來解決這些問題。dom

首先咱們得知道:函數

A ^ A = 0 , A ^ 0 = Aui

那麼咱們能夠想一想,假如咱們將題目中的數組與 1~1000進行異或操做那麼剩下的值就是那一個重複的值。spa

​ 簡單的來個示例,假如數組是[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 = 3code

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

相關文章
相關標籤/搜索