cpp-位运算

消去整数x从右→左的第一个1(最后一位1)

原理:

x+"全1"→让最后一位1右边的0全变为1,最后一位1由于进位变成0,最后一位左边的所有位由于其进1而全1都+1进位变成+0座椅保持不变

令x+"全1"→y

则y与x只有从右边到最后一位正好相反。故进行与正好可以使得这些不同的位都变成0==>消去了最后一位1

-----应用1:用O(1)时间检测整数n是否是2的幂次.

2的幂次:消去最后一位1后应该均为0

0001

0010

0100

1000

#include<iostream>
using namespace std;
//-----原理:消去整数x从后-前的第一个1(最后一位1)---------

//-----应用1:用O(1)时间检测整数n是否是2的幂次.

int delete_final(int x){
	return x & (x-1);
}
void func1(){
	int a;
	cout<<"请输入一个整数:"<<endl;
	cin>>a;
	if(a!=0 && delete_final(a)==0){//易漏掉0!!!!
		cout<<a<<"是2的幂次"<<endl;
	}
	else{
		cout<<a<<"不是2的幂次"<<endl;
	}
}

int main(){
	func1();
}

-----应用2: 计算在一个 32 位的整数的二进制表示中有多少个 1

使用消去最后一位1的方式每次可以消去一位1,循环上述操作直到0

#include<iostream>
using namespace std;


//-----原理:消去整数x从后-前的第一个1(最后一位1)---------

//------应用2: 计算在一个 32 位的整数的二进制表示中有多少个 1
int delete_final(int x){
	return x&(x-1);
}
int count(int a){
	
	int num = 0;
	while(a!=0){
		a = delete_final(a);
		num++;
	}
	return num;
}
int main(){
int a;
	cout<<"请输入一个32位整数:"<<endl;
	cin>>a;
	count(a);
}

-----将整数A转换为B,需要改变多少个bit位

考虑到异或操作同0异1,A^B后算有几个1(异1)

#include<iostream>
using namespace std;
int delete_final(int x){
	return x&(x-1);
}
int count(int a){
	
	int num = 0;
	while(a!=0){
		a = delete_final(a);
		num++;
	}
	return num;
}
void diff(int a,int b){
	int res = a ^ b;
	cout<<a<<"与"<<b<<"不同位数:"<<count(res)<<endl;
}
int main(){
	int a,b;
	cout<<"请输入两个整数"<<endl;
	cin>>a>>b;
	diff(a,b);
}

使用二进制进行子集枚举

在集合数量不是很大的情况下可以使用

-----应用:给定一个含不同整数的集合,返回其所有的子集

含n个整数,用一个含n个二进制位的数上的每一位表示每一个整数对应位是否包含在子集中

含n个元素的结合正好有2^n种可能的子集,故正好能表示完

集合{1,2,3,4},n=4

n:0000---空集

n:0001---{1}

n:0010---{2}

n:0011---{1,2}

n:0100---{3}

让n在每一轮递增1:n=0,1,2,3,4,5......2^n-1

每轮的所需要处理内容:

从右→左依次检查n的每一位i:i==1则将a[i]加入集合,i==0则不加入集合

扫描完一轮n就可以获得一个子集

如何检查n的每一位i是否为1?

使用与运算——第i位是否为1:n右移i位后 &1则可

故可以让n依次移位来检查该位是否为1

#include<iostream>
#include<vector>
using namespace std;

