【Cpp】关于位运算的常用&实用方法个人总结以及加减乘除的实现。「加减乘除」

以下是关于位运算的一点常用方法总结以及仅使用位运算的加减乘除实现。

判断奇偶数

因为二进制除了最后一位上是1时代表是十进制的1,其余位上是1时都是代表着2的倍数,所以如果一个数最后一位是1就可以判断为奇数;
反之可以判断为偶数;

int jioushuJudge(int n){
	if(n & 1 == 1){
		cout << "该数是奇数";
	} else {
		cout << "该数是偶数";
	}
}

交换两个数

首先我们先明确一下异或的作用是如果两数相同就等于0,如果不同则为1.所以我们可以得到两个性质
1.一个数异或0等于本身;
2.一个数异或本身等于0;
而异或^是可以使用交换律的.
所以我们把新的x带入到第二行中的x就可以看到是y = (x ^ y) ^ y,经过交换律变化可以为y = x ^ (y ^ y),即为y = x ^ 0 = x;到这就已经把x的值赋给了y
同样的,把第一行的x带入到第三行中可以交换变为x = (x ^ x) ^ y = 0 ^ y = y,就完成了把y赋值给x的操作

int swap(int x, int y){
    x = x ^ y;
    y = x ^ y;
    x = x ^ y;
}

子集枚举

对若干个名字进行排列组合.
我们可以把名字取不取分为0或者1,然后有几个名字就是几位二进制数.每一位数都对应着一个名字.最后只需要判断对应位上是0还是1就可以知道取不取该名字.

string names[4] = {
        "Alex",
        "Laurie",
        "doinb",
        "xiaohu"
};
void work(int n, int x) {
    for (int i = 0; i < n; i++) {
    //其中的(1 << i)是使一位上是1,然后经过与运算就可以判断x该位上是否是1
        if (x & (1 << i))cout << names[i] << ' ';
    }
    cout << endl;
}

int main() {
    int n = 4;
    for (int i = 0; i < (1 << n); i++) {
    //4位数一共有2的4次方的组合数,(1 << 4)代表的是往左移4位,等于2的4次
        work(n, i);
    }
    return 0;
}

求数的n次方

此处求的是2的n次方.想法是把n转换成二进制数,然后把有1的位数单独拿出来进行次方运算,例如:2的11(1011)次方=(2 ^ 8) * (2 ^ 2) * (2 ^ 1)

int cifang(int n) {
    int res = 1;
    int x = 2;
    while(n != 0){
        if(n & 1 ){
            res *= x;
        }
        x *= x;//相当于次方数*2
        n = n >> 1;
    }
    cout << res;
}

判断是不是2的整数幂

因为二进制的从最右边开始是2的0次幂开始往右增加,所以只需要把原数和原数-1进行与&运算是不是为0可以判断出是否是2的整数幂了。因为2的整数幂只会存在一个1,所以-1把那个1变为0,并且低位全部为1,相与为0说明是2的整数幂。

bool judge(int n){
	if(n & (n - 1)) return false;
	return true;
}

在其余数都出现偶数次的数组中找出出现奇数次的数

题目是这样的:有n个数,只有一个数没有重复过,其他数都出现了两次,那个怎么找出那个数?
这里也需要用到异或运算符的一个性质,两个相同的数异或结果等于0,所以我们直接把所有数都异或一遍,由于交换律,重复的数都变为0了,所以剩下的单独的数和0异或还是他本身,这就求出来了。

int find(int n){
	int a[n];
	int res = 0;
	for(int i = 0; i < n; i++){
		cin >> a[i];
		res ^= a[i];
	}
	return res;
}

找出那两个出现了奇数次的数

这道题比上一题稍微难一点,但是思路都是一样的。
我们先把所有数都异或得到一个数,这个数是我们要求的两个数的异或结果。
这样还不可以分出是哪两个数。由于这是异或的结果,所以里面的1肯定是两个数不同才产生的,而且这个1必定是一方所有的,因为只有0和1才能异或为0.这样我们只需要找出那个1的位置然后把所有数中的那个1位置是1的数全部异或一遍.
这样的结果就是其中一个数了(这次是因为第二个数没有参加异或,而参加了的都是偶数次,除了目标数)。
最后再把找出的数和第一次异或的数异或以下就可以得到第二个数了。

int find(int n){
	int a[n];
	int res = 0;
	int ress = 0;
	for(int i = 0; i < n; i++){
		cin >> a[i];
		res ^= a[i];
	}
	int locate = res & -res;//一个数前面加一个-就是把这个数取反然后再加一,这样就可以得到最右边的那个1的位置
	for(int i = 0; i < n; i++){
		if(a[i] & locate)
			ress ^= a[i];
	}
	int res2 = res ^ ress;
	cout << ress << ' ' << res2;
}

