位运算_1 2016.6.3

原创 2016年05月31日 12:55:06

参考:

http://www.matrix67.com/blog/archives/263

http://www.matrix67.com/blog/archives/264

http://www.matrix67.com/blog/archives/266

http://www.matrix67.com/blog/archives/268

%%%Matrix67


由于位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快


1、位运算符号

a & b 位与

a | b 位或

a ^ b 位异或

  ~a 位取反

a << b 位左移

a >> b 位右移


2、位运算的使用

(1)& 运算

位与运算主要有2个功能

①屏蔽

例:若 a 为一个 32 位的无符号型整数,表达式 a & 0xfffffffc 的结果可以使 a 的机器码的第 0、1 位为 0

②测0

可以用来判断一个非 0 的整数 N 的奇偶性,它的二进制的最末位为0表示该数为偶数,最末位为1表示该数为奇数

if (N & 1) {
    printf("N is an odd\n");
} else {
    printf("N is an even\n");
}

(2)| 运算

位或运算的主要功能是置 1

例如一个数 | 1的结果就是把它的二进制最末位变成1

如果需要把二进制最末位变成0,对这个数 | 1之后再减一就可以了,其实际意义就是把这个数变成最接近的偶数


(3)^ 运算

①位求反

因为异或可以这样定义:0 和 1 异或 0 都不变,异或 1 则取反

②相等比较

若 a ^ b 的值为 0 则 a 和 b 相等,否则不相等


所以两次异或同一个数最后结果不变,即(a ^ b) ^ b = a

位异或运算可以用于简单的加密

比如我想对我 MM 说 1314520,但怕别人知道,于是双方约定拿我的生日 19960112  作为密钥

1314520 xor 19960112 = 19177448,我就把 19177448 告诉 MM

MM 再次计算 19177448 xor 19960112 的值,得到 1314520,于是她就明白了我的企图


③交换两个变量的值

可以使用 a = a ^ b,b = a ^ b,a = b ^ a来实现

相当于 b = (a ^ b) ^ b = a,a = a ^ (a ^ b) = 0 ^ b = b


等效于 a = a + b,b = a - b ,a = a - b

相当于 b = (a + b) - b = a,a = (a + b) - a = b


(4)~运算

位取反运算的含义是对参与运算的运算对象的机器码按二进制方式对相应位进行位取反,1 变为 0,0 变为 1


使用not运算时要格外小心,你需要注意整数类型有没有符号

如果位取反的对象是无符号整,那么得到的值就是它与该类型上界的差

例:

typedef unsigned int uint;

uint N = 100;
N = ~N;
printf("%u\n", N);

//Result : 4294967195

位取反运算的主要功能是测试某些位是否为 1


例:测试某整型变量 a 的第 0、4、8、12 位是否为 1

可以首先对 a 进行取反,再对取反后的值测 0

表达式 ~a & 0x1111 的值为 0,则表示被测的 0、4、8、12 位为 1,否则被测的位不全部为 1


(5)<< 运算

位左移运算符构成的表达式一般格式为 a << n ,其中 a 是需要移位的数据, n 是移位的位数

左移后的结果相当于 a * 2 ^ n

但此结论只适合没有溢出的情况

对于正整数而言,移出的位没有出现 1,且移位后的最高位为 0,则不溢出

对于负整数而言,移出的位没有出现 0,且移位后的最高位为 1,则不溢出


可以用位左移运算来定义 maxn 等常量


(6)>> 运算

位右移运算符构成的表达式一般格式为 a >> n ,其中 a 是需要移位的数据, n 是移位的位数

右移后的结果相当于乘以 a * 2 ^ (-n)

但此结论只适合没有溢出的情况


我们经常用 >> 1来代替 / 2,比如二分查找、堆的插入操作等等

想办法用位右移运算代替除法运算可以使程序效率大大提高

最大公约数的二进制算法用除以 2 操作来代替慢得出奇的 mod 运算,效率可以提高60%



Matrix67大牛博客的那个例题的链接失效了

我在这里放个传送门

Vijos 1197 费解的开关 (Matrix67原创)

但是我不懂那些A题的姿势,等看完大牛的博客,学习了新姿势以后再来做吧,QAQ


贴一个我最近A的一道和位运算能扯上关系的题

CodeForces_677C Vanya and Label

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#include <cmath>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;

const int mod = 1e9 + 7;
const int INF = 0x7fffffff;
const int maxn = 1e5 + 10;
int num[300];
char s[maxn];
int _0_num[70];

int Count_0(int n);

