位运算的简单总结


           位运算的简单总结

           在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))

先总结这么多吧,以后遇到再加上………………
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值