一、位运算实例

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<<ba>>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语言中的位运算的技巧的更多相关文章

  1. C语言中的位运算和逻辑运算

    这篇文章来自:http://blog.csdn.net/qp120291570/article/details/8708286 位运算 C语言中的位运算包括与(&),或(|),亦或(^),非( ...

  2. C/C++语言中的位运算

    在计算机程序中,数据的位是可以操作的最小数据单位,理论上可以用“位运算”来完成所有的运算和操作. 一般的位操作是用来控制硬件的,或者做数据变换使用,但是,灵活的位操作可以有效地提高程序运行的效率.C语 ...

  3. 深入理解计算机系统(2.2)---布尔代数以及C语言上的位运算

    布尔代数上的位运算 布尔代数是一个数学知识体系,它在0和1的二进制值上演化而来的. 我们不需要去彻底的了解这个知识体系,但是里面定义了几种二进制的运算,却是我们在平时的编程过程当中也会遇到的.这四种运 ...

  4. PHP中的位运算与位移运算(其它语言通用)

    /* PHP中的位运算与位移运算 ======================= 二进制Binary:0,1 逢二进1,易于电子信号的传输 原码.反码.补码 二进制最高位是符号位:0为正数,1为负数( ...

  5. C语言中两位ASCII码可以表示汉字

    最近偶然有人问到这个相关字符编码的问题,所以百度了下参考了这两个资料,进行了简单分析. ******************************************************** ...

  6. Google Earth Engine 中的位运算

    Google Earth Engine中的位运算 按位运算是编程中一个难点,同时也是在我们后续处理影像数据,尤其要使用影像自带的波段比如QA波段经常会用到的一个东西.通过按位运算我们可以筛选出我们想要 ...

  7. 关于C/C++中的位运算技巧

    本篇文章讲述在学习CSAPP位运算LAB时的一些心得. 移位运算的小技巧 C/C++对于移位运算具有不同的策略,对于无符号数,左右移位为逻辑移位,也就是直接移位:对于有符号数,采用算术移位的方式,即左 ...

  8. ACM中的位运算技巧

    听说位运算挺好玩的,那这节总结一下ACM中可能用到的位运算技巧. XOR运算极为重要!!(过[LC136](只出现一次的数字 - 力扣(LeetCode)):数组中每个数字都出现两次,只有一个出现一次 ...

  9. C++中的位运算总结

    1)位运算 位运算是指对转换成二进制的数字进行每一位上的0.1的运算,运算涉及到五种运算:与(&),或(|),异或(^),左移(<<),右移(>>). 如下表所示:   ...

随机推荐

  1. Ali也开始玩了阿

    http://blog.alipay.com/ 查看源代码

  2. Java(5/6和8)中interface和Abstract Class

    这篇文章主要是自己在使用java的过程中对自己一些之前常困惑的问题的一些总结. 正如题目所言,这篇博客主要是讨论java中的接口与抽象类的区别,有自己的使用心得,以及自己平时在使用的过程中遇到的问题及 ...

  3. AutoPostBack通过现象看本质

    在做人事档案管理系统时遇到一个功能需要实现前台数据(实时)与后台进行交互,解决这个问题首先想到的是应用控件的AutoPostBack属性.本以为这个问题就这样解决了(不用javascript.jque ...

  4. 使用typeof方法反射属性和方法

    Type type = typeof(System.Int32);//获得int类型的Type对象 foreach (MethodInfo method in type.GetMethods())// ...

  5. AMQP(Advanced Message Queuing Protocol)

    一套确定的消息交换功能,也就是“高级消息交换协议模型”.AMQP模型包括一套用于路由和存储消息的功能模块,以及一套在这些模块之间交换消息的规则. 一个网络线级协议(数据传输格式),客户端应用可以通过这 ...

  6. 随便讲讲XSS攻击

    作为一个前端工程师,XSS漏洞不应该只是安全部门的工作.在项目上马的时候就应该对可能涉及的安全问题有所预防才是有一个好前端.- -   什么是XSS •跨站脚本攻击(Cross-site script ...

  7. CSS3:三个矩形,一个宽200px,其余宽相等且自适应满铺

    某公司面试题:下图绿色区域的宽度为100%,其中有三个矩形,第一个矩形的宽度是200px,第二个和第三个矩形的宽度相等.使用CSS3中的功能实现它们的布局. 这里要用到的CSS3特性box-flex ...

  8. Please Send Me a Card

    Please Send Me a Card 发现身边很多程序员都能看懂英文技术文章的60%-80%内容,但大家都有一个毛病,就是不会说,不会写作,在逛英文技术社区的时候,想发表点什么评论,总担心自己写 ...

  9. 【Hook技术】实现从"任务管理器"中保护进程不被关闭 + 附带源码 + 进程保护知识扩展

    [Hook技术]实现从"任务管理器"中保护进程不被关闭 + 附带源码 + 进程保护知识扩展 公司有个监控程序涉及到进程的保护问题,需要避免用户通过任务管理器结束掉监控进程,这里使用 ...

  10. 跟我一起玩转Sencha Touch 移动 WebApp 开发1

    跟我一起玩转Sencha Touch 移动 WebApp 开发(一) 1.目录 移动框架简介,为什么选择Sencha Touch? 环境搭建 创建项目框架,框架文件简介 创建简单Tabpanel案例 ...