位運算的簡單總結


          位運算的簡單總結

           在ACM訓練中聽老師講了一些位運算的技巧,又看了大牛們寫的關於位運算的一些總結,自己想學習學習,就把老師講的和各位大牛們寫的再總結一下,位運算通過位操作符的運算,可以簡化一些複雜問題的計算。比如判斷一個數是不是2的n次冪。是不是4的n次冪.
1.一些運算符的介紹與含義

運算符  含義

&    按位與

|    按位或

^    按位異或

~   按位取反

<<   左移

>>   右移

2.位運算最簡單的應用,判斷一個數的奇偶性。

       大家都知道判斷一個數的奇偶性可以用n%2來判斷,實際上奇數的二進制最後一位是1,偶數的二進制最後一位是0,因此只需要用n&1就可是判斷這個數的奇偶性。結果爲就是奇數,結果爲0就是偶數。

3.用位運算來判斷一個數是不是2的n次冪。

        如果一個數是2的n次冪,則n&(n-1)==0;比如8的二進制是1000,則7的二進制就是0111,想與的到結果,這只是四位表示,在電腦中的32位或者64位表示的原理是一樣,但如果這個數是0的話,n&(n-1)==0;所以判斷一個數是不是2的正整數次冪的表達式是(!n&(n-1)&&n)

4 快速判斷一個數是不是4的n次冪

      我們剛纔講了快速判讀一個數是不是2的n次冪,那麼問題來了,能不能快速判斷一個數是不是4的n次冪,4的二進制是0100,16的二進制是0001 0000,64的二進制是 0100 0000。我們可以發現4的n次冪二進制表達形式的權值都在奇數位上,即1出現在奇數位。那麼16進制的A剛好是1010,奇數位都爲0,那麼n&0xAAAAAAAA(16進製表達,二進制一共32位)。如果結果爲0,並且n!=0,那麼n就是4的n次冪。判斷式爲n&(0xAAAAAAAA).

5.判斷一個數組中只出現一次的數,而其他數都出現兩次。

       我們知道位運算的異或運算,兩位相同的0,不同的1,那麼兩個相同的數進行異或運算,結果肯定爲0,一個數與0進行異或運算,結果肯定是它本身。那麼我們就可以的到判斷的代碼
<span style="font-size:18px;">int get_result(int a[n])
{
int result=0;

for(int i=0;i<n;i++)
{
result^=a[i];
}
return result;
}</span>

6 判斷一個數組中只出現一次的兩個數,其它數都出現兩次
   
     剛纔我們能判斷只出現一次的一個數,那麼我們同樣進行異或運算,可以得到一個數c,假設兩個只出現一次的數位a,b那麼c=a^b.我們只需要找到c中二進制位權值爲1的位,說明a和b在這兩個位上的權值肯定不一樣,一個是1,一個是0,那麼我們可以根據這個位是1還是0,把數組中數分爲兩組分別進行異或運算,最終我們就得到了兩個數。下面是我擼的代碼
 
#include<iostream>
using namespace std;
int a[100];
int main()
{
	int n;
	
	cin >> n;
	int result=0;
	int result1 = 0;
	int result2 = 0;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
		result ^= a[i];//result最後的結果爲a^b
	}
	int k = 1;
	int count = 1;//用來記錄result中權值爲1的爲
	while (result&k != 1)
	{
		count++;
		k << 1;//將1向左移動一位
	}
	for (int i = 0; i < n; i++)
	{
		if (a[i] & (1 << (k - 1)))//k-1爲將1移到k位需要移動的位數,判斷k爲是1
		{
			result1 ^= a[i];
		}
		else
		{
			result2 ^= a[i];
		}
		
	}
	cout << result1 << ' ' << result2 << endl;
	return 0;
}

判斷數組中一個數出現一次,其它數都出現三次
 同樣用位運算的思想,但是不能直接用異或運算就能解決,首先我們知道一個數的二進制表達形式,不是1就是0,那麼我們對二進制中的每個位相加mod3,最後的結果肯定是0,再把所有的數與0進行或運算,就可以得到我們要的數了。
測試代碼:
#include<iostream>
using namespace std;
int main()
{
	int i,n;
	int a[18];
	int count[32]={0};
	int result=0;
	cin>>n;
	for( i=0;i<n;i++)
	{
		cin>>a[i];
	}
	for( i=0;i<32;i++)
	{
		for(int j=0;j<n;j++)
		{
			if(1&(a[j]>>i))//從低位依次向高位掃描,是1相加。將所有數二進制這個位上的和相加
			{
				count[i]++;
			}
		}
		result|=((count[i]%3)<<i);//從低位到高位進行與運算,依次確定每一位,得到只出現一次的數
	}
	cout<<result<<endl;
	return 0;
}


7求二進制最低位的權值是多少,轉化爲10進制

     n&(n-1)可以消去二進制最低位的1,如1100&1011得到1000,再用1100-1000就可以得到最低位的權值。n-(n&(n-1)).(沒使用過這個公式,學習介紹一下)

8求一個數二進制中1的個數
 
while(n!=0){
             count++;
             n=n&(n-1);
        }

也可以不斷進行移位相與運算判斷,沒這種方法簡單

