位运算在编程中的应用1

来源:互联网 发布:淘宝网购买 编辑:程序博客网 时间:2024/05/17 06:14

1、4个位运算符用于整型数据,包括char。将这些位运算符称为位运算符的原因是他们对每个位进行操作,而不影响其左右两侧的位。

(1)、按位取反:~

一元运算符~将每个1变为0,将每个0变为1:~(1001100)=0110011

(2)、位与:&

不同于逻辑运算符的(&&)操作,&通过两个操作数逐位进行比较产生一个新值。对于每个位,只有两个操作数的对应位置都为1时结果才为1。(10010011)&(00111101)的结果为(00010001)。

(3)、位或:|

区别于逻辑运算符的(||)。|通过两个操作数逐位进行比较产生一个新值。对于每个位,两个操作数的对应位置有一个为1时结果才就为1。(10010011)|(00111101)结果为(100111111).

(4)、位异或:^

^通过两个操作数逐位进行比较产生一个新值。对于每个位,如果操作数中的对应位有一个为1(但不是都为1)结果为1

。(10010011)^(00111101)结果为(10101110)。


2、在代码优化时,广泛引入位运算可以提高代码的运行速度,介绍一些常用的位运算的技巧及在算法中的使用。

(1)、求浮点数的绝对值

double abs(double y)
{
    double x = y;
    *(((int *) &x) + 1) &= 0x7fffffff;
    return x;
}

   (2)、 整数的平均值
对于两个整数x,y,如果用 (x+y)/2 求平均值,会产生溢出,因为 x+y 可能会大于INT_MAX,但是我们知道它们的平均值是肯定不会溢出的,我们用如下算法:
#define AVE(x,y) ((x)&(y))+(((x)^(y))>>1)

  (3)、  判断一个整数是不是2的幂
对于一个数 x >= 0,判断他是不是2的幂
#define POWER2(x) ((((x)&((x)-1))==0)&&((x)!=0))

    (4)、不用temp交换两个整数
int x,y; swap(x,y)
void swap(int &x , int &y)
{
    x ^= y;
    y ^= x;
    x ^= y;
}
(5)、位运算实现加法

定理1设a,b为两个二进制数,则a+b = a^b + (a&b)<<1。
证明:a^b是不考虑进位时加法结果。当二进制位同时为1时,才有进位,因此 (a&b)<<1是进位产生的值,称为进位补偿。将两者相加便是完整加法结果。
定理2使用定理1可以实现只用位运算进行加法运算。
证明:利用定理1中的等式不停对自身进行迭代。每迭代一次,进位补偿右边就多一位0,因此最多需要加数二进制位长度次迭代,进位补偿就变为0,这时运算结束。

用位运算实现加法也就是计算机用二进制进行运算,32位的CPU只能表示32位内的数,这里先用1位数的加法来进行,在不考虑进位的基础上,如下

  1. 1 + 1 = 0
  2. 1 + 0 = 1
  3. 0 + 1 = 1
  4. 0 + 0 = 0


很明显这几个表达式可以用位运算的“^”来代替,如下

  1. 1 ^ 1 = 0
  2. 1 ^ 0 = 1
  3. 0 ^ 1 = 1
  4. 0 ^ 0 = 0

这样我们就完成了简单的一位数加法,那么要进行二位的加法,这个方法可行不可行呢?肯定是不行的,矛盾就在于,如何去
获取进位?要获取进位我们可以如下思考:

  1. 0 + 0 = 0
  2. 1 + 0 = 0
  3. 0 + 1 = 0
  4. 1 + 1 = 1
  5. //换个角度看就是这样
  6. 0 & 0 = 不进位
  7. 1 & 0 = 不进位
  8. 0 & 1 = 不进位
  9. 1 & 1 = 进位

正好,在位运算中,我们用“<<”表示向左移动一位,也就是“进位”。那么我们就可以得到如下的表达式

  1. //进位可以用如下表示:
  2. (x&y)<<1

