算法技巧-01(位运算)

ps:想看结论的直接跳到最后即可,本文的二进制首位指的最右边第一位,而最后一位指的最左边第一位

左移

运算符:<<
运算符后接整数数字,代表左移多少位。

计算方式:整数转换为的二进制数向左移 n 位,右边多出来的位数用 0 补齐,超出的位数直接舍去,比如二进制数 0000 0001 左移 1 位得 0000 0010

注意:

  • 在计算机中,若不是无符号整数,则左边第一位是符号位,当进行移位操作时符号位不会被移动
  • 对于 int 类型的左移,对于移位的操作数只取前 5 位二进制数,即最高左移 31 位当超过 31 又将从头计算也就是第左移第 32 位等于左移 0 位,以此往复,比如左移 64 位也等于左移 0位(当超过位移数超过 32 时c++会警告算术溢出但不会报错)。
    至于为什么是最高能移 31 位,c++中 int 类型除去符号位只有 31 位,但是为了严格起见(防止编译器不同引起的错误)移位运算符最多可移动 sizeof(int) * 8 - 1位
    :2 左移 256 位,按理来说应该为 0 ,但 256 的二进制数为 0010 0000 取前 5 位就是 0,于是左移 0 位,结果仍为 2。
    在这里插入图片描述

对于移位负数位,取其补码的前 8 位为操作数即可
:-255补码的前 5 位为0 0001,左移1 位,结果为4(对于负数,vs会警告算术溢出,但不会报错)
在这里插入图片描述
使用方法:

  • x * 2n = x << n,即在 x 的二进制数前加了个 0
    例:3 左移 2 位, 相当于 3 * 22 = 12
    在这里插入图片描述

右移

运算符:>>
运算符后接整数数字,代表右移多少位

计算方式:整数转换为的二进制数向右移 n 位,左边多出来的位数用 0 补齐(当被操作数为负数可能补 1,与编译器有关,c++负数中用 1 补位),超出的位数直接舍去,比如二进制数 0000 0001 右移 1 位得 0000 0000

同样,右移也只取操作数的前 5 位
在这里插入图片描述

负数也一样,就不举例了

使用方法:

  • 右移运算符和左移相反,用于除法,x / 2n = x >> n。
    当 x 为偶数时相当于删掉了右边的 n 个0,为奇数时,就是整数除法中的结果向下取整
    :4 右移 1 位,相当于 4 / 21 = 2
    在这里插入图片描述

取反

运算符:~

计算方式:将二进制中的 0 全变成 1,1 全变成 0

使用方法:

  • 当 x 是有符号整数时,-x = ~x + 1 = ~(x - 1)。
    负数的补码等于原码取反 +1,所以当正数被取反后要变成负数的原码经过了 -1 再取反,要算 -x 得将减去的 1 加回来所以式子为取反后 +1。若 x 为负数同理,内存中存的 x 的补码(取反后+1),变回来就 -1 后取反。其实 ~x+1 和 ~(x-1)相等,这么说只是方便理解记忆
    :-3 = ~3 + 1
    在这里插入图片描述

运算符:&

计算方式:两者各位分别进行运算,都为1都为1,除此之外都为0。相当于逻辑计算中的 &&(且),当两者都为真的时候返回true,其中之一为假就为false。在c++中任何不为 0 的数代表 true,为 0 代表false
例:0000 0101 & 0000 0100 = 0000 0100(换成整数为5 & 4 = 4)
在这里插入图片描述
特性:清零特定几位,或者取特定几位。
:15(0000 1111)清零前 2 位变成 12(0000 1100),只需15 & 252(1111 1100),即另一个操作数被清零的位置应为 0 ,其他位置为1
在这里插入图片描述
取15前 2 位得 3(11),只需15 & 3(0000 0011),即另一个操作数被取的位置为 1 ,其他位为 0
在这里插入图片描述