8求商或者餘數,更多用來取餘運算

        學了位運算,大家都知道2^k就是1<<k;移位運算符的操作,那麼我們要求n%(2^k)是多少?答案是n&((1<<k)-1)    ps這個公式怎麼證明不知道,希望知道的人看到告訴我一下。日後再看看

        說到取模運算,那我們不得不說神速的快速冪算法,首先得了解一下秦九韶算法
把一個n次多項式f(x) = a[n]x^n+a[n-1]x^(n-1)+......+a[1]x+a[0]改寫成如下形式:

  f(x) = a[n]x^n+a[n-1]x^(n-1))+......+a[1]x+a[0]

  = (a[n]x^(n-1)+a[n-1]x^(n-2)+......+a[1])x+a[0]

  = ((a[n]x^(n-2)+a[n-1]x^(n-3)+......+a[2])x+a[1])x+a[0]

  =. .....

  = (......((a[n]x+a[n-1])x+a[n-2])x+......+a[1])x+a[0].

  求多項式的值時,首先計算最內層括號內一次多項式的值,即

  v[1]=a[n]x+a[n-1]

  然後由內向外逐層計算一次多項式的值,即

  v[2]=v[1]x+a[n-2]

  v[3]=v[2]x+a[n-3]

  ......

  v[n]=v[n-1]x+a[0]

這樣,求n次多項式f(x)的值就轉化爲求n個一次多項式的值。
那麼(a*b)%c=(a%c)*b%c
a^( 2^(i+1) ) mod c=( (a^(2^i)) mod c)^2 mod c
即a^(2^i)%c = ( (a^(2^(i-1))%c) * a^(2^(i-1))) %c
於是再把所有滿足a(i)=1的a^(2^i)%c按照算法1乘起來再%c就是結果, 即二進制掃描從最高位一直掃描到最低位
任意一數都可以化爲2的多少次冪相加。
ps:公式來自http://blog.sina.com.cn/s/blog_959bf1d301019hns.html
快速冪算法也需要這兩個明顯的公式

我們就可以得到快速冪的迭代算法
int PowerMod(int a,int b,int c)
{
   int ans=1;
   a=a%c;
while(b>0)
{
   if(a&1)//判斷是奇數還是偶數
    {
        ans=ans*a%c;
     }
b>>=1;
a=a*a%c;
}
return ans;
}
快速冪主要用來計算啊a^b%c=?b值非常非常大的時候


不用除法得到商和餘數,計算機中的除法是通過減法來實現,我們可以模擬減法實現,如a/b可以寫a-b-b……一直到的數小於b,爲了加快速度,我們也可以a-b-2b-4b……,來實現
代碼如下
#include<iostream>
using namespace std;
int a[100];
int main()
{
	int res=0;//商
	int mod=0;//餘數
	int a;//除數
	int b;//被除數
	cin >> b>>a;
	if (a < 0)
		a = -a;
	if (b < 0)
		b = -b;
	
	while (b >= a)
	{
		int temp = a;
		int i = 0;
		while (  b >=temp )
		{
			b -= temp; 
			res += 1 << i;
			i++;
			temp <<= 1;
		}
	}
	mod = b;
	if ((a > 0 && b < 0) || (a < 0 && b>0))
	{
		res = -res;
	}
		cout << res << ' ' << mod << endl;
	
	return 0;
}

9:整數的平均值
對於兩個整數x,y,如果用 (x+y)/2 求平均值,會產生溢出,因爲 x+y 可能會大於INT_MAX,但是我們知道它們的平均值是肯定不會溢出的,我們用如下算法:

int average(int x, int y) //返回X,Y 的平均值
{
 return (x&y)+((x^y)>>1);
}
10.位運算的其它一些技巧

功能 | 示例 | 位運算
----------------------+---------------------------+--------------------
去 掉最後一位 | (101101->10110) | x >> 1
在最後加一個0 | (101101->1011010) | x << 1
在最後加一個1 | (101101->1011011) | x << 1+1
把最後一位變成1 | (101100->101101) | x | 1
把最後一位變成0 | (101101->101100) | x | 1-1
最後一位取反 | (101101->101100) | x ^ 1
把右數第k位變成1 | (101001->101101,k=3) | x | (1 << (k-1))
把右數第k位變成0 | (101101->101001,k=3) | x & ~ (1 << (k-1))
右數第k位取反 | (101001->101101,k=3) | x ^ (1 << (k-1))
取末三位 | (1101101->101) | x & 7
取末k位 | (1101101->1101,k=5) | x & ((1 << k)-1)

取 右數第k位 | (1101101->1,k=4) | x >> (k-1) & 1

把 末k位變成1 | (101001->101111,k=4) | x | (1 << k-1)
末 k位取反 | (101001->100110,k=4) | x ^ (1 << k-1)
把 右邊連續的1變成0 | (100101111->100100000) | x & (x+1)
把右起第一個0變成 1 | (100101111->100111111) | x | (x+1)
把右邊連續的0變成1 | (11011000->11011111) | x | (x-1)
取右邊連續的1 | (100101111->1111) | (x ^ (x+1)) >> 1
去掉右起第一個1的左邊 | (100101000->1000) | x & (x ^ (x-1))

先總結這麼多吧,以後遇到再加上………………
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章