位運算

1.位運算介紹   

    程序中的所有數在計算機內存中都是以二進制的形式儲存的。位運算說穿了,就是直接對整數在內存中的二進制位進行操作


2.位運算符號

image.png

Java中的運算符號

    & : 按位與

    | :按位或

    ^ :按位異或

    ~ :按位取反

    << :左移,右側補0

    >> :右移,左側補符號位

    >>> :右移,左側補0


3.應用

3.1 Excel表格問題

    在Excel中,用A表示第1列,B表示第2列......Z表示第26列,AA表示第27列,AB表示第28列......以此類推。請寫出一個函數,輸入用字母表示的列號編碼,輸出它是第幾列:

解析:

    A=1,B=2,C=3,......,Z=26

    AA=(26^0 * 1) + (26^1 * 1) = 27;   注:^表示次方這裏

    AB=(26^0 * 2) + (26^1 * 1)= 28;


3.2 交換兩個數字

    在不使用額外空間的情況下交換兩個數字:

解析:

一般解法:

swap1.png

位運算解法:

swap2.png


3.3 比較練習

    對於兩個32位整數a和b,請設計一個算法返回a和b中較大的。但是不能用任何比較判斷。若兩數相同,返回任意一個。

public:
	int Flip(int c)
	{
		return c^1;
	}
	int GetSign(int c)//非負爲1,負爲0.
	{
		return Flip((c>>31)&1);
	}
	int getMax(int a, int b) 
	{
		int c=a-b;
		int as=GetSign(a);//a的符號,as==1表示a爲非負,as==0表示a爲負
		int bs=GetSign(b);//b的符號,bs==1表示b爲非負,bs==0表示b爲負
		int cs=GetSign(c);//a-b的符號
		int difab=as^bs;//表示a和b是否符號不相同,不相同爲1,相同爲0
		int sam=Flip(difab);//表示a和b是否符號相同,相同爲1,不相同爲0
		if(sam)
			return cs?a:b;
		else
			return (as-bs)?a:b;
	}
};


3.4 尋找奇數出現次數

    有一個整型數組A,其中只有一個數出現了奇數次,其他的數都出現了偶數次,請打印這個數。要求時間複雜度爲O(N),額外空間複雜度爲O(1).

解析:

給定一個eo和一個已知數組,然後用eo與數組每個元素進行異或運算。

a2.png

需要了解的是:任意調換整數異或的順序不會改變最終的異或值:

a4.png

所以最終的異或結構爲:eo = D;


3.5 尋找整數出現的次數(兩個數字)

    給定一個整型數組arr,其中有兩個數出現了奇數次,其他的數都出現了偶數次,找到這兩個數。要求時間複雜度爲O(N),額外空間複雜度爲O(1).

解析:

a.png

class OddAppearance {
public:
	vector<int> findOdds(vector<int> arr, int n) 
	{
		vector<int> res;//假設最後得到的兩個數是a,b
		int check1=0;
		for(int i=0;i<n;i++)//首先設一個數爲0與數組每個數進行異或運算,最終得到check1 = a ^ b,因爲a與b肯定不同,索引check1肯定不爲0
			check1=check1^arr[i];
		int k=0,temp=check1;
		while(!(temp&1))//因爲check1不爲0,所以肯定存在第k爲爲1
		{
			k++;
			temp>>=1;
		}
		int help=pow(2.0,k),check2=0;
		for(int i=0;i<n;i++)//重新設check2,只與第k位爲1的數做異或運算,完成後check2爲a和b其中一個數
			if(arr[i]&help)
				check2=check2^arr[i];
		check1=check1^check2;//check2爲a和b其中一個數,則另外一個數是check1^check2
		res.push_back(check1<check2?check1:check2);//按大小輸出
		res.push_back(check1>check2?check1:check2);
		return res;
	}
};


3.6 二進制中1的個數

    實現一個函數,輸入一個整數,輸出該數二進制中表示1的個數。例如:輸入9變成二進制位1001,輸出2;

思路:先判斷整數二進制表示中最右邊一位是不是1,如果不是右移一位,再繼續判斷,直到整個整數變成0爲止。一個整數與1做與運算的結果是1,表示整數最右邊一位是1,否則是0。可以寫出下面代碼:

int NumberOf1(int n){
    int count = 0;
    while(n){
        if(n & 1)
            count ++;
        n = n >> 1;
    }
    return count;
}

問題:上面函數如果輸入一個負數,比如0x80000000,運行的時候會出現死循環。因爲把負數右移一位的時候,並不是簡單把最高位移到第二位變成0x40000000,而是0xC0000000。這是因爲移位前是個負數,仍然要保證移位後是個負數,因此移位後的最高位會設爲1。如果一直做右移運算,最終這個數字就會變成0xFFFFFFFF而陷入死循環。


解決:不移動數字i,當i與1進行與運算並且判斷後,左移1位再與i做與運算,這樣就能判斷i的次低位是不是1......這樣反覆左移,就可以判斷:

int NumberOf1(int n){
    int count = 0;
    unsigned int flag = 1;
    while(flag){
        if(n & flag)
            count ++;
        flag = flag << 1;
    }
    return count;
}


上面算法需要循環32次,優化算法(有幾個1循環幾次):把一個整數減去1,再和原來整數做與運算,會把該整數最右邊一個1變成0.那麼一個整數的二進制中有多少1,就可以進行多少次這樣的操作。

int NumberOf1(int n){
    int count = 0;
    unsigned int flag = 1;
    while(n){
        ++ count;
        n = (n - 1) & n;
    }
    return count;
}


3.7 不用加減乘除做加法

image.png

image.png

int Add(int num1, int num2){
    int sum, carry;
    do{
        sum = num1 ^ num2;
        carry = (num1 & num2) << 1;
        
        num1 = sum;
        num2 = carry;
    }while(num2 != 0);
    
    return num1;
}

3.8 hashmap中的hash算法

Java7:

static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        //該函數確保每個位位置上只有常數倍數的哈希碼具有有限的碰撞數(默認負載係數約爲8)。
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

Java8:

/**
     * Computes key.hashCode() and spreads (XORs) higher bits of hash
     * to lower.  Because the table uses power-of-two masking, sets of
     * hashes that vary only in bits above the current mask will
     * always collide. (Among known examples are sets of Float keys
     * holding consecutive whole numbers in small tables.)  So we
     * apply a transform that spreads the impact of higher bits
     * downward. There is a tradeoff between speed, utility, and
     * quality of bit-spreading. Because many common sets of hashes
     * are already reasonably distributed (so don't benefit from
     * spreading), and because we use trees to handle large sets of
     * collisions in bins, we just XOR some shifted bits in the
     * cheapest possible way to reduce systematic lossage, as well as
     * to incorporate impact of the highest bits that would otherwise
     * never be used in index calculations because of table bounds.
     */
     //計算鍵. hashcode()和擴展(XORs),將散列的高位數降低。由於該表使用的是兩種掩模,
     //所以只在當前掩碼上方的位上的散列集合總是會發生碰撞。(在已知的例子中,有一組浮點鍵,在小的表中保持連續的整數。)所以我們應用一個變換,
     //將高位的影響傳播到下。在速度、效用和比特傳播質量之間存在權衡。因爲許多常見的散列集已經合理分佈(所以不要受益於傳播),
     //因爲我們用樹來處理大型的碰撞在垃圾箱,我們只是XOR一些改變以最便宜的方式來減少系統lossage,以及將最高位的影響,否則永遠不會因爲指數計算中使用的表。
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }


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