打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
编程技巧
作者:yunyu5120
这是我的这一系列文章的第一篇,主要讲述我学习过程中积累的一些编程技巧,由于我也是一个初学者,高手莫笑。这一篇主要讲解位运算的基础知识鱼与其简单应用,我主要以C/C++语言讲述,其他语言可以类推。如果你已经对位运算基础和应用十分熟悉,那么本文并不适合你。
我相信还是有一部分人对位运算还不是很了解,我希望你在看了本博文之后能对位运算有深刻的了解,并运能够用自如,能够体会到编程的乐趣。
“写程序,位运算是必要的吗?”
这个问题问的好,其实位运算并不是必要的,有什多方法可以可以代替位运算,但是位运算其特有的对程序的优化特点是无法替代的!当然如果你在写Windows应用程序,其中调用的一些Windows APi 你就必须用到位运算,如最简单的MessageBox。当然其中牵扯到的位运算过于简单,就是简单的或运算。想想当初写的第一个windows程序用到MessageBox竟然出现了一个windows窗口,而不是那黑糊糊的Console,让我兴奋了还一段时间!可是当时的我也不知道这里面牵扯的很多知识,甚至什么是API都不知道!
我们在学习C/C++的时候书本上对位运算的相关知识讲得很少,就是简单的“或与非”。如果你的记性好那么你还会记得在位运算中还有一个运算叫做 “异或”运算和移位运算。不知道你现在对位运算的基础是否还清楚,我在这里假设我们都忘了位运算的基础,所以下面我们对位运算进行复习一下。
C/C++语言提供的位运算符有:
运算符含义功能
&按位与如果两个相应的二进制位都为1,则该位的结果值为1;否则为0。
|按位或两个相应的二进制位中只要有一个为1,该位的结果值为1。
∧按位异或若参加运算的两个二进制位同号则结果为0(假)异号则结果为1(真)
~取反~是一个单目(元)运算符,用来对一个二进制数按位取反,即将0变1,将1变0。
<<左移左移运算符是用来将一个数的各二进制位全部左移N位,右补0。
>>右移表示将a的各二进制位右移N位,移到右端的低位被舍弃,对无符号数,高位补0。
位运算的结果演示:
位运算或 “|” or与 “&”and非 “~” not异或 “^” xor
操作数101010101110101011010101010000001
操作数20010101010101010(无)01111111
也能算结果01111111100000000101010111111110
好了看了上面的两个表格,相信你已经对位运算有所了解了,那么接下来,我们就来讲讲位运算的应用。
1、  用于整数的奇偶性判断
想想,我们要判断一个数的奇偶性,在没用位运算之前我们可以用下列的代码来实现:
[cpp]
template<class Type>
bool Parity(Type value)
{
if(value % 2 == 0)
return false;
else
return true;
}
//加以优化
template<class Type>
inline bool Parity(Type value)
{
return (value % 2 != 0);
}
要知道,上面的代码我们使用的是对2取余,如果操作数value是小数的话,还勉强行得通,但是value是一个上百万的大数,那么这就白白浪费了CPU的大量时间,程序的效率和性能就很差。我们知道任何数在计算机储存中都是以二进制储存的,细心的你就会发现在二进制的最小一位有个特点,为0就是偶数,为1就是奇数,按照这个原理我们根本没必要让我们的CPU大哥白白做那么多的工作,只要一步判断就可以了。接下来就让我们看看位运算的精妙之处!
那么我们的目的就是判断最小位是0还是1,可是我们怎么判断呢?我们要用位运算阿里判断,就是与或非。在上面的复习之中我们只说了位运算的计算方法,并没有说其用处。那么在这里我们用到的就是“与”!与运算特有的一个功能就是判断指定位上的值(0或1)。我们来看下面的表格(与运算)。
操作数110101010010101011111111111111110
操作数200000001000000010000000100000001
运算结果00000000000000010000000100000000
我们要注意一下这里的 操作数2 ,它只有最低位是1,其余位都是0,这就是关键所在,操作数1是随机值。我们看看结果只会有两种结果:0或1。这个结果就取决于操作数1的最低位,它为1时就为1,为0时就为0.
“那么我要判断的是第二位呢?”
好!那我们就把操作数2改为 00000010 那么结果就只会有 00000000 或 00000010 其结果取决于第二位。
有了这个基础那么我们来看看怎么用位运算判断奇偶性吧:
[cpp]
template<class Type>
bool Parity(Type value)
{
if(value & 0x0001 == 0)
return false;
else
return true;
}
//加以优化
template<class Type>
inline bool Parity(Type value)
{
return (value & 1 != 0);
}
//在简化
#define PARITY(value) (value&1)
使用a%2来判断奇偶性和a & 1是一样的作用,但是a & 1要快好多。
2、  判断n是否是2的正整数冪
所谓2的正整数冪就是指 2,4,8,16,32,64,128,256,512,1024,2018.............等数字,若何判断一个数是否是这样的数呢?我们看看不用位运算的计算方法:
[cpp]
#include "math.h"
template<class Type>
bool IsPowerOfTwo(Type value)
{
for(int i = 0,l = 8*sizeof(value); i < l ;i++)
{
if(pow(2,i) == value)
{
return true;
}
}
return false;
}
在这个算法中,我们使用了一个循环。其原理非常简单就是一一的对比,但是其中还调用了数学函数库,效率大大降低。接下来我们讲讲怎样用位运算来判断。我们首先要研究一下这些数的特性,请看下表(与运算):
2的幂8163264
n00001000000100000010000001000000
n-100000111000011110001111100111111
与结果00000000000000000000000000000000
我们发现 n&(n-1) =  0  我们可以 用逻辑非 !(n&(n-1)) =  1 。那是不是这样就可以了呢,你会发现 !(0&(0-1)) =  1 但是 0并不是 2的正整数冪。我们可以用 逻辑与 !(n&(n-1)) &&  n  = 1;请看下面的代码:
[cpp]
template<class Type>
inline bool IsPowerOfTwo(Type value)
{
if((!(n&(n-1)) ) && n == 1)
return true;
else
return false;
}
//简化
#define ISPOWEROFTWO(n) ((!(n&(n-1)) ) && n)
3、  统计n中1的个数
朴素的统计办法是:先判断n的奇偶性,为奇数时计数器增加1,然后将n右移一位,重复上面步骤,直到移位完毕。
[cpp]
template<class Type>
inline bool Parity(Type value)
{
return (value % 2 != 0);
}
template<class Type>
inline int CountOne(Type value)
{
if(value != 0)
{
return Parity(value) + CountOne(value >> 1);
}
return 0;
}
朴素的统计办法是比较简单的,那么我们来看看比较高级的办法。
举例说明,
考虑2位整数 n=11,里边有2个1,先提取里边的偶数位10,奇数位01,把偶数位右移1位,然后与奇数位相加,因为每对奇偶位相加的和不会超过“两位”,所以结果中每两位保存着数n中1的个数;
相应的如果n是四位整数 n=0111,先以“一位”为单位做奇偶位提取,然后偶数位移位(右移1位),相加;再以“两位”为单位做奇偶提取,偶数位移位(这时就需要移2位),相加,因为此时没对奇偶位的和不会超过“四位”,所以结果中保存着n中1的个数;
依次类推可以得出更多位n的算法。整个思想类似分治法。
在这里就顺便说一下常用的二进制数:
二进制数二进制值用处
0xAAAAAAAA10101010101010101010101010101010偶数位为1,以1位为单位提取奇位
0x5555555501010101010101010101010101010101奇数位为1,以1位为单位提取偶位
0xCCCCCCCC11001100110011001100110011001100以“2位”为单位提取奇位
0x3333333300110011001100110011001100110011以“2位”为单位提取偶位
0xF0F0F0F011110000111100001111000011110000以“8位”为单位提取奇位
0x0F0F0F0F00001111000011110000111100001111以“8位”为单位提取偶位
0xFFFF000011111111111111110000000000000000以“16位”为单位提取奇位
0x0000FFFF00000000000000001111111111111111以“16位”为单位提取偶位
例如:32位无符 号数的1的个数可以这样数:
[cpp]
int CountOne(unsigned long n)
{
//0xAAAAAAAA,0x55555555分别是以“1位”为单位提取奇偶位
n = ((n & 0xAAAAAAAA) >> 1) + (n & 0x55555555);
//0xCCCCCCCC,0x33333333分别是以“2位”为单位提取奇偶位
n = ((n & 0xCCCCCCCC) >> 2) + (n & 0x33333333);
//0xF0F0F0F0,0x0F0F0F0F分别是以“4位”为单位提取奇偶位
n = ((n & 0xF0F0F0F0) >> 4) + (n & 0x0F0F0F0F);
//0xFF00FF00,0x00FF00FF分别是以“8位”为单位提取奇偶位
n = ((n & 0xFF00FF00) >> 8) + (n & 0x00FF00FF);
//0xFFFF0000,0x0000FFFF分别是以“16位”为单位提取奇偶位
n = ((n & 0xFFFF0000) >> 16) + (n & 0x0000FFFF);
return n;
}
看起来似乎采用位运算的代码比朴素方法代码要复杂的多,但是在性能上有着朴素方法无法比拟的优越性,只要四步简单的运算就能达到目的,而朴素方法不是用循环就是递归,这大大降低了CPU的运算性能。
4、对于正整数的模运算(注意,负数不能这么算)
先说下比较简单的:
乘除法是很消耗时间的,只要对数左移一位就是乘以2,右移一位就是除以2,据说用位运算效率提高了60%。
乘2^k 众所周知: n<<k。所以你以后还会傻傻地去敲2566*4的结果10264吗?直接2566<<4就搞定了,又快又准确。
除2^k众所周知: n>>k。
那么 mod 2^k 呢?(对2的倍数取模)
n&((1<<k)-1)
用通俗的言语来描述就是,对2的倍数取模,只要将数与2的倍数-1做按位与运算即可。
好!方便理解就举个例子吧。
思考:如果结果是要求模2^k时,我们真的需要每次都取模吗?
在此很容易让人想到快速幂取模法。
快速幂取模算法
经常做题目的时候会遇到要计算 a^b mod c 的情况,这时候,一个不小心就TLE了。那么如何解决这个问题呢?位运算来帮你吧。
首先介绍一下秦九韶算法:(数值分析讲得很清楚)
把一个n次多项式f(x) = a[n]x^n+a[n-1]x^(n-1)+......+a[1]x+a[0]改写成如下形式:
f(x) = a[n]x^n+a[n-1]x^(n-1))+......+a[1]x+a[0]
= (a[n]x^(n-1)+a[n-1]x^(n-2)+......+a[1])x+a[0]
= ((a[n]x^(n-2)+a[n-1]x^(n-3)+......+a[2])x+a[1])x+a[0]
=. .....
= (......((a[n]x+a[n-1])x+a[n-2])x+......+a[1])x+a[0].
求多项式的值时,首先计算最内层括号内一次多项式的值,即
v[1]=a[n]x+a[n-1]
然后由内向外逐层计算一次多项式的值,即
v[2]=v[1]x+a[n-2]
v[3]=v[2]x+a[n-3]
......
v[n]=v[n-1]x+a[0]
这样,求n次多项式f(x)的值就转化为求n个一次多项式的值。
好!有了前面的基础知识,我们开始解决问题吧
由(a × b) mod c=( (a mod c) × b) mod c.
我们可以将 b先表示成就:
b = a[t] × 2^t + a[t-1]× 2^(t-1) + …… + a[0] × 2^0.  (a[i]=[0,1]).
这样我们由 a^b  mod  c = (a^(a[t] × 2^t  +  a[t-1] × 2^(t-1) + …a[0] × 2^0) mod c.
然而我们求  a^( 2^(i+1) ) mod c=( (a^(2^i)) mod c)^2 mod c .求得。
具体实现如下:
使用秦九韶算法思想进行快速幂模算法,简洁漂亮
// 快速计算 (a ^ p) % m 的值
[cpp]
__int64 FastM(__int64 a, __int64 p, __int64 m)
{
if (p == 0) return 1;
__int64  r = a % m;
__int64  k = 1;
while (p > 1)
{
if ((p & 1)!=0)
{
k = (k * r) % m;
}
r = (r * r) % m;
p >>= 1;
}
return (r * k) % m;
}
http://acm.pku.edu.cn/JudgeOnline/problem?id=3070
5、计算掩码
比如一个截取低6位的掩码:0×3F
用位运算这么表示:(1 << 6) - 1
这样也非常好读取掩码,因为掩码的位数直接体现在表达式里。
按位或运算很简单,只要a和b中相应位出现1,那么a|b的结果相应位也为1。就不多说了。
6、子集
枚举出一个集合的子集。设原集合为mask,则下面的代码就可以列出它的所有子集:
for (i = mask ; i ; i = (i - 1) & mask) ;
很漂很漂亮吧。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
位运算 之(1) 按位与(AND)& 操作
C语言编程开发中用好位操作符(转)
C语言位运算详解
C#中的操作符
JAVA里面运算符的如何使用和优先级怎么样?
Java位运算原理及使用讲解
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服