使用方法:

  • 判断整数 x 的奇偶性: x & 1(奇数转换为二进制的第一位为 1,偶数为 0,所以只需取 x 的第一位就能判断其奇偶性)。
    :判断 15(0000 1111) 是奇数还是偶数
    在这里插入图片描述
    它也表示 x % 2 的值,详见后面用法

  • 判断 x 是否为 2n(其中 n 为正整数),(x & (x-1)) == 0
    2n 的二进制为一个 1 后接 n 个 0 (如22 的二进制为100,一个 1 接两个 0),2n - 1 的二进制位 n 个1(如 3 的二进制为 11, 两个 1),当两者进行 & 运算结果肯定为0。所以要判断 x 是否为 2n,只需让 x 和 x - 1 进行 & 运算,结果为 0 则是。
    :判断 8 和 7 是否为 2n
    在这里插入图片描述

  • 用于模(%) 2n 的运算,x % 2n = x & (2n - 1)
    2n+a + 2n+b+…肯定能整除 2n(其中 n 所加的数大于等于 0 ),所以 x…xx…0 能被0…01…0(x 中至少有一个 1)整除。比如 12(23 + 22,二进制 1100 )能被 4(22,二进制 100)整除。
    而求模运算是求 x 除以一个数后留下的余数,即只用关心不能被整除的部分。当模 2n 方时,二进制大于 n 位的数(相当于上面各种 2 的指数相加)都能被其整除掉,所以只用取 x 的二进制数中小于等于 n 位的数,即和 x 进行 & 运算的数前 n 位全为 1,也就是 2n - 1
    :15 % 4 = 3 相当于 15 & (22 - 1) = 3
    在这里插入图片描述
    还能再进行优化,2n 是不是等于 1 * 2n,即 2n 等于 1 << n。于是 x % 2n = x & ((1 << n) - 1)
    15 % 4 = 3 相当于 15 & ((1 << 2) - 1) = 3
    在这里插入图片描述

  • 取 x 的第 k 位二进制,将 x 向右移 k - 1 位后取第 1 位数即可。x >> ( k - 1) & 1
    :取 5 (0000 0101)的第 3 位,5 >> 2 & 1 = 1
    在这里插入图片描述
    有了取 x 的第 k 位,怎么会没有清零第 k 位呢。将 x 和 一个 0 后接 k-1 个 1 的二进制数进行 & 运算不就行了?清零第 3 位就是 x & 011(十进制 3)。看上去确实这样,15(0000 1111) 的第三位清零应为 0000 1011(十进制 11),试试看。
    在这里插入图片描述
    结果并不为 11,这怎么回事?我们将 3 写全 0000 0011,和 0000 1111的 & 运算结果为 0000 0011,将不该置零的位置零了。哦,应该和1111 1011进行运算,但是实际上整数类型具体多少位不知道啊。还记得取反嘛,不管多少位,将 100 进行取反不就是 1111 1011了么。
    所以清零第 k 位应为 x & ~2k-1
    再优化一下,2k-1 = 1 << (k - 1),最终为 x & ~(1 << (k - 1))
    :清零 15(0000 1111) 的第 3 位,即 15 & ~(1 << 2) = 11(0000 1011)
    在这里插入图片描述

运算符:|

计算方式:两者各位分别进行运算,都为0都为0,除此之外都为1。相当于逻辑计算中的 ||(或),当两者都为假的时候返回false,其中之一为真就为true

特性:置指定位置为 1(和上面取特定位不同哦,取是不管0 1都保持原样,置 1 是不管0 1都设为 1)。
:让 8(0000 1000)的第 1 位和第 3 位置 1, 8 和 5(101)进行 | 运算结果为 13(0000 1101)。即让需要置零的位设为 1 不需要的位设为 0 即可
在这里插入图片描述
使用方法:

  • 将 x 的第 k 位置 1。x | 2k-1
    还记得 2n 吗?第 n + 1 位为 1 ,其余位全为 0,所以和 2k-1 进行 | 运算,相当于和第 k 位为 1 的数进行 | 运算。再看看 | 运算的特性。
    再优化一下,x | (1 << (k - 1)),我就不用再说原因了吧
    :置 8 (0000 1000)的第 3 位为 1,结果为 12(0000 1100)
    在这里插入图片描述
  • 两个数相加,x | y
    限制比较严格,x 的二进制为 0 的位 y 才能为 1(也可以为 0),x 二进制为 1 的地方 y 一定不能为 1。
    :10(0000 1010)加 5(0000 0101)得 15(0000 1111)
    在这里插入图片描述
    这个方法可以用在 2n 加上比其小的数中

异或

运算符:^

计算方式:两者各位分别进行运算,两者相同时为 0(同为 1 或 同为0),两者不同时为 1(一者为 0 一者为 1)。异就代表要找不同的。所以当 0、1 遇 0 仍为 0、1,遇 1 则取反为 1、0

