位运算之异或运算的理解

 

  • 实验名称:对位运算异或(^)的探究
  • 实验内容:编程实现

输入偶数个数,找出其中出现奇数次的两个数字

  • 分析
  1. 从系统输入n(n为偶数)个数字;
  2. 开一个动态数组把n个数字存起来,知道他们的地址
  3. 在输入n个数字的同时就开始处理他们,也就是进行位运算
  4. 然后得出答案

四、值得注意的问题:

1.首先我们要了解这个异或的的符号的作用,位运算的计算过程都是二进制的,所以我们要探究它的作用,很多情况下我们得自己手动操作。

2.异或的内容是:两个数的二进制0和1,进行比较,只有在比较两个位不同的时候结果才是1,(真),否则就是0(假);也就是输入的两个数,不同才为1,相同为0;

3.这里应该注意到一个细节,那就是0^n=n,0跟任何数异或之后都是那个数的本身,因为0就代表着假,那么那个数字非0也就是真,结果当然是1,如果那个数字是0的话,结果也就是0了,无论结果如何,我们都能知道那个数字是多少。

4:要找出这个答案,其实最难的是异或运算之后的操作,n个数字异或完,也就是那两个奇数次的数字之间的异或,比如:1 1 2 2 2 3 3 4  出现偶数相同的数字肯定异或之后就是0,所有出现偶数次的数字通过异或之后就都为0,那么我们就可以知道出现奇数次的数字异或之后肯定保留了下来,比如上面的例子  异或之后的结果就是 2^4  因为2和4两个数出现了奇数次。那要怎么进行分开呢?
5.最值得注意的问题就是接下来了,找到了两个出现奇数次的数字的异或结果了,可是这并不是分开的,是两个异或的结果,那么我们就要想一个办法把其中一个数字分开,也就能找出另一个数字了。
6.对,现在思路出来了,就要去找出一个方法去实现我们所想的,程序就是要通过人去构思,然后用程序去实现我们的想法。这是我对程序的编写的认识。
7.对异或这个符号的运算的概念一定要了解清楚,不然就无从下手了。异或通俗来说就是两个数如果相同那么就是0,不同就是1,这个符号其实就是要找不同的数字。
 
五、编程清单
#include<stdio.h>
int a[10000009];//开一个全局变量,申请更多的空间,在局部开的话,可能不够
int main()
{
    int i,n,x,v,s1,s2,k,t,t1;//n代表个数,v代表接下来所有数字异或之后的值,i是循环变量
    while(scanf("%d",&n)!=EOF)//EOF就是文件结束的意思  这里只是让我们能多次输入样例
    {
        v=0;//V初始化为0,对接下来异或的运算不会产生影响,在前面我已经说清楚了,为什么要这样了。
        for(i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
            v^=a[i];
        }
        s1=v;s2=v;//弄两个变量,是因为s1用来找两个数中的一个赋值的变量,s2是用来找到那个数之后异或一下就是另一个数
        k=0;//这里的k就是代表v出现1的位置
        while(!(s1&1))//这个循环就是用来找出异或v的二进制中从右往左遍历找它第一次出现1的位置
        {//s1&1 这个是算出s1二进制最末尾的值是01 循环条件是还没找到1,要接着找
            s1=s1>>1;//这也是一个位运算>>  这代表右移,因为你在它的二进制的最末尾没有找到1那肯定要把最末尾的那个数字去除,让它的上一位当最末尾,因为前面找1只能是在最末尾的
            k++;//末尾没有出现1,也就是要+1,说明从右往左开始数的第一不是1K其实也就是从右往左开始数1第一次出现的位置
        }
//解释一下为什么找异或结果二进制出现第一个1的作用,首先我们要明白异或的运算,那两个出现奇数次的数字异或起来肯定是不同的,(不会出现相同的,如果是相同那不就违背了出现奇数次的数字了,两个相同的奇数次不就是偶数次吗? 啊哈哈哈)
//0^1=1  这就是异或,那为什么我找v1的位置呢,因为在v1的那个位置  那两个数字也在二进制那两个位置,那么肯定一个是0一个是1,这个我们不用管,接下我们就去遍历一下刚才输入的数在这个位置也出现1,说明就是这个数字了
        for(i=0;i<n;i++)//循环遍历找出现奇数次的那个数
            if((a[i]>>k)&1)//这个判断条件是因为找到了v1在第几个位置,那么要找到我们需要的那个数,所以我右移K位跟v的位置一样,然后最末尾的就是我们要找的位置了  然后就看他是不是1
                v^=a[i];//条件成立,就说明a[i]就是所找的那个数字了。直接对v异或就是 那个数字
       //a^b=v  v^a=b  v^b=a         
        printf("%d %d\n",v,v^s2);//v是刚才找到的那个数字,s2是两个数的异或 v已知的数去异或也就找到了另一个数字了
}
return 0
}
编程运行的结果如下:
 
