尼玛二进制真神奇


首先,请原谅我作为一个软件系的学生对计算机的无知委屈

其次,请原谅我作为一个理科生对数字的不敏感委屈

嗯,不是专业导论老师死的早,只因上课上的少,外加背的不够好尴尬。。。

一、所谓位运算:

       位运算直接对整数在内存中的二进制位进行操作,优势在于其因为是底层运算,速度自然较一般的运算快。

       位运算的几个运算符(&,|,^,~,<<,>>)的基本含义在此就不做赘述了,其实我刚知道位运算的时候可是一头雾水,根本不知道到底用位运算来干什么。

二、常见的运算用法(参自Matrix67的博客,部分内容有修改和添加)

       1.“&1”,按位与1运算

         对于int x,x&1的结果是什么呢,自然是x的二进制数和000...0001去按位与,如果x是偶数,那么x的二进制数结尾必然是0,那么x&1的结果必然是0;反之,若x为奇数,那么x的二进制数结尾必然是1,那么x&1的结果必然是1。

         因此if(x&1),则成为了在计算时间上优于if(x%2)的判断奇偶的方式。

       2."|1",按位或1运算

        仍旧是int x,x|1的的结果自然是把x的二进制数尾数强行改为1。即,如果x为一个奇数,那么x|1==x;如果x为一个偶数,那么x|1==x+1。可见其作用是找到离x最近的奇数(且该奇数大于等于他本身)同样地,这个方法效率同样优于x%2。

        PS:那么找到最接近的偶数该怎么办呢?自然是同理了。

       3.按位异或

          ①"^1",按位异或1运算:作用,偶数+1,奇数-1。

          ②交换数值:写法:例如对于int x,y;

                                                    x = x ^ y ;

                                                    y = x ^ y ;

                                                    x = x ^ y ;

                                原理:

           首先我们要知道一下“异或运算的逆运算就是其本身”这句话,如何验证呢?你可以尝试编写一个小程序,让int x与任意一个int y进行(x ^ y)^ y运算,然后观察得到的结果。

#include<iostream>
using namespace std;
int main()
{
    int x,y;
    while(cin>>x>>y)
    {
        x^=y^=x^=y;
        cout<<"x="<<x<<",y="<<y<<endl;
    }
    return 0;
}

           在知道了这句话之后,我们就可以利用一对互逆的运算符去做交换数值的事,这里用一对我们熟知的互逆运算符(+、-)为例,对于int x , y ;

           x = x + y ;           y = x - y ;         x = x - y ;

           如此便进行了x与y之间的交换。

           这个方法也因此优化了我们最初学习的用第三个变量int z去进行x和y之间的交换。

           (PS:因为异或运算的逆运算是其本身,所以同样的,我们可以利用这一性质对某串数字进行加密,你所需要的,只是一串数字key,然后将你想保存的数x,进行x ^ key,当你想再得到x的时候,将刚才的结果再进行一次^ key便得到了你想要的信息了)

        4.取反运算:

           ①对于无符号的数来说,对其取反的结果是得到一个该类型数据的最大值与该数的差。

           ②对于有符号的数来说,例如对int x(x为正)取反的结果是-x-1。但为什么会这样呢?原因是计算机将“0”这个非正非负的数存放在了内存的第一位,这就导致正数的最大数在绝对值上要比最小的负数的绝对值小1。

           如:对于short int 型,计算机用$0000到$7FFF依次表示0到32767的数,剩下的$8000到$FFFF依次表示-32768到-1的数。

        5.左<</右>>移运算:

        对于一个二进制数来说,左移n位就是将数的末尾加上n个位的0,转换成十进制就是乘以2的n次幂;右移n位就是将数的后n个位去掉,转换成十进制数就是除以2的n次幂(取整)。

        这种做法速度和效率会远远高于乘法和除法,在应用上如筛法求最大公约数中代替mod、快速幂取模等都有不错效果。

        下面以快速幂取模为例给出一个代码:

//快速计算 (a ^ p) % m 的值
#include<cstdio>
int FastMod(int a,int p,int m)
{
    if (p==0) return 1;
    int  r=a%m;
    int  k=1;
    while(p>1)
    {
        if((p&1)!=0)
            k=(k*r)%m;
        r=(r*r)%m;
        p>>=1;
    }
    return (r*k)%m;
}
int main()
{
    int a,p,m;
    while(~scanf("%d%d%d",&a,&p,&m))
    {
        printf("%d\n",FastMod(a,p,m));
    }
    return 0;
}


三、尼玛神奇的二进制
最后,让我们以一串有趣的数列结束吧:

0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1 …

                       如何解释它的规律呢?

嗯,它们分别表示 0, 1, 2, 3, 4, … 的二进制表达中有多少个数字 1 ,其中 0 代表有偶数个数字 1 , 1 代表有奇数个数字 1 。

                       那我们如何得到这串数列呢?

方法应该不止一种:

①递推法,首先定义a[0]=0,对于之后的a[i],若i为奇,则因为与其前一个偶数的二进制数只差在了末尾0和1上,所以其奇偶性必然与前一个偶数相反,即a[i]=1-a[i-1];若i为偶,因为其二进制尾数必定是0,所以其奇偶性必然与它mod 2的数相同(如上表中的十进制10与5,8与4的关系一样),即a[i]=a[i>>1]。

②取反并后置法,方法如字面意思一样,从 0 出发,不断执行“取反并后置”的操作。

                       0 → 01 → 0110 → 01101001 → 0110100110010110 → …

这种方法的实质仍是对二进制的一种计算,同样,我们来看上面的表格:

对于十进制数0,1而言,下一步计算的应该是等长度的十进制数2,3,那它们的二进制数有什么关联呢?

0 1<====>10 11

如果没找到规律,那么我们再看下一步归并操作,0,1,2,3和4,5,6,7

00 01 10 11<====>100 101 110 111

我们不难发现,他们之间对应位的区别,只在于二进制数最前面加了个1,这样也就自然解释了为什么要先取反,再后置了。


然后我们在仔细观察中,还能发现这个数列的又一规律:只保留这个序列的a[0],a[2],a[4]...的a[偶数]项,可以发现这个子数列和原数列竟然是一模一样的。而通过第一种递推法的启示,我们还可以得到a[1],a[3],a[5]....的a[奇数]项,是所有偶数项数列或者原数列的取反数列。

这一切,都来自于尼玛神奇的二进制。


(PS:更多的二进制实用技巧,可以去matrix67的博客:www.matrix67.com/blog,或者百度”Matrix67:位运算简介及实用技巧“)



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值