vector<vector<int>> func(vector<int>& arr){
	vector<vector<int>> res;
	int n = arr.size();
	for(int i=0;i<(1<<n);i++){//2^n轮-善于使用移位!
      //每一轮:i&1,i右移,直到n次
      vector<int> cur_res;
      for(int j=0;j<n;j++){
      	if((i&(1<<j))!= 0){//易错
      		cur_res.push_back(arr[j]);
			}
		}
      //保存本轮结果
      res.push_back(cur_res);
	}
	return res;
}
int main(){
	vector<int> v;
	cout<<"请输入数组数量"<<endl;
	int n;
	cin>>n;
	int a;
	cout<<"请输入数组:"<<endl;
	for(int i=0;i<n;i++){
		cin>>a;
		v.push_back(a);
	}
	
	vector<vector<int>> res = func(v);
	
	for(int i=0;i<res.size();i++){
		vector<int> cur = res[i];
		cout<<"["<<" ";
		for(int j=0;j<cur.size();j++){
			cout<<cur[j]<<" ";
		}
		cout<<"]"<<endl;
	}
}

全集问题联系回溯,回溯与位运算可能可以互相解决

a^b^b=a

异或运算的特性:b^b=0,a^0=a

原因:a^0时,a的1^0变成1,a的0^0变成0,正好不变

-----应用1:数组中,只有一个数出现一次,剩下都出现两次,找出出现一次的。

数组所有元素进行异或,最后的结果就是所要找的元素

#include<iostream>
using namespace std;

int func(int arr[],int n){
	int res = 0;//初始化为0,利用0^a = a
	for(int i=0;i<n;i++){
		res ^= arr[i];
	}
	return res;
}

-----应用2:数组中,只有两个数出现一次,剩下都出现两次,找出出现一次的这俩个数a和b。

数组所有元素进行异或,最后的结果res是:res = a^且res!=0

根据异或的特性,a和b不同的位运算时才会出现1。所以可以找到res某一位为1(因为res!=0)且a和b在这一位必不同(一个为1一个为0)

利用这个特性将原数组分割成两个子数组,分别含a,b.

再利用a^b^b=a与a^b^a=b则可以求

#include<iostream>
using namespace std;
struct one{
	int a;
	int b;
};
int delete_final(int x){
	return x&(x-1);
}
void swap(int i,int j,int arr[]){
	int temp = arr[i];
	arr[i] = arr[j];
	arr[j] = temp;
}
int findOne(int arr[],int lo,int hi,int init){
	int res = init;
	for(int i=lo;i<=hi;i++){
		res ^= arr[i];
	}
	return res;
}
one func(int arr[],int n){
	//求res,res = a ^ b
	
	int res = 0;
	for(int i=0;i<n;i++){
		res ^= arr[i];
	}
	
	//找到res从右→左第一个位上的1(最后一位1)
	int posi = res - delete_final(res); //除最后一位外其余全是0
	//数组元素与res进行&运算,这一位为1的结果非0
	int mi = -1;//指向结果非0元素
	int k = 0;
	//分割子数组-双指针法
	while(k<n){
		if((arr[k] & posi) != 0 ){//一定加括号!运算符优先级
			swap(++mi,k,arr);
		}
		k++;
		
	}
	for(int i=0;i<n;i++){
		cout<<arr[i]<<" ";
	}
	one r; 
	r.a = findOne(arr,0,mi,res);
	r.b = findOne(arr,mi+1,n-1,res);

	return r;
}

int main(){
	int n;
	cout<<"请输入数组长度:"<<endl;
	cin>>n;
	int a[n];
	cout<<"请输入数组:"<<endl;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	one res = func(a,n);
	cout<<"结果为:"<<res.a<<" "<<res.b<<endl;
	
	
}

实际上不需要分割子数组,反而分割后改变原数组

#include<iostream>
using namespace std;

void func(int arr[],int n){
	int res = 0;
	for(int i=0;i<n;i++){
		res ^= arr[i];
	}
	int posi = res-(res&(res-1));
	int x = res;
	int y = res;
	for(int i=0;i<n;i++){
		if((arr[i]&posi)!=0){
			x ^= arr[i];
		}
		else{
			y ^= arr[i];
		}
	}
	cout<<x<<"   "<<y;
}
int main(){
	int n;
	cout<<"请输入数组长度:"<<endl;
	cin>>n;
	int a[n];
	cout<<"请输入数组:"<<endl;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	func(a,n);
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值