int main()
{
#ifdef __AiR_H
    freopen("in.txt", "r", stdin);
#endif // __AiR_H
    for (int i = '0'; i <= '9'; ++i) {
        num[i] = i - '0';
    }
    for (int i = 'A'; i <= 'Z'; ++i) {
        num[i] = i - 'A' + 10;
    }
    for (int i = 'a'; i <= 'z'; ++i) {
        num[i] = i - 'a' + 36;
    }
    num['-'] = 62;
    num['_'] = 63;
    for (int i = 0; i < 64; ++i) {
        _0_num[i] = Count_0(i);
    }
    gets(s);
    int len = strlen(s);
    ll ans = 1;
    for (int i = 0; i < len; ++i) {
        int t1 = num[(int)s[i]];
        int t2 = _0_num[t1];
        if (t2 != 1) {
            ans = (ans * t2) % mod;
        }
    }
    printf("%I64d\n", ans);
    return 0;
}

int Count_0(int n)
{
    int ret = 1;
    int num = 6;
    while (num--) {
        if ((n&1) == 0) {
            ret *= 3;
        }
        n >>= 1;
    }
    return ret;
}


列举一些常见的二进制位的变换操作

⒈去掉最后一位               >> 1

⒉在最后加一个0            << 1

⒊在最后加一个1            (<< 1) + 1

⒋把最后一位变为1        | 1

⒌把最后一位变为0        (| 1) - 1
⒍最后一位取反              ^ 1

⒎右数第 k 位变为1       | (1 << (k - 1))

⒏右数第 k 位变为0       & (~(1 << (k - 1)))
⒐右数第 k 位取反         ^ (1 << (k - 1))

⒑取末三位                     & 7

⒒取末 k 位                     & ((1 << k) - 1)

⒓取右数第 k 位             (>> ( k - 1)) & 1

⒔把末 k 位变为1           | ((1 << k) - 1)

⒕末 k 位取反                 ^ ((1 << k) - 1)


⒖把右边连续的 1 变为 0              (100101111 -> 100100000)      x & (x + 1)

⒗把右边起第一个 0 变为 1          (100101111 -> 100111111)        x | (x + 1)

⒘把右边连续的 0 变为 1              (11011000 -> 11011111)            x | (x - 1)

⒙取右边连续的 1                          (100101111 -> 1111)                   (x ^ (x + 1)) >> 1

⒚去掉右起第一个 1 的左边         (100101000 -> 1000)                 x & (x ^ (x - 1))


最后一个在树状数组中会用到,还有一种写法是 x & (-x)


-x 实际上是 x 按位取反,末尾加 1 以后的结果

 388288 = 1001010110010000

-388288 = 0110101001110000



3、二进制位中的1有奇数个还是偶数个

int Judge(int n)    //二进制中 1 的个数的奇偶性,奇数返回 1,偶数返回 0
{
    int ret = 0;
    while (n) {
        if (n & 1) {
            ret += 1;
        }
        n >>= 1;
    }
    if (ret & 1) {
        return 1;
    }
    return 0;
}

这样写的效率并不高,位运算的神奇之处还没有体现出来

下面这段代码就强了

int Judge(int n)    //二进制中 1 的个数的奇偶性,奇数返回 1,偶数返回 0
{
    n ^= (n >> 1);
    n ^= (n >> 2);
    n ^= (n >> 4);
    n ^= (n >> 8);
    n ^= (n >> 16);
    if (n & 1) {
        return 1;
    }
    return 0;
}

为了说明上面这段代码的原理,我们还是拿1314520出来说事

1314520的二进制为101000000111011011000


第一次异或操作的结果如下:

          00000000000101000000111011011000
xor       0000000000010100000011101101100
—————————————————————
          00000000000111100000100110110100


得到的结果是一个新的二进制数,其中右起第 i 位上的数表示原数中第i和 i+1 位上有奇数个 1 还是偶数个 1

比如,最右边那个 0 表示原数末两位有偶数个 1,右起第 3 位上的 1 就表示原数的这个位置和前一个位置中有奇数个 1


对这个数进行第二次异或的结果如下:

        00000000000111100000100110110100
xor       000000000001111000001001101101
—————————————————————
        00000000000110011000101111011001

结果里的每个1表示原数的该位置及其前面三个位置中共有奇数个 1 ,每个 0 就表示原数对应的四个位置上共偶数个 1

一直做到第五次异或结束后,得到的二进制数的最末位就表示整个32位数里有多少个 1,这就是我们最终想要的答案



4、计算二进制中的 1 的个数


同样假设x是一个 32 位整数。经过下面五次赋值后,x 的值就是原数的二进制表示中数字 1 的个数

比如,初始时 x 为 1314520,那么最后x就变成了 9,它表示 1314520 的二进制中有 9 个 1


int Count_1(int x)    //计算二进制中 1 的个数
{
    x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);
    x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);
    x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF);
    return x;
}

为了便于解说,我们下面仅说明这个程序是如何对一个 8 位整数进行处理的

数字211的二进制为11010011


+—+—+—+—+—+—+—+—+
| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |   <—原数
+—+—+—+—+—+—+—+—+
|  1 0  |  0 1  |  0 0  |  1 0  |   <—第一次运算后
+——-+——-+——-+——-+
|    0 0 1 1    |    0 0 1 0    |   <—第二次运算后
+—————+—————+
|        0 0 0 0 0 1 0 1        |   <—第三次运算后,得数为5
+——————————-+