到这里,我们基本上拥有了这样两个表达式

  1. x^y //执行加法
  2. (x&y)<<1 //进位操作

我们来做个2位数的加法,在不考虑进位的情况下

  1. 11+01 = 100  // 本来的算法
  2.  
  3. // 用推算的表达式计算
  4. 11 ^ 01 = 10
  5.  
  6. (11 & 01) << 1 = 10
  7.  
  8. //到这里 我们用普通的加法去运算这两个数的时候就可以得到 10 + 10 = 100
  9. //但是我们不需要加法,所以要想别的方法,如果让两个数再按刚才的算法计算一次呢
  10.  
  11. 10 ^ 10 = 00
  12.  
  13. (10 & 10) << 1 = 100

到这里基本上就得出结论了,其实后面的那个 “00” 已经不用再去计算了,因为第一个表达式就已经算出了结果。

继续推理可以得出三位数的加法只需重复的计算三次得到第一个表达式的值就是计算出来的结果。

 

c代码如下:

int Add(int a,int b)
{
 int jw=a&b;
 int jg=a^b;
 while(jw)
 {
  int t_a=jg;
  int t_b=jw<<1;
  jw=t_a&t_b;
  jg=t_a^t_b;
 }
 return jg;
}

到此结束

(6)、异或运算实现的算法

题目:给定一个数组,除了一个元素以外其他的元素都是出现两次,求出并返回这个元素

原理:任意数值的二进制与零的异或结果为这个数值,相同数值的异或为零。即 a^0=a,a^a=0所以出现两次的元素异或结束后为零,出现一次的元素和零异或得到这个元素的值。

<span style="font-size:18px;">class Solution {public:    int singleNumber(int A[], int n) {        int num = 0;     for (int i = 0; i < n; ++i) {         num ^= A[i];     }     return num;    }};</span>

(7)\位运算在算法提中的使用

一个数组中除了一个数出现一次其他的都出现三次返回这个出现一次的数

上一题是是利用两个相同的数异或结果为0来计算的,但这个题目中其他数字是出现了3次,因此肯定不可以再使用异或了。

我们换一个角度来看,如果数组中没有x,那么数组中所有的数字都出现了3次,在二进制上,每位上1的个数肯定也能被3整除。如{1, 5, 1, 5, 1, 5}从二进制上看有:

1:0001

5:0101

1:0001

5:0101

1:0001

5:0101

二进制第0位上有6个1,第2位上有3个1.第1位和第3位上都是0个1,每一位上的统计结果都可以被3整除。而再对该数组添加任何一个数,如果这个数在二进制的某位上为1都将导致该位上1的个数不能被3整除。因此通过统计二进制上每位1的个数就可以推断出x在该位置上是0还是1了,这样就能计算出x了。

推广一下,所有其他数字出现N(N>=2)次,而一个数字出现1次都可以用这种解法来推导出这个出现1次的数字。

示范代码如下:

#include <stdio.h>  #include <string.h>  int FindNumber(int a[], int n)  {    int bits[32];    int i, j;    // 累加数组中所有数字的二进制位    memset(bits, 0, 32 * sizeof(int));    for (i = 0; i < n; i++)      for (j = 0; j < 32; j++)        bits[j] += ((a[i] >> j) & 1);    // 如果某位上的结果不能被整除,则肯定目标数字在这一位上为    int result = 0;    for (j = 0; j < 32; j++)      if (bits[j] % 3 != 0)        result += (1 << j);    return result;  }  int main()  {    printf("    【白话经典算法系列之十七】数组中只出现一次的数\n");    printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n");    printf(" -- http://blog.csdn.net/morewindows/article/details/12684497 -- \n\n");      const int MAXN = 10;    int a[MAXN] = {2, 3, 1, 2, 3, 4, 1, 2, 3, 1};    printf("%d\n", FindNumber(a, MAXN));    return 0;  }  


0 0
原创粉丝点击