zoukankan      html  css  js  c++  java
  • 关于位运算(转)

    本内容转自:https://www.cnblogs.com/Kobe10/p/6306183.html#undefined,参考:https://blog.csdn.net/zzran/article/details/8108787 为了方便加深印象,自己又照着重新写了一遍。人家这篇博客写的有思想、有深度,想着要是哪一天我也能独自总结出这样的精髓,一定要买颗糖奖励一下,哈哈.......皮一下?

      第一次接触到这种比较伤脑细胞的题,这是一个关于位运算比较简单的入门题,原题是这样描述的:在一个整型数组中有一个元素只出现一次,其它元素都出现两次。求出只出现一次的元素。(要求:线性时间复杂度,不能使用额外空间。聪明的你能搞定吗?)。很明确的说,我不聪明,也搞不定,按照我那榆木脑袋的思维,一定会先给它们排排序,再找出那只出现一次的的元素。程序代码如下:

     1 //很明显这里数组元素的个数一定是奇数个 
     2 #include<iostream>
     3 #include<algorithm>
     4 using namespace std;
     5 int main(){
     6     int n;
     7     cin>>n;
     8     int a[n];
     9     for(int i=0;i<n;i++)
    10         cin>>a[i];
    11     sort(a,a+n);//排序
    12     for(int i=0;i<n;i+=2){
    13         if(a[i]==a[i+1])
    14             continue;
    15         else{
    16             cout<<a[i]<<endl;
    17             break;
    18         }
    19     }
    20     return 0;     
    21 }

    上面程序数据测试没有问题,但网站无法AC,说得好听点:就是无法达到题目所要求的线性时间复杂度,说得难听点:这代码写了跟没写一样。接下来请看大神的思维:

      这个题目的突破口在哪里?题目为什么要强调有一个数字出现一次,其他的出现两次?我们想到了异或运算的性质:任何一个数字异或它自己都等于0,任何一个数字和0异或都等于它本身也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字,因为那些出现两次的数字全部在异或中抵消掉了。

    举个栗子:2  3  4  2  3

    所有数字依次异或运算:2 ^ 3 ^ 4 ^ 2 ^ 3 = (2 ^ 2) ^ (3 ^ 3) ^ 4= 0 ^ 0 ^ 4 = 4  (满足分配律)

    根据以上思维,代码如下:

     1 #include<iostream>
     2 using namespace std;
     3 int main(){
     4     int n,num;
     5     cin>>n;
     6     int a[n];
     7     for(int i=0;i<n;i++)
     8         cin>>a[i];
     9     for(int i=0;i<n;i++)
    10         num^=a[i];
    11     cout<<num<<endl;
    12     return 0;
    13 }

      当你看到这的时候,你以为就完了???兄弟请不要急忙离开,请往下看,还有一个更头疼,更伤脑筋的问题:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)

      这是上一问题的升级版,如果还是用排序的方法,很遗憾,那你就可以淘汰出局了!有了上面简单问题的解决方案之后,我们回到原始的问题。如果能够把原数组分为两个子数组。在每个子数组中,包含一个只出现一次的数字,而其他数字都出现两次。如果能够这样拆分原数组,按照前面异或的办法就是分别求出这两个只出现一次的数字了。我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为其他数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0,也就是说在这个结果数字的二进制表示中至少就有一位为1。我们在结果数字中找到第一个为1的位的位置,记为第N位。现在我们以第N位是不是1为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N位都为1,而第二个子数组的每个数字的第N位都为0(不知道这段黑体字你有没有看懂,看不懂的话自己在草稿纸上举个例子就很容易明白了)。现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其他数字都出现了两次。因此到此为止,所有的问题我们都已经解决。

    基于上述思路,我们不难写出如下代码:

    void FindNumsAppearOnce(int data[], int length, int &num1, int &num2){
          if (length < 2)
                return;
          int resultExclusiveOR = 0;
          for (int i = 0; i < length; ++ i)
                resultExclusiveOR ^= data[i];//得到数组中只出现一次的数字的异或结果   
          unsigned int indexOf1 = FindFirstBitIs1(resultExclusiveOR);//索引异或结果的二进制位数中第一位为1的位数(从右向左第一位),并将位数存放在indexOf1中
          num1 = num2 = 0;
          for (int j = 0; j < length; ++ j){//分组 
                if(IsBit1(data[j], indexOf1))
                      num1 ^= data[j];
                else
                      num2 ^= data[j];
          }
    }
    unsigned int FindFirstBitIs1(int num){//找到第一位为1的位数 
          int indexBit = 0;
          while (((num & 1) == 0) && (indexBit < 32)){//这里<32,是为了防止越界,因为int数据类型最大只有2^32,只有32位
                num = num >> 1;
                ++ indexBit;
          }
          return indexBit;
    }
    bool IsBit1(int num, unsigned int indexBit){//判断是否为1 
          num = num >> indexBit;
          return (num & 1);
    }

    完整代码如下:

     1 #include<iostream>
     2 using namespace std;
     3 int find_1(int num){
     4     int m=0;//存放第一位为1的位数
     5     while(((m&1)==0)&&m<32){
     6         num=num>>1;//如果第一位不是1,右移继续判断,直到为1停止,并返回m 
     7         m++;
     8     }
     9     return m; 
    10 }
    11 bool is_1(int x,int m){
    12     x=x>>m;
    13     return (x&1);
    14 }
    15 void f(int a[],int n,int &num1,int &num2){
    16     if(n<=2)
    17         return ;
    18     int num1_OR_num2=0;//存放只出现一次两个数的异或结果
    19     for(int i=0;i<n;i++)
    20         num1_OR_num2^=a[i];
    21     int locateIS1=find_1(num1_OR_num2);//得到异或结果中第一位为1的位数
    22     num1=num2=0;
    23     for(int i=0;i<n;i++){
    24         if(is_1(a[i],locateIS1)){
    25             num1^=a[i];
    26         }
    27         else{
    28             num2^=a[i];
    29         }
    30     } 
    31 }
    32 int main(){
    33     int n,num1,num2;
    34     cin>>n;
    35     int a[n];
    36     for(int i=0;i<n;i++)
    37         cin>>a[i];
    38     f(a,n,num1,num2);
    39     cout<<num1<<" "<<num2<<endl;
    40     return 0;
    41 }

    运行结果如下:

      当你看到这时,请君稍安勿躁,请继续再往下看:

      题目:一个数组中有三个数字a、b、c只出现一次,其他数字都出现了两次。请找出三个只出现一次的数字。(与最前面的一题不同,前面是2个不同,现在是3个)

      分析:在这道题中,如果我们能够找出一个只出现一次的数字,剩下两个只出现一次的数字就很容易找出来了。如果我们把数组中所有数字都异或起来,那最终的结果(记为x)就是a、b、c三个数字的异或结果(x=a^b^c)。其他出现了两次的数字在异或运算中相互抵消了。我们可以证明异或的结果x不可能是a、b、c三个互不相同的数字中的任何一个。我们用反证法证明。假设x等于a、b、c中的某一个。比如x等于a,也就是a=a^b^c。因此b^c等于0,即b等于c。这与a、b、c是三个互不相同的三个数相矛盾。由于x与a、b、c都各不相同,因此x^a、x^b、x^c都不等于0我们定义一个函数f(n),它的结果是保留数字n的二进制表示中的最后一位1,而把其他所有位都变成0。比如十进制6表示成二进制是0110,因此f(6)的结果为2(二进制为0010)。f(x^a)、f(x^b)、f(x^c)的结果均不等于0接着我们考虑f(x^a)^f(x^b)^f(x^c)的结果。由于对于非0的n,f(n)的结果的二进制表示中只有一个数位是1,因此f(x^a)^f(x^b)^f(x^c)的结果肯定不为0。这是因为对于任意三个非零的数i、j、k,f(i)^f(j)的结果要么为0,要么结果的二进制结果中有两个1不管是那种情况,f(i)^f(j)都不可能等于f(k),因为f(k)不等于0,并且结果的二进制中只有一位是1于是f(x^a)^f(x^b)^f(x^c)的结果的二进制中至少有一位是1。假设最后一位是1的位是第m位。那么x^a、x^b、x^c的结果中,有一个或者三个数字的第m位是1

      接下来我们证明x^a、x^b、x^c的三个结果第m位不可能都是1。还是用反证法证明。如果x^a、x^b、x^c的第m位都是1,那么a、b、c三个数字的第m位和x的第m位都相反,因此a、b、c三个数字的第m位相同。如果a、b、c三个数字的第m位都是0,x=a^b^c结果的第m位是0。由于x和a两个数字的第m位都是0,x^a结果的第m位应该是0。同理可以证明x^b、x^c第m位都是0。这与我们的假设矛盾。如果a、b、c三个数字的第m位都是1,x=a^b^c结果的第m位是1。由于x和a两个数字的第m位都是1,x^a结果的第m位应该是0。同理可以证明x^b、x^c第m位都是0。这还是与我们的假设矛盾。因此x^a、x^b、x^c三个数字中,只有一个数字的第m位是1。于是我们找到了能够区分a、b、c三个数字的标准。这三个数字中,只有一个数字满足这个标准,而另外两个数字不满足。一旦这个满足标准数字找出来之后,另外两个数字也就可以找出来了。

    代码如下:

     1 #include<iostream>
     2 #include<stdio.h> 
     3 using namespace std; 
     4 int get_first_bit(int num)  
     5 {  
     6     return num&~(num-1);  
     7 }  
     8 void get_two_unique_num(int *a,int n,int *num1,int *num2)  
     9 {  
    10     int result_code=0;  
    11     for(int i=0;i<n;i++)  
    12         result_code^=a[i];  
    13     int diff=get_first_bit(result_code);  
    14     *num1=0;  
    15     *num2=0;  
    16     for(int i=0;i<n;i++)  
    17     {  
    18         if(a[i]&diff)  
    19         {  
    20             (*num1)^=a[i];  
    21         }  
    22         else  
    23         {  
    24             (*num2)^=a[i];  
    25         }  
    26     }  
    27 }  
    28 void get_three_unique_num(int *a,int n,int *num1,int *num2,int *num3)  
    29 {  
    30     int result_code=0;  
    31     for(int i=0;i<n;i++)  
    32         result_code^=a[i];  
    33     int flag=0;  
    34     for(int i=0;i<n;i++)  
    35         flag^=get_first_bit(result_code^a[i]);  
    36     flag=get_first_bit(flag);  
    37     *num1=0;  
    38     for(int i=0;i<n;i++)  
    39     {  
    40         if(get_first_bit(result_code^a[i])==flag)  
    41         {  
    42             (*num1)^=a[i];  
    43         }  
    44     }  
    45     for(int i=0;i<n;i++)  
    46     {  
    47         if(a[i]==(*num1))  
    48         {  
    49             int temp=a[i];  
    50             a[i]=a[n-1];  
    51             a[n-1]=temp;  
    52             break;  
    53         }  
    54     }  
    55     get_two_unique_num(a,n-1,num2,num3);  
    56 }  
    57 int main()  
    58 {  
    59     int n;
    60     cin>>n;
    61     int a[n];
    62     for(int i=0;i<n;i++)
    63         cin>>a[i]; 
    64     int num1,num2,num3;  
    65     get_three_unique_num(a,n,&num1,&num2,&num3);  
    66     cout<<num1<<" "<<num2<<" "<<num3<<endl;
    67     return 0;
    68 }  

    运行结果如下:

  • 相关阅读:
    wp8 入门到精通
    C# 从入门到精通
    wp8 json2csharp
    wp8 安装.Net3.5
    delphi资源文件制作及使用详解
    delphi弹出选择对话框选择目录SelectDirectory 函数
    delphi 判断WIN8 , WIN8.1 , WIN10 系统版本
    外壳扩展创建快捷方式和获取快捷方式的目标对象
    文本究竟是使用哪种字
    用Delphi创建服务程序
  • 原文地址:https://www.cnblogs.com/geziyu/p/9687242.html
Copyright © 2011-2022 走看看