第一章 位运算

第一章 位运算

位运算

1 整数原码反码补码

正数:原码=反码=补码
负数:原码=补码的反码+1;补码=原码的反码+1

2 位运算符

位运算是指对转换成二进制的数字进行每一位上的0、1的运算,运算涉及到五种运算:与(&),或(|),异或(^),左移(<<),右移(>>)。

C/C++语言提供了六种位运算符来进行位运算操作:
& 	按位与			(双目)
| 	按位或			(双目)
^ 	按位异或		(双目)
~ 	按位非(取反)	        (单目)
<< 	左移			(双目)
>> 	右移			(双目)

3 应用

3.1 与&运算

(1)a&1:用于判断奇偶;
(2)a&(1<<(n-1)):判断第n位是0还是1;
(3)x & (x - 1) :用于消去x最后一位的1。用于统计x的二进制数中1的个数等;
(4)x & (-x) :一般是用来获取某个二进制数的 LowBit ,在树状数组中会用到,lowbit(x)是x的二进制表达式中最低位的1所对应的值。
lowbit 的定义是:x & (-x),当x为 0 时结果为 0;x为奇数时,结果为 1;x为偶数时,结果为x中最大的 2 次方因子。
(5)x = x|(1<<i):设置第 i 位为 1
(6)x = x & (~(1<<i)):设置第 i 位为 0
(7)x = x ^ (1<<i):将第 i 位取反
例题:
(1)(Leetcode191) 位1的个数
输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
方法1:普通方法,移位+与运算

    while(x!=0)
    {
    	cnt+=(x&1);
    	x=(x>>1);
    }

方法2:使用x&(x-1) 或者x&(-x)

unsigned int  x=-1;
	int cnt=0;
	while(x){
		x-=x&(-x);//或者x=x&(x-1);
		cnt++;
	}
	cout<<cnt;
3.2 异或^运算

可以看做不进位加法。异或的性质:

1)0^A=A 0异或任何数A都等于A本身
2)A^A=0 任何数A异或其本身都等于0
3)异或运算满足交换律, 即A^B=B^A
4)异或运算满足结合律,即(A^B)^C=A^(B^C)

例题:
(1)(Leetcode-136)一个数组中,只有1个数出现奇数次,其他数均出现偶数次。求出现奇数次的这个数

int MyFun(std::vector<int>& arr)//vector<int> arr 等价于int arr[]
{
	int ret = 0;
	for (int it : arr)
	{
		ret ^= it;
	}
	return ret;
}

(2)(Leetcode-268)给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。

int missingNumber(int[] nums) {
        int ans = 0;
        for(int i = 0; i < nums.length; i++){
            ans = ans ^ (i + 1) ^ nums[i];
        }
        return ans;
    }

4 建议练习

Leetcode网站-位运算
136,137,190,191,231,260,268,287.

5 补充知识bitset专题

在 STL 的头文件中 中定义了模版类 bitset,用来方便地管理一系列的 bit 位的类。bitset 除了可以访问指定下标的 bit 位以外,还可以把它们作为一个整数来进行某些统计。

bitset 模板类需要一个模版参数,用来明确指定含有多少位。
bitset 就相当于一个 只能存储二进制,也就是 0 和 1 的 bool 数组,但是可以直接当作一个数进行左移右移,取或取反等二进制操作。
如果直接用 bool 数组存储二进制每一位的话,n 位存储复杂度为 O(n),但是用 bitset 的话复杂度会减少到 O(n/32)。
所以一般用二进制状态压缩时就会用 bitset。

5.1 定义

对于 bitset 的定义有四种方式:

5.1.1 直接定义,每一位初始为0:

bitset name; // 定义长度为 N 的二进制数组,命名为 name;

bitset<8> b1;//定义8位二进制数。默认都是0
cout << b1 <<endl;
5.1.2 存储数字的二进制:

bitset name(num); // 定义长度为 N 的二进制数组,命名为 name,将数字 num 的二进制存到其中;

bitset<8> b2(12); //二进制长度8,将12转化为二进制存到其中。
cout << b2 << endl;
5.1.3 存储 01字符串 对应的二进制:

bitset name(string); // 定义长度为 N 的二进制数组,命名为 name,将01串 string 存到其中,长度不够前补 0,长度过长截断;

string s = "10010";
bitset<8> b3(s);
cout << b3 << endl;
5.1.4 存储 01字符数组 中的二进制:

bitset name(char[]); // 定义长度为 N 的二进制数组,命名为 name,将 01字符数组存到其中,长度不够前补0,长度过长截断;

char chs[10] = "10010";
bitset<8> b4(chs);
cout << b4 << endl;

【注意】
直接输出 bitset 为正常的二进制,但是遍历所有位置来输出的话就是逆序的:

bitset<8> bs(13);
cout << bs <<endl; //00001101
for(int i=0;i<7;i++) cout<<bs[i]; //10110000

定义 bitset 对象的示例代码:

const int MAXN = 32;

bitset<MAXN> bt;            //  bt 包括 MAXN 位,下标 0 ~ MAXN - 1,默认初始化为 0
bitset<MAXN> bt1(0xf);      //  0xf 表示十六进制数 f,对应二进制 1111,将 bt1 低 4 位初始化为 1
bitset<MAXN> bt2(012);      //  012 表示八进制数 12,对应二进制 1010,即将 bt2 低 4 位初始化为 1010
bitset<MAXN> bt3("1010");   //  将 bt3 低 4 位初始化为 1010
bitset<MAXN> bt4(s, pos, n);//  将 01 字符串 s 的 pos 位开始的 n 位初始化 bt4
5.2 常用操作:
5.2.1 像数字一样进行 取或 或者 左移右移
b2 |= b3; //两个二进制数取或操作; 
b2 &= b3; //两个二进制数取与操作; 
b2 ^= b3; //取异或;
b2 = ~b2; //取反; 
b2 <<= 2; //左移右移;
5.2.2 自带函数
bt.any()        //  bt 中是否存在置为 1 的二进制位?
bt.none()       //  bt 中不存在置为 1 的二进制位吗?
bt.count()      //  bt 中置为 1 的二进制位的个数
bt.size()       //  bt 中二进制位的个数
bt[pos]         //  访问 bt 中在 pos 处的二进制位
bt.test(pos)    //  bt 中在 pos 处的二进制位是否为 1
bt.set()        //  把 bt 中所有二进制位都置为 1
bt.set(pos)     //  把 bt 中在 pos 处的二进制位置为 1
bt.reset()      //  把 bt 中所有二进制位都置为 0
bt.reset(pos)   //  把 bt 中在pos处的二进制位置为0
bt.flip()       //  把 bt 中所有二进制位逐位取反
bt.flip(pos)    //  把 bt 中在 pos 处的二进制位取反
bt[pos].flip()  //  同上
bt.to_ulong()   //  用 bt 中同样的二进制位返回一个 unsigned long 值
os << bt        //  把 bt 中的位集输出到 os 流
int cnt_1 = b2.count(); //查询二进制数组中1的个数;
int len  = b2.size(); //二进制数组的长度,就是定义的长度; 
int test = b2.test(7); //判断第x个位置是0还是1,也就是输出第x个位置,注意逆序;

b2.flip(); //将二进制每一位取反;
b2.flip(3); //将二进制第x位取反;
b2.set(); //将二进制每一位置为1; reset置为0; 
b2.set(3); //将第x个位置置为1;
	
string ss = b2.to_string(); //将二进制数组转化为字符串。 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值