特性:取反特定几位
:将 10(0000 1010)的第 1、2 位取反,10 和 3(0000 0011)进行 ^ 运算得 9(0000 1001)。即将需要取反的位置置为 1 其他位置为 0。
在这里插入图片描述
使用方法:

  • 交换 x、y 的值。先x = x ^ y 然后 y = x ^ y 最后 x = x ^ y 即可完成交换
    对于 y 来说,最终赋值公式为 y = x ^ y ^ y。^ 运算满足结合律,先看 y ^ y,二进制数全部相同,y ^ y = 0。再计算 x ^ 0,上面提过 1、0 和 0 做 ^ 运算不变,所以 x ^ 0 = 0。所以 y = x ^ y ^ y = x
    对于 x 来说,x = x ^ y ^ x ^ y ^ y。由上面可知 x ^ y ^ y = x,所以简化得 x = x ^ y ^ x。该式子是否眼熟,和上面一模一样,不再赘述。
    :a(13)和 b(35)交换
    在这里插入图片描述

  • 取 x 的绝对值。设一个 y = x >> sizeof(int) * 8 - 1,(x ^ y) - y 或者 (x + y) ^ y
    还记得移位操作符中所说的几点吗?sizeof(int) * 8 - 1 代表 int 类型除去符号位的最多移位数(一般是31,可以直接写 y = x >> 31),计算机中最左边的第一位代表符号位,而且 c++ 中右移运算符对正数补 0 对负数补 1。
    当一个数为正数右移 sizeof(int) * 8 - 1 位时,数字全被移完了,所以 y = 0,因此(x ^ y) - y = (x ^ 0) - 0 = x,整数的绝对值等于自己。
    当一个数是负数时进行右移,c++中负数补 1 啊,所以任何负数右移的结果都是 1111 … 1111(-1的补码),所以 y = -1,因此 (x ^ y) - y = (x ^ -1) - (-1),x 和 全 1 进行 ^ 运算相当于对 x 进行取反,整个式子的含义就成了取反后 +1 即 ~x + 1,这个式子又想起了什么 ~x + 1 = -x。总结来说当 x 为正数时该式的结果为 x,为负数时结果为 -x,代表取 x 的绝对值
    :计算 4 和 -4 的绝对值
    在这里插入图片描述

  • 计算 x 和 y的平均值 (x & y)+((x ^ y) >> 1)
    想想看 & 操作符是干嘛的?两者为 1 就为 1,其中之一为 0 就为 0,所以 & 运算保留的是两个数都为 1 的地方,给你两个数 2(10) 和 2(10)进行 & 运算,结果仍为 2(10),这不是废话嘛!
    再让我狡辩一下,你发现他保留的其实是两个数相同地方的均值(因为两数的二进制都为 1 的地方相加原本要进1 位,但是 & 运算让它保留在了原地,相当于右移 1 位,也就是除以 2),2 + 2 的均值是 2。再举两个数6(110)和 2(010)进行 & 运算,结果为 2(010),怎么不是均值了呢?注意我说的,是两个数相同地方的均值110 和 010 相同地方为 10,所以还是 2 和 2的均值得 2。
    有了相同地方的均值,再找不同地方的均值加起来不就可以了嘛。想想 ^ 运算是干嘛的,找不同的。6(110)和 2(010)不同在左边第一位,^ 运算的结果为 4(100),不同的地方也找到了,加起来 2 + 4 = 6,不对啊,6 和 2 的均值不是 4嘛。这个地方注意了,两个不同地方表明必有 1 个 1和 1 个 0,1 + 0 需要进位吗?不需要,和 & 运算不一样,所以 ^ 运算后要除以 2 才是不同地方的均值,也就是 (x ^ y) >> 1,现在4(100)右移 1 位得 2(10),2 + 2 = 4,是 6 和 2 的均值。
    所以这个式子总的来说就是将相同的地方和不同的地方拆开分别计算均值
    :8 和 24的均值
    在这里插入图片描述

总结

ctrl cv来的口诀:

清零取反要用与,某位置一可用或
若要取反和交换,轻轻松松用异或

  • x << n:x 乘 2n
  • x >> n:x 除以 2n
  • ~x + 1 或者 ~(x - 1):-x
  • x & 1:判断 x 奇偶性,结果为 0 则为偶数,为 1 则为奇数
  • x & (x-1):判断 x 是否为 2n(其中 n 为大于 0 的正整数),结果为 0 则是 2 的幂,不为 0 则不是
  • x & ((1 << n) - 1):x 模(%)2n
  • x >> ( k - 1) & 1:取 x 的第 k 位二进制数
  • x & ~(1 << (k - 1)):将 x 的第 k 为二进制置为 0
  • x | (1 << (k - 1)):将 x 的第 k 位二进制数置为 1
  • x | y:x 加 y,其中 x 二进制数为 1 的地方 y 不能为 1
  • x = x ^ y, y = x ^ y, x = x ^ y:交换 x 和 y
  • y = x >> sizeof(int) * 8 - 1,然后 (x ^ y) - y 或者 (x + y) ^ y:x 的绝对值
  • (x & y)+((x ^ y) >> 1):计算 x + y 的平均值

参考:
c语言中的位运算有什么优点:https://wenda.so.com/q/1528809035217327

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值