zoukankan      html  css  js  c++  java
  • 位运算骚操作 Part 3

    ▶ 异或运算 "^" 具有的部分性质:

    ● 交换律,结合律

    ● a ^ b == (!a & b) | (a & !b),a ^ 1 == !a,a ^ 0 == a,a ^ a == 0,a ^ !a == 1,

    ● RAID 5 的理解:写入时,数据 A 写入硬盘 0, 数据 B 写入硬盘 1,数据 A^B 写入硬盘 2;读取时,数据 A 可用硬盘 1 和硬盘 2 的数据 B^C 进行验校或恢复

    ▶ 使用异或运算找非重复元素的方法,参考【https://blog.csdn.net/u013609078/article/details/51622077】,但是原文写的像屎一样,很多说法连良定义都没有

    ●(1)整型数组中有 1 个元素只出现一次,其他数字都出现两次,要求找出这个数字

      ■ 把所有元素进行规约异或即可

    ●(2)整型数组中有 2 个元素只出现一次,其他数字都出现两次,要求找出这个数字

      ■ 核心思想是利用两元素的差异(二进制表示中从低位到高位首个不同的位)作为条件来构造子列,把所有与 b 具有相同条件的元素去掉,在该子列中找 a 就等价于问题(1)

      ■ 找出 a 后 b = (a ^ b) ^ a

      ■ 代码

     1 #include <iostream>
     2 #include <vector>
     3 #include <cassert>
     4 
     5 using namespace std;
     6 
     7 inline int getLastOneBit(int num)           // 求 num 最低 "1" 位,返回值有且仅有一位为 1,特例输入 0 返回 0
     8 {
     9     return num & ~(num - 1);
    10 }
    11 
    12 vector<int> findTwo(vector<int> & haha)
    13 {
    14     int ab = 0;
    15     for (int i = 0; i < haha.size(); i++)   // 第一轮得到 a ^ b,用 ab 来表示
    16         ab ^= haha[i];
    17     assert(ab != 0);                        // 保证 a != b
    18     int lastOneBit = getLastOneBit(ab);     // 取 ab 的最低 "1" 位(定义为从低位到高位,首个至少有一个值为 1 的位),即 a 和 b 二进制表示中从低位到高位首个不同的位
    19                                             //     如 ab = 01100100,则 lastOneBit = 00000100,可能的情况 a = ...100,b = ...000
    20     int a = 0;
    21     for (int i = 0; i < haha.size(); i++)   // 不妨设 a 在 lastOneBit 位上为 1,第二轮取所有该位为 1 的元素作为子列进行异或运算,重复元素不影响结果
    22     {
    23         if (haha[i] & lastOneBit)
    24             a ^= haha[i];
    25     }
    26     int b = ab ^ a;
    27     cout << "findTwo -> a: " << a << ", b: " << b << endl;
    28     return vector<int>({ a, b });
    29 }
    30 
    31 int main()
    32 {
    33     vector<int> haha = { 2, 3, 5, 2, 3, 5, 7, 11 };
    34     vector<int> result = findTwo(haha);
    35 
    36     cout << "result -> a: " << result[0] << ", b: " << result[1] << endl;
    37     return 0;
    38 }

      ■ 输出结果

    findTwo -> a: 7, b: 11
    result -> a: 7, b: 11

    ●(3)整型数组中有 3 个元素只出现一次,其他数字都出现两次,要求找出这个数字

      ■ 引理:若x ^ y ^ z == 0,则 x,y,z 最低 "1" 位有且仅有两个数为 1,如 x == 00001000,y == 00000100,z == 00001100 满足该式

       证明:3 个数的最低 "1" 位四种情况:

         ① 全相同,该位 1 ^ 1 ^ 1 == 1;

         ② 全不同,该位 1 ^ 0 ^ 0 == 1;

         ③ 两个 0 一个 1,该位 1 ^ 0 ^ 0 == 1;

         ④ 两个 1 一个 0,该位 1 ^ 1 ^ 0 == 0,仅这种情况满足上式

      ■ 注意到恒等式 (a ^ b) ^ (a ^ c) ^ (b ^ c) == ((a^b^c) ^ c) ^ ((a^b^c) ^ b) ^ ((a^b^c) ^ a) == 0,所以 (a ^ b),(a ^ c),(b ^ c) 三个数的的最低 "1" 位有且仅有两个 1,

       不妨设 (a ^ c) 和 (b ^ c) 该位为 1,(a ^ b) 的该位为 0,则 (a ^ c) 与 (b ^ c) 的最低 "1" 位相同,且 (a ^ b) 的最低 "1" 位不同,

       于是 getLastOneBit(a ^ c) ^ getLastOneBit(b ^ c) ^ getLastOneBit(a ^ b) == getLastOneBit(a ^ b),干掉了最低 "1" 位相同的那两个,

       类似问题(2),我们构造原数组的一个子列,筛选出原数组中满足 getLastOneBit((a^b^c) ^ x) == getLastOneBit(a ^ b) 的那些元素 x,

         ① 若 x 是重复元素,则 x 会在子列中出现零次或两次,不影响异或的结果;

         ② 若 x == a || x == b,则上述等式不成立,x 不在子列中;

         ③ 若 x == c,则上述等式成立,x 在子列中

       所以在该子列中找 c 就等价于问题(1),然后把找到的 c 添加到原数组末尾(相当于新子列中 c 为重复元素),在该新数组中找 a 和 b 就等价于问题(2)   

      ■ 代码

     1 #include <iostream>
     2 #include <vector>
     3 #include <cassert>
     4 
     5 using namespace std;
     6 
     7 inline int getLastOneBit(int num)
     8 {
     9     return num & ~(num - 1);
    10 }
    11 
    12 vector<int> findTwo(vector<int> & haha)
    13 {
    14     int ab = 0;
    15     for (int i = 0; i < haha.size(); i++)
    16         ab ^= haha[i];
    17     assert(ab != 0);
    18     int lastOneBit = getLastOneBit(ab);
    19 
    20     int a = 0;
    21     for (int i = 0; i < haha.size(); ++i)
    22     {
    23         if (haha[i] & lastOneBit)
    24             a ^= haha[i];
    25     }
    26     int b = ab ^ a;
    27     cout << "findTwo -> a: " << a << ", b: " << b << endl;
    28     return vector<int>({ a, b });
    29 }
    30 
    31 int findOne(vector<int> & haha)
    32 {
    33     int abc = 0;
    34     for (int i = 0; i < haha.size(); i++)               // 第一轮得到 a ^ b ^ c,用 abc 表示
    35         abc ^= haha[i];
    36 
    37     int lastOneBit = 0;
    38     for (int i = 0; i < haha.size(); i++)               // 第二轮得到 getLastOneBit(a ^ b) ^ getLastOneBit(a ^ c) ^ getLastOneBit(b ^ c),成对元素不影响结果
    39         lastOneBit ^= getLastOneBit(abc ^ haha[i]);
    40     //lastOneBit = getLastOneBit(lastOneBit);           // 原博客中有这句,实际不需要
    41 
    42     int c = 0;                                          
    43     for (int i = 0; i < haha.size(); i++)               // 第三轮选出所有最低 "1" 位与 lastOneBit 相同的元素(a 和 b 肯定不在里边),用异或找出 c
    44     {
    45         if (getLastOneBit(abc ^ haha[i]) == lastOneBit)
    46             c ^= haha[i];
    47     }
    48     cout << "findOne -> c: " << c << endl;
    49     return c;
    50 }
    51 int main()
    52 {
    53     vector<int> haha = { 2, 3, 5, 2, 3, 5, 7, 11, 13 };
    54     int c = findOne(haha);                              // 先找出一个非重复的元素
    55     haha.push_back(c);                                  // 将找到的元素添加到原数组末尾,相当于去掉了这个非重复元素
    56     vector<int> result = findTwo(haha);                 // 寻找剩余两个非重复元素
    57     result.push_back(c);
    58 
    59     cout << "result -> a: " << result[0] << ", b: " << result[1] << ", c: " << result[2] << endl;
    60     return 0;
    61 }

      ■ 输出结果

    findOne -> c: 13
    findTwo -> a: 7, b: 11
    result -> a: 7, b: 11, c: 13

    ▶ 格雷码与普通二进制码相互转换。证明使用中括号记法,考虑结果每一位来源于 bin 的哪几位的计算

    1 gray = bin ^ (bin >> 1)
    2 bin = gray ^ (gray >> 1)
  • 相关阅读:
    递归删除指定目录下的 .git 文件
    mina 字节数组编解码器的写法 I
    爬取大众点评
    使用Scrapy抓取数据
    Redis:默认配置文件redis.conf详解
    Redis:五种数据类型的简单增删改查
    使用python-docx生成Word文档
    IT部门域事件与业务分析
    因为说比做容易,所以要少说慎说
    主要问题
  • 原文地址:https://www.cnblogs.com/cuancuancuanhao/p/10476810.html
Copyright © 2011-2022 走看看