算法3 位运算基础知识及算法例题

位运算也是各大互联网公司面试经常会问的一类问题。

计算机中的数在内存中都是以二进制形式进行存储的,用位运算就是直接对整数在内存中的二进制位进行操作,因此其执行效率非常高,在程序中尽量使用位运算进行操作,这会大大提高程序的性能。

位操作符

& 与运算
两个位都是 1 时,结果才为 1,否则为 0

1 0 0 1 1
& 1 1 0 0 1


1 0 0 0 1

| 或运算
两个位都是 0 时,结果才为 0,否则为 1

1 0 0 1 1
| 1 1 0 0 1


1 1 0 1 1

^ 异或运算
两个位相同则为 0,不同则为 1

1 0 0 1 1
| 1 1 0 0 1


0 1 0 1 0

~ 取反运算
0 则变为 1,1 则变为 0

1 0 0 1 1
~


0 1 1 0 0

​<< 左移运算
向左进行移位操作,高位丢弃,低位补 0

int a = 8; a<<3;
移位前:0000 0000 0000 0000 0000 0000 0000 1000
移位后:0000 0000 0000 0000 0000 0000 0100 0000

“>>” 右移运算
向右进行移位操作,对无符号数,高位补 0,对于有符号数,高位补符号位

unsigned int a = 8; a >> 3;
移位前:0000 0000 0000 0000 0000 0000 0000 1000
移位后:0000 0000 0000 0000 0000 0000 0000 0001 ​
int a = -8; a >> 3;
移位前:1111 1111 1111 1111 1111 1111 1111 1000
移位前:1111 1111 1111 1111 1111 1111 1111 1111

原码
最高位为符号位,0代表正数,1代表负数,非符号位为该数字绝对值的二进制表示。

如:

127的原码为0111 1111
-127的原码为1111 1111

反码
正数的反码与原码一致;

负数的反码是对原码按位取反,只是最高位(符号位)不变。

如:

127的反码为0111 1111
-127的反码为1000 0000

补码
正数的补码与原码一致;

负数的补码是该数的反码加1。

如:

127的补码为0111 1111
-127的补码为1000 0001

补码的好处
如果计算机内部采用原码来表示数,那么在进行加法和减法运算的时候,需要转化为两个绝对值的加法和减法运算;

计算机既要实现加法器,又要实现减法器,代价有点大,那么可不可以只用一种类型的运算器来实现加和减的远算呢?

很容易想到的就是化减为加,举一个生活中的例子来说明这个问题:

时钟一圈是360度,当然也存在365度,但其实它和5度是一样的;

相同的道理,-30度表示逆时针旋转30度,其与顺时针旋转330度是一样的;

这里数字360表示时钟的一圈,在计算机里类似的概念叫模,它可以实现化减为加,本质上是将溢出的部分舍去而不改变结果。

易得,单字节(8位)运算的模为256=2^8。

在没有符号位的情况下,127+2=129,即:
在这里插入图片描述

这时,我们将最高位作为符号位,计算机数字均以补码来表示,则1000 0001的原码为减1后按位取反得1111 1111,也就是-127。

也就是说,计算机里的129即表示-127,相当于模256为一圈,顺时针的129则和逆时针127即-127是一样的。

故可以得到以下结论:

负数的补码为模减去该数的绝对值。

如-5的补码为:

-5=256-5=251=1111 1011(二进制)

同样的,临界值-128也可以表示出来:

-128=256-128=128=1000 0000(二进制)

但是正128就会溢出了,故单字节(8位)表示的数字范围为-128–127。

最后,我们来看一下,补码是如何通过模的溢出舍弃操作来完成化减为加的!

16-5=16+(-5)=11

1 0000 1011将溢出位舍去,得0000 1011(二进制)=11。
在这里插入图片描述

经典算法

1. 实现几何运算

交换两个数的值。

public int Add(int a,int b){
	
	a = a^b;
	b = a^b;
	a = a^b;
}

public int Add(int num1,int num2){
	//迭代进位与无进位的和相加
	int temp;
	while(num2!=0){
		//两个数字相与并左移一位相当于加法的进位
		temp = (num1&num2)<<1;
		//两个数字相异或相当于加法无进位
		num1^=num2;
		num2 = temp;
	}
	return num1;
}

public int Min(int num1,int num2){
	int temp=0;
	//而负数在计算机中的二进制是用补码表示的,一个负数就是它的绝对值的反码加一。
	temp=Add(~num2,1);
	return Add(num1,temp);
}


左移乘2

int multiply(int a, int b) {  
    //将乘数和被乘数都取绝对值 
    int multiplicand = a < 0 ? add(~a, 1) : a;   
    int multiplier = b < 0 ? add(~b , 1) : b;  
     
    //计算绝对值的乘积  
    int product = 0;  
    while(multiplier > 0) {    
        if((multiplier & 0x1) > 0) {// 每次考察乘数的最后一位    
            product = add(product, multiplicand);    
        }     
        multiplicand = multiplicand << 1;// 每运算一次,被乘数要左移一位    
        multiplier = multiplier >> 1;// 每运算一次,乘数要右移一位(可对照上图理解)  
    }   
    //计算乘积的符号  
    if((a ^ b) < 0) {    
        product = add(~product, 1);  
    }   
    return product;
}


右移除2

int divide_v2(int a,int b) {   
    // 先取被除数和除数的绝对值    
    int dividend = a > 0 ? a : add(~a, 1);    
    int divisor = b > 0 ? a : add(~b, 1);    
    int quotient = 0;// 商    
    int remainder = 0;// 余数    
    for(int i = 31; i >= 0; i--) {
        //比较dividend是否大于divisor的(1<<i)次方,不要将dividend与(divisor<<i)比较,而是用(dividend>>i)与divisor比较,
        //效果一样,但是可以避免因(divisor<<i)操作可能导致的溢出,如果溢出则会可能dividend本身小于divisor,但是溢出导致dividend大于divisor       
        if((dividend >> i) >= divisor) {            
            quotient = add(quotient, 1 << i);            
            dividend = substract(dividend, divisor << i);        
        }    
    }    
    // 确定商的符号    
    if((a ^ b) < 0){
        // 如果除数和被除数异号,则商为负数        
        quotient = add(~quotient, 1);    
    }    
    // 确定余数符号    
    remainder = b > 0 ? dividend : add(~dividend, 1);    
    return quotient;// 返回商
}

幂次方(快速幂算法)

/**
 * 1.全面考察指数的正负、底数是否为零等情况。
 * 2.写出指数的二进制表达,例如13表达为二进制1101。
 * 3.举例:10^1101 = 10^0001*10^0100*10^1000。
 * 4.通过&1和>>1来逐位读取1101,为1时将该位代表的乘数累乘到最终结果。
 */
public double Power(double base, int n) {
    double res = 1,curr = base;
    int exponent;
    if(n>0){
        exponent = n;
    }else if(n<0){
        if(base==0)
            throw new RuntimeException("分母不能为0"); 
        exponent = -n;
    }else{// n==0
        return 1;// 0的0次方
    }
    while(exponent!=0){
        if((exponent&1)==1)
            res*=curr;
        curr*=curr;// 翻倍
        exponent>>=1;// 右移一位
    }
    return n>=0?res:(1/res);       
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值