不使用算数运算符进行加法运算

众所周知,在加法中,对小学生来说有一个“难”问题,那就是进位。而在位运算中使用加法也要考虑这个问题。
首先我们把两个数进行异或运算,这样可以考虑不进位的情况,得到的结果是把两个对应的1消掉但是还没进位的情况,记为a。
然后我们通过两数相与得到两个1消掉的位置(两数相与就可以把一样的1保留下来),再通过<<1来得到需要加1的地方,记为b。
然后判断b是否为空,如果b不为空,说明存在进位的情况,这时我们就把a和b当作新的两个数重新进行以上操作,把a中需要加的对应位上的1加上。
这样直到b为空为止,说明进位结束,此时a就是两数相加的结果。

int add(int a, int b){
    int sum = a;
    while(b){
        sum = a ^ b;//不进位情况之和
        b = (a & b) << 1;//进位所产生的需加上的数
        a = sum;
    }
	return sum;
}

不使用算数运算符进行减法运算

加法都写出了,减法就不在话下了。把要减去的数改为取反后的原减数加1;简单的调用加法函数就可以了。

int Minus(int a, int b){
	int c = add(~b, 1);
	return add(a, c);
}

不使用算数运算符进行乘法运算

其实二进制的乘法和十进制的差不多,只需要把进位改为2就可以了。我们先来手动写一个乘法来找找规律。
请添加图片描述
可以发现相当于每次都是加上a乘2的所在位的位数-1次,也就是加上把a向左移动几位的数。
既然有这个规律,我们在每次如果b不为0并且b最后一位为1时,sum加上一个a,然后把a往左移一位,b右移一位(看下一个数)。当然别忘了,b不为零但是最后一位为0时也不要忘了把a左移,b右移,只不过这种情况不往sum里加而已。
还有一个问题,就是符号的问题,我们只能先把两个数都变成绝对值进行计算,最后再根据符号进行添加符号。

int Mul(int a, int b){
    int a1,b1;
    if(a < 0) a1 = add(~a, 1);//取绝对值
    else a1 = a;
    if(b < 0) b1 = add(~b, 1);//取绝对值
    else b1 = b;
    int sum = 0;
    while(b1){
        if(b1 & 1){
            sum = add(sum, a1);
        }
        a1 = a1 << 1;
        b1 = b1 >> 1;
    }
    if((a ^ b) < 0)//如果有一个是负数的话结果也会是负数,所以取反加一
        return add(~sum, 1);
    else return sum;
}

不使用算数运算符进行除法运算

除法和乘法的性质差不多,但是有点不同。除法的本质是减法,所以每次都减去除数,然后次数加一,直到被除数小于除数为止。
但是这种方法效率太低,所以可以使用二分的思想进行优化。
举个例子,比如2的9次方可以是2+2+2+2+2+2+2+2+2,也可以是2的8次方+2,也可以是2的4次方+2的4次方+2(差不多这个意思,能看得懂吗hh),每次以2的幂来进行判断会快很多。
因为int型是有32位的,所以我们要从最大的31位开始判断,因为第32位是符号位。
有个问题需要注意,为了避免数据溢出,所以不使用除数乘2次幂来进行判断,改用被除数输除以2次幂来判断。
i不断变小,知道被除数右移i位后大于除数为止,这说明这是这个除数与2次幂相乘最接近被除数的一个数,然后被除数减去该数,以及次数加上这个2次幂,这样和一个一个减一下子就快了不少。
当然和乘法一样,符号也要考虑。

int devide(int a, int b){   
    int a1,b1;
    if(a < 0) a1 = add(~a, 1);//取绝对值
    else a1 = a;
    if(b < 0) b1 = add(~b, 1);//取绝对值
    else b1 = b;
	int res = 0;
	
    for(int i = 31; i >= 0; i = minuss(i, 1)){//为了避免使用--,所以调用减的函数
        if((a1 >> i) >= b1)//这里是为了避免溢出,所以用a1右移而不是b1左移
        {
            res = add(res, (1 << i));
            a1 = minuss(a, (b1 << i));
        }
    }
    if((a ^ b) < 0)//如果是负数,就取反再加一
        res = add(~res, 1);
    return res;
}

最后

以上是关于位运算的一些使用方法(以后有新的会有补充)以及加减乘除的实现,新手上路,有错请指正;


撒花🎉~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Khalil三省

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

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

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

打赏作者

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

抵扣说明:

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

余额充值