今天刷lintcode遇到了这道easy的A+B Problem,题目的要求是不使用‘+’算出整型数据a和b的和,主要考察的是位操作与四则运算的相互转化。
算法分析
不用‘+’计算数据的和,乍一看一点思路也没有。事实上,对于任何十进制数的加减,我们一般对其做加法时从末尾开始依次相加,逢10进1。同理,对于计算机中常用的二进制数,我们也是从末尾开始逐位相加,不过是逢2进1,借1当2。
事实上有:0+0=0,0+1=1+0=1,1+1=10;对比C语言的XOR操作我们知道:0^0=0,0^1=1^0=1,1^1=0;到这里想必大家已经可以看出共同点,即——当不考虑二进制数的进位时,两数的相加可以看作是二数的异或操作。
再考虑C语言的&操作可得:0&0=0,0&1=1&0=0,1&1=1,即只有二进制数的位同为1时&操作得到1,也即真值为真。而相加时只有都为1时发生进位。所以我们发现——考虑二进制数的进位,&操作可以标明需要进位的位。
对于需要进位的位,我们很容易通过将原数*2^1次完成,而这又可以由左移<<1完成。
根据上述,我们可以得到: (a+b)=(a^b)+(a&b<<1)。(其中,右边需要多次计算,直到a+b的各位已经完全考虑完进位,以得到最后的答案。)
例如:
0110+0100=1010
0110^0100=0010,(0110&0100)<<1=1000;
0010^1000=1010, (0010&1000)<<1=1010;
1010^1010=0000, (0000&1010)<<1=0000.
可知答案为1010.
C++代码如下(采用递归):
class Solution {
public:
/**
* @param a: An integer
* @param b: An integer
* @return: The sum of a and b
*/
int aplusb(int a, int b) {
while (b == 0)
return a;
while (b != 0)
{
int x, y;
x = a; y = b;
a = x ^ y;
b = (x & y) << 1;
return aplusb(a^b, (a&b) << 1);
}
// write your code here
}
};
分析可知该算法复杂度为O(n),其中n为int类型数据的宽度。
乘法及幂运算(multiplication& exponentiation)
解决完这个问题以后,我们很容易想到,如果是乘法和幂运算是不是也可以用位操作代替‘*’或者pow函数呢?
事实上,根据乘法的结合律我们知道a*(b+c)=a*b+a*c,同样的,对于二进制的运算也有相同的规则。例如:
4*6=4*(4+2)=(1*2^2+0*2^1+0*2^0)*(1*2^2+1*2^1+0*2^0)=1*2^4+1*2^3=24.
一般的,我们有:
二进制数的乘法运算,实际上就是对于每一位的权值的相加。
对于幂运算,如果不运用pow函数实际上很难操作,这里使用的是暴力求解,算法复杂度较高,有时间我会进一步优化算法。
C代码如下(将三者全部整理在一起):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
/**
* @param a: An integer
* @param b: An integer
* @return: The sum of a and b
* @supplement:The mutiplication and exponentiation of a and b
*/
int aplusb(int a, int b) {
if(b == 0)
return a;
while (b != 0)
{
int x, y;
x = a; y = b;
a = x ^ y;
b = (x & y) << 1;
return aplusb(a^b, (a&b) << 1);
}
}
int multiply(int a, int b){
int sum = 0;
while (b == 0)
{
return 0;
}
while (b != 0)
{
int i;
for (i = 0;b != 0; ++i)
{
if ((b & 1) == 1)
{
sum = aplusb(sum, a << i);
b >>= 1;
}
else b >>= 1;
}
}
return sum;
}
int exponentiation(int a, int b){
int exp = 1;
if(b != 0)
{
int i;
for ( i = 0; b != 0; i++)
{
if ((b & 1) == 1)
{
int temp = 1;
int j;
int y = 1;
if (i == 0)
{
exp = a;
continue;
}
for ( j = 0; j < i; j++)
{
temp = multiply(temp, 2); //i次循环使得temp=2^i
}
for ( j = 0; j < temp; j++)//temp个a相乘
{
y = multiply(y , a);
}
exp = multiply(exp, y);//exp记录每一项,再与新的一项相乘
b >>= 1;
}
else b >>= 1;
}
}
return exp;
}
int main(){
int a, b;
printf("Enter the numbers to be calculated:");
scanf("%d,%d", &a, &b);
printf("The sum of a and b is %d.", aplusb(a, b));
printf("\nThe multiplication of a and b is %d.", multiply(a, b));
printf("\nThe exponentiation result of a and b is %d", exponentiation(a, b));
system("pause");
return 0;
}
最后
由一道easy的LintcodeProblem所引出的思考其实并不那么浅薄,由这道题目我们发现其实位操作进一步揭体现了二进制的特点。
THANK YOU.