优秀的算法都大量用到位运算,而位运算在工作中很少用到,借助一个示例,我们看一下其的优势以及原理,顺便mark一波常见位运算。
上一篇《有趣的二进制》我们讲到二进制的一些基础知识,但没有讲到位运算,有同学大呼不过瘾,那这一篇主要讲解下位运算的运用,还是从一个例子开始,希望对大家有启发。记得后面例子应用请自行mark,帮助很大。
一、数独
数独是介绍位运算的好例子,运用位运算和不运用效率差别还是挺大的。我们先看数独需求:
1、当前数字所在行数字均含1-9,不重复
2、当前数字所在列数字均含1-9,不重复
3、当前数字所在宫(即3x3的大格)数字均含1-9,不重复(宫,如下图每个粗线内是一个宫)
、
1、常规算法
若是我们采用常规方式的,每填写一个数字,需要检查当前行、列,宫中其他位置是否有重复数字,极端情况下需要循环27(3*9)次来进行检查,我们看下常规算法下check
1 | 复制代码 int check(int sp) { |
这个效率是否很吓人,每次填写一个就需要check27次,有木有check一次的算法?当然有了,采用位运算,一次搞定。来我们看下位运算的思路:
2、位运算
我们看上图所示,单个行(或者列、宫)数据,都是有1-9共9个数字,我们统称为九宫数字。若是我们采用二进制,以九宫数字充当二进制数据的位坐标,采用9位的二进制就可以与之一一对应,位上有数据,标识为1,无数据标识为0,如此一个正数就能解决一行九宫数据状态,无需需存一个数组。
比如 看图中深红色部分,当前九宫数据中已经有1和3,那么二进制右起第一位和第三位标识为1,一个数字5就可以存下当前行(或者列、宫)数组状态了,如若数字为511表明,所有的九宫数字都用完了,如图第一行。
check一个数字是否已经被占用了,可以采取位运算来获取二进制的右数第k位来查看是否是1,若是1,表明指定数字已经被占用了。我们看下具体check算法:
1 | 复制代码 // sp 是当前位置索引,indexV 行索引,indexH 列索引,indexB九宫格索引 |
1 | 复制代码 // 行、列、宫二进制数据指定位置标记为1 |
我们以以下图例位置举例,如何获得当前位置可以填取的数字
可以看到2个位运算就解决了检查可用数字的操作了,而之前常规算法,需要用27次查找才可以获取到。当然了这个算法还可以优化,比如采用启发式DFS,搜索可用数字,速度更快,感兴趣可点击这里。
常规算法和位运算算法C语言代码,我已经上传码云了,想了解的点击下面链接,自行去查看去。(常规算法google的)
二、基础
1、位操作符
符号 | 含义 | 规则 |
---|---|---|
& | 与 | 两个位都为1时,结果为1 |
— | — | — |
或 | ||
^ | 异或 | 0和1异或0都不变,异或1则取反 |
~ | 取反 | 0和1全部取反 |
<< | 左移 | 位全部左移若干位,高位丢弃,低位补0 |
>> | 算术右移 | 位全部右移若干位,,高位补k个最高有效位的值 |
>> | 逻辑右移 | 位全部右移若干位,高位补0 |
注意:
1、位运算只可运用于整数,对于float和double不行。
2、另外逻辑右移符号各种语言不太同,比如java是>>>。
3、位操作符的运算优先级比较低,尽量使用括号来确保运算顺序。比如1&i+1,会先执行i+1再执行&。
三、应用实例
很棒的应用实例,你可以mark一下,方便以后对照使用。
1、混合体
位运算实例
位运算 | 功能 | 示例 |
---|---|---|
x >> 1 | 去掉最后一位 | 101101->10110 |
— | — | — |
x << 1 | 在最后加一个0 | 101101->1011010 |
x << 1 | 1 | 在最后加一个1 |
x | 1 | 把最后一位变成1 |
x & -2 | 把最后一位变成0 | 101101->101100 |
x ^ 1 | 最后一位取反 | 101101->101100 |
x | (1 << (k-1)) | 把右数第k位变成1 |
x & ~ (1 << (k-1)) | 把右数第k位变成0 | 101101->101001,k=3 |
x ^(1 <<(k-1)) | 右数第k位取反 | 101001->101101,k=3 |
x & 7 | 取末三位 | 1101101->101 |
x & (1 << k-1) | 取末k位 | 1101101->1101,k=5 |
x >> (k-1) & 1 | 取右数第k位 | 1101101->1,k=4 |
x | ((1 << k)-1) | 把末k位变成1 |
x ^ (1 << k-1) | 末k位取反 | 101001->100110,k=4 |
x & (x+1) | 把右边连续的1变成0 | 100101111->100100000 |
x | (x+1) | 把右起第一个0变成1 |
x | (x-1) | 把右边连续的0变成1 |
(x ^ (x+1)) >> 1 | 取右边连续的1 | 100101111->1111 |
x & -x | 去掉右起第一个1的左边 | 100101000->1000 |
x&0x7F | 取末7位 | 100101000->101000 |
x& ~0x7F | 是否小于127 | 001111111 & ~0x7F->0 |
x & 1 | 判断奇偶 | 00000111&1->1 |
2、交换两数
1 | 复制代码int swap(int a, int b) |
3、求绝对值
1 | 复制代码int abs(int a) |
4、二分查找32位整数前导0个数
1 | 复制代码int nlz(unsigned x) |
5、二进制逆序
1 | 复制代码int reverse_order(int n){ |
6、 二进制中1的个数
1 | 复制代码 unsigned int BitCount_e(unsigned int value) { |
———————–end————————-
大码候,关注个人成长和技术学习,期待用自己的一点点改变,能够给予你一些启发
扫描关注更多。
本文转载自: 掘金