zoukankan      html  css  js  c++  java
  • 位运算技巧

    位运算

    这里讨论一些位操作技巧,如果使用得当会有助于提高代码运行效率。

    这里假设你已经知道整型数据二进制 补码 表示方式。

    位运算符

    运算符 名称 例子 结果
    & And(按位与) a & b 将把 a 和 b 中都为 1 的位设为 1,否则设为 0。
    | Or(按位或) a | b 将把 a 和 b 中任何一个为 1 的位设为 1。
    ^ Xor(按位异或) a ^ b 将把 a 和 b 中一个为 1 另一个为 0 的位设为 1
    (相同的为0,不同的为1)
    ~ Not(按位取反) ~ a 将 a 中为 0 的位设为 1,反之亦然。
    << Shift left(左移) a << b 将 a 中的位向左移动 b 次(每一次移动都表示“乘以 2”)。
    >> Shift right(右移) a >> b 将 a 中的位向右移动 b 次(每一次移动都表示“除以 2”)。

    异或性质

    • 任何数异或自己都为 0; 即 a ^ a = 0

    • 任何数「异或」0 都为本身;即 a ^ 0 = a

    • 「异或」支持交换律和结合律 (a ^ b) ^ c = a ^ (b ^ c)

    运算符优先级

    下表按照 PHP 优先级从高到低列出了运算符。同一行中的运算符具有相同优先级,此时它们的结合方向决定求值顺序。

    PHP运算符优先级

    操作技巧

    判断奇偶 a & 1

    • a & 1 等于 0,a 为偶数
    • a & 1 等于 1,a 为奇数

    交换两个数

    function swap(&$a, &$b) 
    {
            if ($a != $b) {
            $a ^= $b;
            $b ^= $a;
            $a ^= $b;
        }   
    }
    

    计算过程:

    记住「异或 ^ 」的性质

    • 任何数异或自己都为 0; 即 a ^ a = 0

    • 任何数「异或」0 都为本身;即 a ^ 0 = a

    • 进行「异或」的数可以无序交换 (a ^ b) ^ c = a ^ (b ^ c)

    a=a^b
    b=b^a=b^(a^b)=(b^b)^a=a  --> b=a
    a=a^b=(a^b)^b^(a^b)=(a^a)^(b^b)^b=b   --> a=b
    

    变换符号

    function signReversal(int $a) {
      return ~a + 1;
    }
    

    原理:

    变换符号就是正数变成负数,负数变成正数。

    如对于-11和11,可以通过下面的变换方法将-11变成11

    1111 0101(二进制) –取反-> 0000 1010(二进制) –加1-> 0000 1011(二进制)

    同样可以这样的将11变成-11

    0000 1011(二进制) –取反-> 0000 0100(二进制) –加1-> 1111 0101(二进制)

    因此变换符号只需要取反后加1即可。

    求绝对值

    function abs($a) {
        return $a ^ ($a >> 31) - ($a >> 31);
    }
    

    原理:

    我们知道在我们对一个数进行位运算的时候,是在这个数的补码上进行的

    对于补码我们知道,正数的补码是原码,负数的补码为原码除了最高位的符号位,取反,然后加1。把补码转换成原码的时候,正数还是原码,

    负数时把补码除了符号位取反然后加 1 (我们可以发现如果这时候连符号位也求反,然后加1,与以前不同的只是少了一个符号位,现在实际上就是这个数的绝对值)。

    所以我们可以得到对一个 **负数求绝对值 **的表达式为

    function signReversal(int $a) {
      return ~a + 1;
    }
    

    那么由这些知识我们可以很快地得到求一个数的绝对值的表达式:

    先移位来取符号位,int i = a >> 31;要注意如果 a 为正数,i 等于 0,为负数,i 等于 -1。然后对 i 进行判断——如果 i 等于0,直接返回。否之,返回~a+1。完整代码如下:

    function my_abs(int $a) {
        $i = $a >> 31;
        return $i == 0 ? $a : (~$a + 1);
    }
    

    现在再分析下。对于任何数,与 0 「异或」都会保持不变,与 -1 即 0xFFFFFFFF 「异或」就相当于取反。因此,a 与 i 「异或」后再减 i(因为 i 为 0 或 -1,所以减 i 即是要么加 0 要么加 1)也可以得到绝对值。所以可以对上面代码优化下:

    function my_abs(int $a) {
        $i = $a >> 31;
        return ($a ^ $i) - $i;
    }
    
    // 简化后为
    function abs($a) {
        return $a ^ ($a >> 31) - ($a >> 31);
    }
    

    找出没有重复的那个数

    给你一组整型数据,这些数据中,其中有一个数只出现了一次,其他的数都出现了两次,让你来找出一个数 。

    如:1,2,3,4,5,1,2,3,4

    function find($arr) {
        $tmp = $arr[0];
        foreach($arr as $value) {
            $tmp ^= $value;
        }
        return $tmp;
    }
    

    两个相同的数异或的结果是 0,一个数和 0 异或的结果是它本身,所以我们把这一组整型全部异或一下,例如这组数据是:1, 2, 3, 4, 5, 1, 2, 3, 4。其中 5 只出现了一次,其他都出现了两次,把他们全部异或一下,结果如下:

    由于异或支持交换律和结合律,所以:

    1^2^3^4^5^1^2^3^4 = (1^1)^(2^2)^(3^3)^(4^4)^5= 0^0^0^0^5 = 5。

    也就是说,那些出现了两次的数异或之后会变成0,那个出现一次的数,和 0 异或之后就等于它本身。

    避免溢出求平均值

    function average($a, $b) {
            return ($a & $b) + (($a ^ $b) >> 1); 
    }
    

    a & b 取出 a 和 b二进制都为 1 的所有位

    a ^ b a 和 b 中有一个为 1 的所有位

    >>1 除以 2

    想象一下a和b按照位整齐排序,当 a 和 b 对应为上全为 1 的时候相加会使此位为 0,并且会向前进一位,所以当出现对应位全为1的时候,直接在此位保留一个 1 就算对这两个对应位求平均值了

    然后剩下就是对应位不全为1的时候,分为a的某一个位为1,对应的b的那个位为0,或者倒过来,或者两个都为0,这个时候就要把他们除以2(右移1位)了

    然后把结果加起来就可以了。

    参考资料

    位操作基础篇之位操作全面总结

    机器数和真值 原码,反码,补码及位运算

    用位运算求一个数的绝对值

    数据结构与算法系列之——位运算全解,弄懂位运算

  • 相关阅读:
    Memcache 内存分配策略和性能(使用)状态检查
    C# 中字符串转换成日期
    Task及Mvc的异步控制器 使用探索
    MVC项目实践,在三层架构下实现SportsStore-01,EF Code First建模、DAL层等
    从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 二十三║Vue实战:Vuex 其实很简单
    从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 二十一║Vue实战:开发环境搭建【详细版】
    vue-router 快速入门
    Vue.js——60分钟快速入门
    五小步让VS Code支持AngularJS智能提示
    AngularJS----服务,表单,模块
  • 原文地址:https://www.cnblogs.com/martini-d/p/wei-yun-suan.html
Copyright © 2011-2022 走看看