整个程序是一个分治的思想

第一次我们把每相邻的两位加起来,得到每两位里1的个数,比如前两位10就表示原数的前两位有2个1

第二次我们继续两两相加,10+01=11,00+10=10,得到的结果是00110010,它表示原数前4位有3个1,末4位有2个1

最后一次我们把0011和0010加起来,得到的就是整个二进制中1的个数


程序中巧妙地使用取位和右移

比如第二行中 0x33333333的二进制为 00110011001100….,用它和 x 做位与运算就相当于以 2 为单位间隔取数

位右移的作用就是让加法运算的相同数位对齐


5、二分查找32位整数的前导0个数

int Count_Pre0(unsigned int x)    //二分查找 32 位整数的前导 0 的个数
{
    if (x == 0) {
        return 32;
    }
    int n = 1;
    if ((x >> 16) == 0) {
        n += 16; x <<= 16;
    }
    if ((x >> 24) == 0) {
        n += 8; x <<= 8;
    }
    if ((x >> 28) == 0) {
        n += 4; x <<= 4;
    }
    if ((x >> 30) == 0) {
        n += 2; x <<= 2;
    }
    n -= (x >> 31);
    return n;
}


Vijos 1201 高低位交换(Matrix67原创)


其实当时搞单片机对这些位运算用的比较多

专门来研究的话更是感觉位运算厉害啊,虽然用位运算有时候会导致程序可读性很低,但是其实可以加个注释来说明一下就更好了


#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#include <cmath>

using namespace std;

typedef unsigned int uint;

int main()
{
    uint n;
    scanf("%u", &n);
    n = (n << 16) | (n >> 16);
    printf("%u\n", n);
    return 0;
}




版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

趣题:用位运算生成下一个含有k个1的二进制数

http://www.matrix67.com/blog/archives/813     在所有8-bit的整数中,含有k个数字“1”的二进制数一共有C(8,k)个。给出其中的一个二进制数,你...

剑指Offer面试题10二进制中1的个数(位运算)附带一个进制转换题

面试题10:二进制中1的个数 实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如9的二进制是1001,则输出2。 位运算相关知识:位运算共5种,与(&)是同时为1才为1,或(|)是有一...

位运算--统计一个数的二进制序列中1的个数

给出一个十进制数,求出该数的二进制序列中1的个数。比如 15 的二进制序列是 00000000  00000000  00000000 00001111   1的个数是4. 下边将给出几种不同的解...

位运算的妙用_判断2的乘方和二进制1的个数

本来定好每个星期写一遍算法的博客的,可是因为最近赶项目,博客停止更新一段时间了,现在抽空继续更新。本来心情挺好的,一看手机,欠费两百多,真是日了狗了,百度云没有设置只能 wifi 下下载和上传,用了我...

位运算--求一个 数二进制中1的个数

1.五种位运算:(1)&(与)–有0则0;无0则1; (2)|(或)–有1则1,无1则0; (3)^(亦或)–相同为0,不同为1; (4)>>右移(最右边的位被抛弃) 正数,最左边...

C语言位运算的应用(1)

如果能巧妙地应用位运算来解决一些问题,可能会带来意想不到的惊喜! 以下是一些常见的位运算的应用; 1,不用中间变量来交换两个数的数值。 首先我们可以用 “+ - * / ” 四则运算符来解决这...

位运算简介及实用技巧(二):进阶篇(1) [Matrix67]

二进制中的1有奇数个还是偶数个     我们可以用下面的代码来计算一个32位整数的二进制中1的个数的奇偶性,当输入数据的二进制表示里有偶数个数字1时程序输出0,有奇数个则输出1。例如,1314520...

Offer题10 字母表示26进制&二进制中1的个数&位运算相关题目

字母表示26进制二进制中1的个数(面试题10)& 左移右移代替乘除取膜 位运算相关题目 1.字母表示26进制 题目:Excel2003中,用A表示第1列,B表示第2列……Z表示第26...

统计二进制中1的个数和 - 位运算

(1)、按位与(&),将两个操作数化为二进制后并将对应的每一位分别进行逻辑与操作。(a%(2^n)=a&(2^n-1)) (2)、按位或(|),将两个操作数化为二进制后并将对应的每一位分别进行逻辑或...
  • yibcs
  • yibcs
  • 2013年04月30日 18:50
  • 898

二分查找位运算——32位整数中寻找第一个为1的位

问题描述 在程序设计中经常会遇到这样一个问题,即在一个32位整数中,从右到左寻找第一个为1的位。这样的问题是很常见的,而面对这样一个问题,一种常见的解法就是逐位的遍历这个整数中的所有位,直到...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:位运算_1 2016.6.3
举报原因:
原因补充:

(最多只允许输入30个字)