位运算

本文总结了位运算的各种常见用途,包括求绝对值、数值交换、判断奇偶数、找出数组中唯一重复的数、奇偶位互换、计算二进制中1的个数以及解决出现K次和出现一次的问题。通过实例和技巧分析,揭示了位运算在算法中的高效应用。

位运算常见用法小结

#位运算基本操作符:
|或
&与
^异或
~取反
12.26所学:
1.位运算求绝对值abs()
原理阐述:对于一个正数而言,返回自己即可,对于负数,二进制表示为补码形式,补码形式又是等于反码形式加一
00000…0011表示3
11111…1100+1 表示-3
即1111…1101 所以负数取反后加一即可

int abs(int a)
{
	return (a<0? (~a+1):a);
} 

2.位运算交换两值
//位运算交换两个数值

void swap(int& a,int& b)
{
	a=a^b;
	b=a^b;
	a=a^b;
}

这个我直接记忆"aba等",一句话的事儿(原理不懂的话举几个栗子,就明白了).

3.位运算判断奇数偶数
规律:当一个数为奇数时,他的最后一位总是1(自己想想是不是这样的?),当它是偶数时,他的最后一位是0,那么我们通过将这个数字与1做与运算来判断他到底是奇_偶数?

//位运算判断奇数偶数,这里假设奇数返回为true,偶数返回false 
bool ji_ou(int a)
{
	return (a&1==1?true:false) ; 
}

4.找出唯一成对的数
Problem:
1-1000这1000个数放在含有1001个元素的数组中,只有唯一的一个元素值重复,其他均只出现一次。设计一个算法,将它找出来,你能否设计一个算法实现?
思路一
桶排序思想,开数组,空间为1001,依次填入,检验元素个数,当元素个数为二时返回
思路二(位运算精髓)

#include<cstdio> 
#include<stdlib.h> 
using namespace std;
int main()
{
	int result;
	int N=11; 
	int *given;
	given=(int*)malloc(sizeof(int)*N);
	for(int i=0;i<10;++i)
	{
		scanf("%d",&given[i]);	/*这十个数中有且只有一个数字是重复的,再次和0~10做异或,偶数次异或的结果做异或运算还是异或,奇数次结果还是它本身所以,最后temp就是重复的那个数字*/
	}
	int* cur;
	cur=(int*)malloc(N*sizeof(int));	/*注意这里不可以在定义的时候给它空间分配,先定义指针再分配空间*/
	for(int i=0;i<10;++i)
	{
		cur[i]=i; 
	}
	for(int i=0;i<10;++i)
	{
		result^=cur[i]^given[i];	//异或满足交换律,所以可以这样写 
	} 
	free( cur);
	free( given);
	printf("%d",result); 
	return 0; 
} 

核心思想:我们利用偶数个同一个数a的异或等于0这一思路,开创一个新的数组分别存0~1000,再让reusult异或两个数组里的元素,奇数个异或结果就是自己,So!最后异或剩下的就是那个成对的数.

5.将整数的奇偶位互换(这里假设从左往右读1,2,3,…位)

#include<stdio.h>
/*用0来做与运算意思是消除,用1来做与运算意识是保留*/

void transfer(int  a)
{
	int b=a&0xaaaaaaaa;	/*取得奇数位操作!!!*/ 
	int c=a&0x55555555;	/*取得偶数位操作!!!*/
	printf("%d",(b>>1)^(c<<1)); 
}
 
int main()
{ 
	int a;
	scanf("%d",&a); 
	transfer(a);
	return 0;
}

核心思路:正如代码中写的那样,用0来做与运算表示消除,用1来做与运算表示保留,我们可以通过与0101010101010…01
来做与运算来消除奇数位上的数,通过与10101010101…10做与运算来消除偶数上的数.
我们把第一个运算的结果左移,将第二个运算的结果做右移运算,再将两个左/右移的结果做耦合,**如何耦合?**规律:

a^0=a;

我们利用这点将左移,右移的结果直接做异或运算即可得到正确的答案!!!

6.二进制中一的个数
问题描述:给你一个十进制数字,输出二进制表示中1的个数.

#include<stdio.h>
bool ji_ou(int a)
{
	return (a&1==1? true : false); 
} 

int count_one(int a)
{
	int count=0;
	while(a!=0)
	{
		if(ji_ou(a))
		{
			count++;	
		} 
		a>>=1;
	}
	return count;
} 

int main()
{
	int count=0;
	int a;
	scanf("%d",&a);
	count=count_one(a);
	printf("%d",count);
}

思路讲解:我们通过判断最后一位与1做与&运算的结果来判断 最后一位是不是1,再依次右移,得到结果

7.出现K次和出现一次
问题描述:数组中只有一个数只出现1次,其它数都出现K次,将出现1次的数输出。
#include
using namespace std;
/数组中只有一个数出现了一次,其他的数都出现了K次,请输出那个只出现了一次的数/
//暴力解法:桶排序思想即可
核心思路(位运算)
技巧:K个数字转化为K进制数字做异或运算,结果为0
eg:K=3
101
101
101结果为
000
因为是不进位加法,(1+1+1)%3=0
是不是很巧妙?!

/*
使用位运算时,K分两种情况:

如果K是偶数,非常简单,将所有元素进行异或运算,得到的就是结果。
如果K是奇数,此法不再适用。
这里要用到一个数学规律,一个K进制数,如果不进位加K次,结果一定为0.
如0b111+0b111=0b000(进位舍弃)。
回到题目,如果K是奇数,我们可以把每一个元素都转换成K进制数。把转换出来的每一位都回到对应的结果数组中。最后再把结果数组转换回十进制数。
当然如果k是偶数的话,也可以使用第2种方法,也就是说第2种方法是通用的。
*/ 

#include<stdio.h>
#include<math.h>
int main(){
	int num,k; //num是多少个数 k是出现k次
	scanf("%d%d",&num,&k);   //num应该满足 (num-1)%k==0
	int a[num];
	int b[32] = {0}; //用于存 不进位 k进制 的每位数 
	int i,j;
	for(i = 0;i < num;i++){
		scanf("%d",&a[i]);
	}
	for(i = 0;i < num;i++){
		int j = 0;
		while(a[i]!=0){
			b[j] = b[j] + a[i] % k; //这个位本来的加上下一个数%k的余数 
			b[j] = b[j] % k;  //如果达到K则进行不进位的取余 
			
			a[i] = a[i] / k;
			j++;
		}
	}
	int result;
	for(i = 0;i < 32;i++){
		result = result + b[i] * pow(k,i);//将k进制不进位的数转为10进制 
	}
	printf("%d",result);
	return 0;
}

这道题其实很不容易想到,这个代码如果没懂前会觉得很晦涩难懂,这里的b[j]相当于做每列的加法,依次从最右边的位数开始向下累加,同时对K取余
eg:
101
101
101
最右边开始b[1]=1,b[2]=0,b[3]=1,然后当前a[i]==0
这里可能会有朋友会问到,万一不是同样的元素相邻出现呢?不会有问题的吗?
问得好!我一开始也是这样怀疑的,直到给了几组测试数据后我才渐渐明白基本原理,这里严格的数学证明就不给了(蒟蒻高数太菜)
异或的可交换性.a^ b ^c = a^ c^ b

8.快速幂运算
求a的k次方

int pow(int a,int k)
{
	int ans=1;
	while(k){
	if(k&1)==1) ans*=a;
	a*=a;
	k>>=1;
	}
	return ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shallow_Carl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值