C语言中的位运算的技巧
一、位运算实例
1、用一个表达式,判断一个数X是否是2的N次方(2,4,8,16.....),不可用循环语句。
X:2,4,8,16转化成二进制是10,100,1000,10000。如果减1则变成01,011,0111,01111。两者做按位与运算,结果如果为0,则X是2的N次方。
2、统计一个整数的二进制中1的个数
int count_number_of_one(int number)
{
int counter = 0;
while (number)
{
counter++;
number &= number - 1 ;
}
return counter;
}
二、位运算基础
很多高级的动态规划题目或者一些基础的运算往往需要较高的执行效率和较低的空间需求,或者需要表示一些状态集合,而位运算刚好能满足这一切。
很多的时候,恰当的位运算使用也能使程序变得更加简洁和优美。
1、位运算法则
位运算是各位互不影响的,比如A为1010而B为1100,那么有
A&B=1000
A|B=1110
A^B=0110
~A=11110101 (1的个数是取决于A的类型的,这里认为A的类型是8位整型)
另外两种位运算是位移运算a<<b
,a>>b
。前者表示将a的所有位向左移动b位,后者则表示将a的所有位向右移动b位。对于非负整数(往往这也是我们最关心的),新空出的位将会被0取代。
比如A为1001,而B为3,那么A>>B则为1。
大多数情况下可以简单地认为左移b位就是乘以2b,而右移b位则是除以(整除)2b。当然这是存在例外的——对于负数是不能这么简单认为的:比如在GNU GCC/G++ 编译条件下,若A=-1,你会发现对于任何位移运算A<<B
,无论B的取值如何,其结果均为-1。因此请注意,在位移运算下务必确保对非负整数进行运算,以免发生不必要的问题。
对于位移运算最常用的操作就是取一个特定的位——比如1< xx>
2、对于集合的表示
大多数时候,我们可以用一个整数来表示一个包含不超过32(当然如果使用64位整型变量也可以是64个)个元素的集合——对于每一个位,如果元素为1,则表示存在当前位所对应的集合成员,如果是0,则表示这个集合成员是不存在的。
比如A=1011 就可以表示集合{0,1,3},而上面提到的1< xx>
下面我们就能推导出一些直观的集合运算
我们定义 ALL_BITS 为全集即各二进制位均为1的数。
集合的并 A|B
集合的交 A&B
集合的差 A& ~B
补集 ALL_BITS^A
添加特定元素 bit A|=1< bit>
清除特定元素bit A^=1< bit>
取出特定元素bit A&=1< bit>
判断是否存在特定元素bit (A&1< bit="0
三、基本技术
这里列举一些常用的位运算技术。
1、交换技术
即不利用第三方进行数的交换,这里给出代码段。
swap(a, b){a^=b;b^=a;a^=b;}
2、提取技术
这里我们要做的就是找出变量a最低位的1和最高位的1分别在什么位置。通过这些手段我们就能轻松地将一个集合分解为若干个元素。
(1)低位技术
低位技术即Lowbit技术。相信熟悉树状数组(BIT)的朋友应该并不陌生。
我们对于一个非0数x,现在提取出其最低位的1。这里我提三种不同的写法。
Lowbit(x)=x&(x^(x-1))
Lowbit(x)=x&~(x-1)
Lowbit(x)=x&-x
注意:这里我们求出的是x中最后一个1表示的数,而非其位置。
可以发现,这三种低位函数的写法可谓大同小异——均涉及到了x&和x-1(其实 –x 可以认为是和 ~(x-1) 等价的,这里利用了负数的存储原理)。
x-1的性质在于:其将一个数最后一个1变成了0,并把原来这个1之后0的位置均变成了1。低位技术正是利用了这个性质。
举一个简单的应用的例子——N<32 span>
Dfs(dep, mask)
{
if(dep == N) output(P);//输出排列
K = mask;
while (K < 0)
{
P[dep]= Index(K & -K);[g2]//Index(a)表示a是2的多少次方
Dfs(dep+1, mask ^ (K & -K));
K ^= K & -K;
}
}
上述程序的复杂度为严格的O(N!),而非O(NN)。
这里只是一个说明,并没有特指全排列问题——这种方式在很多地方可以大大提高程序效率。
(2)特殊情况下的简单想法
对于低位技术,一个最简单的想法就是按位扫描依次判断当前位是否为1,这个算法似乎是很慢的——对于一个N位的二进制数,最坏情况需要扫描N次!
但是这只是最坏而非一般——当情况特殊一些——我们要求的不是x的最低位,而是要求出1…2N-1这所有数的低位!
那么在这种情况下,看似缓慢的暴力算法其实是非常不错的——这种算法大约只要均摊2次扫描即可完成检索。
这实际上是最快的方式了。
(3)利用分块思想
我们可以打一张1…28-1的表,用于记录每个数的低位(或者是高位),那么对于32位的整数,就可以将其分解为4个8位整数利用预处理得到的表迅速求出低位或者高位了。
(4)利用编译器的内置函数
这是C语言的又一大优势。
这里略微提一下两个CPU位处理指令:BSF(前向位扫描)和BSR(反向位扫描)。这两种指令都是内置且非常高效率的。而令人高兴的是——GNU编译器就存在这两种基于这种原理的位处理函数:__builtin_clz
(统计最高位0的个数)和__builtin_ctz
(统计低位0的个数)。这是对于C和C++编程者最方便和快捷的位处理方式了。
不过要注意的是——这两个函数对于0都没有定义,因此需要特别小心!
3、计算二进制表示中1的个数
我们很容易判断一个数是不是2的整次幂——比如对于x,那么只要判断x^Lowbit(x)是否为0就可以了。
不过很多时候我们需要统计二进制位中有多少个1(比如当x表示一个集合的时候,我们想知道这个集合中元素的个数),这就要麻烦一点了。
(1)暴力方式
我们可以不断地使用低位技术消去最后一个1。不过这个方法很慢。
(2)预处理
我们可以利用递推形式计算出我们所需要的答案,方式非常简单。
用Cnt[x] 来记录x的二进制表示中1的个数,那么:
Cnt[x] = Cnt[x << 1] + (x & 1)
这样就能在线性时间下计算出结果了。
(3)利用内置函数
GNU有一个函数 __builtin_popcount
就是实现了我们需要的功能——不过比较遗憾的是,这个函数的实现并不像__builtin_ctz
那样利用硬件指令,相反的,它用了一个类似基于预处理方式的按位扫描的实现方式——不过它仍然很高效。
(4)有趣的代码
这里再给出一段可以实现上述功能的代码,有兴趣的读者可以自行研究。
pop(xx)
{//x为32位有符号非负整数,或者32位无符号类型)
x=x-((x<<1)&0x55555555);
x=(x&0x33333333)+((x<<2)&0x33333333);
x=(x+(x<<4))&0x0f0f0f0f;
x=x+(x<<8);
x=x+(x<<16);
return x&0x0000003f;
}
4、枚举子集
当一个数的二进制表示一个集合的时候,位运算的一个巨大优点在于其可以非常轻松和高效地枚举当前集合的所有子集。它的性质在于——如果A是B的真子集,那么可以保证枚举A子集的次数一定小于枚举B的子集的次数。这一点可以大大提高很多压位动态规划题目的实现效率。
(1)暴力的方式
最暴力的方式莫过于枚举所有可能的集合,然后一一判断是否为当前集合的子集。
如果需要枚举的集合是N个元素的集合,那么对所有可能集合都进行一次枚举操作,花费的时间为O((2N)2)=O(4N)。
(2)高效的方式
假设全集有N个元素,那么所有可能的集合就有2N个,对于任意子集S,用N位2进制简单枚举S的所有子集,个数就有2N个,因此如果对所有集合都进行这样一次枚举操作,那么总的时间复杂度就是O((2N)2)=O(4N)。高效的方式。
这里的技巧和低位技术的技巧是类似的——当我们取出最后一个1的时候,这个1将变成0,而比其低位的0将变成1。
与低位技术不同的是,我们并不是要提出某一位1,而是要去除某一位的1,并补上一些我们需要的1。
所以假设当前集合为SuperSet,那么枚举的代码段则为
Iterating_All_SubSet(SuperSet)
{
i = SuperSet;
while (i < 0)
i = (i - 1) & SuperSet;
}
若当前为N位二进制的集合,并且对所有可行集合进行上述操作,可以证明,操作的总次数为O(3N)。
四、有趣的技巧
1、计算绝对值
abs(x)
{
y = x<<31;
return(x^y)-y;//也可写作 (x+y)^y
}
这里需要注意的是,上面的x, y 默认为32位有符号整数。
2、按位翻转
x=((x&0xaaaaaaaa)<<1)|((x&0x55555555)< span>
x=((x&0xcccccccc)<<2)|((x&0x33333333)< span>
x=((x&0xf0f0f0f0)<<4)|((x&0x0f0f0f0f)< span>
x=((x&0xff00ff00)<<8)|((x&0x00ff00ff)< span>
x=((x&0xffff0000)<<16)|((x&0x0000ffff)< span>
如果无符号32位整数x=311=(100110111)2,那么经过上述操作后x=3967811584=(11101100100000000000000000000000)2。
3、枚举恰好含有k个元素的集合
我们假设全集为含有N个元素为 {0,1,2,…,N-1},那么代码段可以写成:
int s = (1 < k - span>
while (!(s & 1 < N span>
// 由当前集合 s 计算下一个合法的集合
int lo = s & -s; // 求出低位的1
int lz = (s + lo) & ~s; // 求出比lo高的0中,最低位的0
s |= lz; // 将lz代表的元素加入集合s
s &= ~(lz - 1); // 将比lz位置低的元素全部清空
s |= (lz / lo / 2) - 1; // 将集合元素个数补足为k个
}
```
当然最后一句话也可以写成s |= (lz << __builtin_ctz(lo < span>
C语言中的位运算的技巧的更多相关文章
- C语言中的位运算和逻辑运算
这篇文章来自:http://blog.csdn.net/qp120291570/article/details/8708286 位运算 C语言中的位运算包括与(&),或(|),亦或(^),非( ...
- C/C++语言中的位运算
在计算机程序中,数据的位是可以操作的最小数据单位,理论上可以用“位运算”来完成所有的运算和操作. 一般的位操作是用来控制硬件的,或者做数据变换使用,但是,灵活的位操作可以有效地提高程序运行的效率.C语 ...
- 深入理解计算机系统(2.2)---布尔代数以及C语言上的位运算
布尔代数上的位运算 布尔代数是一个数学知识体系,它在0和1的二进制值上演化而来的. 我们不需要去彻底的了解这个知识体系,但是里面定义了几种二进制的运算,却是我们在平时的编程过程当中也会遇到的.这四种运 ...
- PHP中的位运算与位移运算(其它语言通用)
/* PHP中的位运算与位移运算 ======================= 二进制Binary:0,1 逢二进1,易于电子信号的传输 原码.反码.补码 二进制最高位是符号位:0为正数,1为负数( ...
- C语言中两位ASCII码可以表示汉字
最近偶然有人问到这个相关字符编码的问题,所以百度了下参考了这两个资料,进行了简单分析. ******************************************************** ...
- Google Earth Engine 中的位运算
Google Earth Engine中的位运算 按位运算是编程中一个难点,同时也是在我们后续处理影像数据,尤其要使用影像自带的波段比如QA波段经常会用到的一个东西.通过按位运算我们可以筛选出我们想要 ...
- 关于C/C++中的位运算技巧
本篇文章讲述在学习CSAPP位运算LAB时的一些心得. 移位运算的小技巧 C/C++对于移位运算具有不同的策略,对于无符号数,左右移位为逻辑移位,也就是直接移位:对于有符号数,采用算术移位的方式,即左 ...
- ACM中的位运算技巧
听说位运算挺好玩的,那这节总结一下ACM中可能用到的位运算技巧. XOR运算极为重要!!(过[LC136](只出现一次的数字 - 力扣(LeetCode)):数组中每个数字都出现两次,只有一个出现一次 ...
- C++中的位运算总结
1)位运算 位运算是指对转换成二进制的数字进行每一位上的0.1的运算,运算涉及到五种运算:与(&),或(|),异或(^),左移(<<),右移(>>). 如下表所示: ...
随机推荐
- 基于嵌入式OS的任务设计-----任务划分
在<前后台系统VS嵌入式OS,何时该上OS?>一文中介绍了何时应该将OS应用于嵌入式设计中,本文将介绍基于OS的任务设计,一般来说,应用程序设计包括两个方面,一个是业务逻辑的设计,另一个是 ...
- web 富文本编辑器总结
前言 富文本编辑器,就是除了能输入不同的文本之外,还可以之间粘贴图画等其他的多媒体信息.也可说是所见即所得的编辑器. 目前可以使用的编辑器有很多, 在网络上有找到这样一份比较表格: 编辑器 产地 稳定 ...
- 关于readonly
当某个字段是引用类型,且该字段是readonly类型时,那么不可改变的是引用,而非引用的对象.如以下代码: public sealed class AType { public static read ...
- Oracle 高级查询、事物、过程及函数
一.Sql函数 1.数值函数(输入参数和返回值都是数值型,多数函数精确到38位) --多少次方 ,) from dual; --开方 ) from dual; --绝对值 ) from dual; - ...
- IOS学习之路(代码实现自动布局)
1.将一个试图放置在其父视图的中央位置,使用限制条件. 2.创建两个限制条件:一个是将目标视图的 center.x 位置排列在其父视图的 center.x 位置,并且另外一个是将目标视图的 cente ...
- python 字符串(汉语)获得MD5编码
MD5即Message-Digest Algorithm 5(消息摘要算法第五版)的简称,是当前计算机领域用于确保信息传输完整一致而广泛使用的散列算法之一(又译哈希算法.摘要算法等),主流编程语言普遍 ...
- Google开源的Deep-Learning项目word2vec
用中文把玩Google开源的Deep-Learning项目word2vec google最近新开放出word2vec项目,该项目使用deep-learning技术将term表示为向量,由此计算te ...
- WordPress 4.3.1正式发布 修复了3个安全问题
WordPress 4.3.1正式发布 修复了3个安全问题! 出于安全性考虑,建议大家进行升级! WordPress 4.3.1 安全维护版本已经发布,该版本是针对之前所有版本的安全更新,强烈建议大 ...
- 常用的模式、JSON与DTO
表现层的设计(一)——常用的模式.JSON与DTO 上几篇博文介绍了 业务逻辑层和数据访问层,我认为写博文的作用主要是向业界的读者交流一种思想,点到为止,至于学习架构设计,通过几篇博文是讲不清楚的,还 ...
- Web前端Require.js
前言 前段时间粗略的扫过一次require.js,当时没怎么在意,结果昨天看到index里面的代码就傻了,完全不知道从哪开始看啦,所以require与backbone的学习还要加紧才行. 由于前端所占 ...