六、结果分析与调试
1.刚开始以为这个代码存在漏洞,如果出现是0的怎么办,想了很久,后面我发现我对题目没有理解清楚,它保证是会出现奇数次的两个,但我再想想,这不用说都知道啊,肯定啊,如果出现相同,那两个相同的奇数次不就是偶数次了,这不就是没出现奇数次的数字了吗?  所以出现结果0 那就是没有出现奇数次的数字了。
2.当初还遇到一个问题就是如果我遍历去找其中的一个数,如果没有的话,那不就是没有出现两个数字是出现奇数次的吗?我当时没有想到。
3.刚开始写的代码是没有考虑到位运算的,直接对每个数字进行奇数,然后再重新去遍历去找出现奇数次的个数,就是我们要找的。这个就用到了数组,可是我拿去系统评测,结果就是超时和越界,因为他给的数据都很大,如果输入的n1千万,1亿呢,接下来输入的n个数也很大,难道我们用数组的下角标去标记够吗?很显然是不行的,如果我标记的数组开的不够大呢,那肯定会出现越界问题。
4.想了很久,向别人请教才知道用位运算来做很高效,然后我就去了解位运算的内容,果然很好用。我是ACM集训队成员,所以每天都在刷题,这个问题就是在OJ的网站遇到的,今天我了解了,我有自己的心得就写下来了。
 
七、结论
1.多思考,一条路行不通,我们就应该换应该思路去做,不是因为我们想不到,可能是这个方法的算术不够好,我们就应该找应该更好的算法来实现这个编程。
2.对位运算的符号&^>> 这几个符号我们一定要很了解,计算机的二进制我们应该利用起来,有时候看起来很繁琐,但也可能是非常的有用,我们应该利用我们身边所能利用的资源。
3.做这个题目我们应该先看到数据的范围,小数据我们可能用暴力的方法直接过,可是遇到大数据的话要怎么办呢。所以我们遇到问题的时候应该先看这个数据是不是很大,是很大的话,我们应该找一个好的算法来解决这个问题。
4.其实位运算的用途还有很多,在遇到大数据的时候其实是非常好用的,现在对位运算的了解还是不够,这是不足的地方,我希望以后学到了更多的,再写下自己的心得,还有位运算&|,这两个,这两个位运算我运用的还不是很经常。
5.位运算的应用,极大方便了我们对数据的处理,这个位运算就想我所遇到的,数据大的时候明显不适合普通的算法去解决,用到它,极大提高了效率。
6.其实这个程序我觉得还能再稍微修改一下,我第一次循环遍历找到其中的一个数,如果这个数刚好出现在很前面,那我找到它了,我就直接跳出,就不需要再遍历我接下来的数组,这个只要找if的判断条件找之后,异或之后就直接跳出就行了。
7.如果输入的每个数值都很大的话,我们应该把数组的类型改成long long int   因为这个能让输入的数据更大。遇到什么清空,我们就应该想到一个去解决它。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值