【算法&数据结构初阶篇】:位运算

算法中很多情况下需要用到各种位运算的转换,比如>>右移、<<左移、&与等等,下面我们利用这些位运算来进行一个进制转换,将一个int整形(32位)十进制转二进制,以及其他的一些转换技巧。

目录

一、十进制转换成32位的二进制

核心点就是将1进行左移操作,从高位32位开始依次向低位0位移动,分别与所需转换的整形数值做与运算 

一、代码:

 二、代码解析:

二、位运算技巧

一、随用随记的特殊技巧

 二、计算演练

一、负数二进制,计算转换得到其十进制:

 三、用位运算实现加减乘除,不能用到+ - * /符号

一、代码演示


一、十进制转换成32位的二进制

核心点就是将1进行左移操作,从高位32位开始依次向低位0位移动,分别与所需转换的整形数值做与运算 

一、代码:

public void printBinary(int num){
    //高位从右到左遍历,打印
    for(int i = 31; i >= 0 ; i--){
        //数值第一轮是与高位1做与运算, 两者为1 与 得1,一个不为1 与 得 0 
        System.out.print(num & (1 << i) == 0 ? 0 : 1)
    }
}

 二、代码解析:

  • 转换二进制,需要知道打印方向,从高位到低位,即从左往右依次打印
  • &与运算的技巧,  1  1 得 1,非1 得 0,所以原数值,若为1 , 与运算后仍为 1, 若为 0, 与运算后仍为0

二、位运算技巧

一、随用随记的特殊技巧

System.out.println((~-5)+1);  //5   -5的相反数为5  取反+1 
System.out.println((~5)+1);   //-5   5的相反数为-5  取反+1 
System.out.println(-1>>>1);   //2147483647  无符号右移,忽略最高位的符号位,即用0补齐最高位
System.out.println(-1>>1);    //-1         带符号右移,负数最高位符号位是 1, 右移后仍用1补齐
printBinary(-1);                    //打印-1二进制  11111111111111111111111111111111

System.out.println(70 & 63);   // 6  即num % 64 等价与 num & 63 因为64二进制是1000000 模64后余数就是后六位的二进制数, 取后6位的技巧,与运算 111111  即 63  与运算1 1得1 这样就得到num的余数了 所以等价于模 
System.out.println(70 % 64);   // 6
System.out.println( 2 ^ 2 ) ; // 0  自己异或自己为0,异或运算,相同为0 不同为1,相当于 无进位相加

 二、计算演练

一、负数二进制,计算转换得到其十进制:

例如计算机中二进制:11111111111111111111111111111011 ,表示的十进制为 -5 ,计算机内存中保存的都是补码,这个要清楚。 

首先符号位即最高位为1,表示为负数,那是负几,就将存数位(即去符号位后的31位)取反,再加1,得到的就是负数的相反数,前面有提到,~(-5)+1=5,得到了相反数后,再加上符号-,即位该二进制对应的十进制:

                                  (符号位)1     1 1 1... 1 1 0 1 1 

操作1:存数位取反                1     0  0  0... 0 0 1 0 0

操作2:存数位+1                    1     0  0  0... 0 0 1 0 1

这里得到的二进制,转换十进制  2^2+2^0 = 5,加入符号位,得到   -5

 三、用位运算实现加减乘除,不能用到+ - * /符号

一、代码演示

package class05;

// 测试链接:https://leetcode.com/problems/divide-two-integers
public class Code03_BitAddMinusMultiDiv {

	// 加法逻辑,理解举例
	// a = 00001110
	// b = 00000111
	// a + b = a和b的无进位相加(^) + a和b的进位信息(&之后左移一位)
	// a和b的无进位相加(^),假设为c
	// a和b的进位信息(&之后左移一位),假设为d
	// c = 00001001
	// d = 00001100
	// c + d = c和d的无进位相加(^) + c和d的进位信息(&之后左移一位)
	// c和d的无进位相加(^),假设为e
	// c和d的进位信息(&之后左移一位),假设为f
	// e = 00000101
	// f = 00010000
	// e + f = e和f的无进位相加(^) + e和f的进位信息(&之后左移一位)
	// e和f的无进位相加(^),假设为g
	// e和f的进位信息(&之后左移一位),假设为h
	// g = 00010101
	// h = 00000000
	// h为0了,g就是结果
	// 而且,00001110 + 00000111你直接自己来计算,最后结果也确实为00010101
	public static int add(int a, int b) {
		int sum = a;
		// 进位信息什么时候消失,什么时候停止
		while (b != 0) {
			// sum为无进位相加
			sum = a ^ b;
			// b为进位信息
			b = (a & b) << 1;
			a = sum;
		}
		return sum;
	}

	public static int negNum(int n) {
		return add(~n, 1);
	}

	// 实现了加法,那么a-b,就是a+(-b),就是a + (~b + 1)
	public static int minus(int a, int b) {
		return add(a, negNum(b));
	}

	// 乘法逻辑:
	// a = 00001100
	// b = 00001101
	// b的第0位是1,所以最终结果包含1个00001100
	// b的第1位是0,所以最终结果又包含0个00001100
	// b的第2位是1,所以最终结果又包含4个00001100,
	// 4个00001100 = 00001100左移2位 =00110000
	// b的第3位是1,所以最终结果又包含8个00001100
	// 8个00001100 = 00001100左移3位 =01100000
	// 然后b就没有1了,最终结果 =
	// 00001100 + 00110000 + 01100000
	public static int multi(int a, int b) {
		int res = 0;
		// 如果b里还有1,就继续
		// 如果b里没有1,就停止
		while (b != 0) {
			// 当前位是1,就加上此时的a
			// 当前位是0,就不加上此时的a
			if ((b & 1) != 0) {
				res = add(res, a);
			}
			// 每次a左移1位
			// 每次b右移1位
			// 有1就加,没有就不加
			a <<= 1;
			b >>>= 1;
		}
		return res;
	}

	public static boolean isNeg(int n) {
		return n < 0;
	}

	// 除法逻辑
	// 默认a和b都是正数
	// 如果不是正数,转化成正数
	// a = 0101010   42
	// a = 0001010
	// b = 0000110   6
	// a向右移动30位,啥都没了,当然 < b,说明如果b向左移动30位,必然大于a
	// a向右移动29位,啥都没了,当然 < b,说明如果b向左移动29位,必然大于a
	// a向右移动28位,啥都没了,当然 < b,说明如果b向左移动28位,必然大于a
	// .....
	// a向右移动6位,啥都没了,当然 < b,说明如果b向左移动6位,必然大于a
	// a向右移动5位,状态是0000001,还是 < b,说明如果b向左移动5位,必然大于a
	// a向右移动4位,状态是0000010,还是 < b,说明如果b向左移动4位,必然大于a
	// a向右移动3位,状态是0000101,还是 < b,说明如果b向左移动3位,必然大于a
	// a向右移动2位,状态是0001010,首次 >= b,说明如果b向左移动2位,开始 <= a
	// 所以a / b的结果,必然包含一个00000100  4
	// 然后让,a = a - (b<<2),也就是让a扣掉已经算出来的部分  42 - 6*4 = 18  10010
	// 继续讨论,a向右移动1位 01001 、 res |= (1 << 1) =>4|2 = 6  18 - 6*1 =12 1100
	// a向右移动0位的后续情况  1100 res | 1 => 6|1 = 7   12 - 6 =6 110
	public static int div(int a, int b) {
		int x = isNeg(a) ? negNum(a) : a;
		int y = isNeg(b) ? negNum(b) : b;
		int res = 0;
		for (int i = 30; i >= 0; i = minus(i, 1)) {
			if ((x >> i) >= y) {
				res |= (1 << i);
				x = minus(x, y << i);
			}
		}
		return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
	}

	// 除法逻辑主函数
	// a / b
	// 先把a和b转化成正数来做
	// 但是Integer.MIN_VALUE是转化不了的,所以单独讨论
	public static int divide(int a, int b) {
		if (a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {
			// 如果a和b都是Integer.MIN_VALUE
			// 结果当然是1
			return 1;
		} else if (b == Integer.MIN_VALUE) {
			// 如果b是Integer.MIN_VALUE
			// 结果当然是0
			return 0;
		} else if (a == Integer.MIN_VALUE) {
			if (b == negNum(1)) {
				// 如果a是Integer.MIN_VALUE,b是-1
				// 根据题目要求,返回Integer.MAX_VALUE
				return Integer.MAX_VALUE;
			} else {
				// 如果a是Integer.MIN_VALUE,且b不是-1
				// 根据我们课上讲的,用补偿的方法
				// 例子1 :
				// 假设整数最小是-9,当前要除以3
				// -9无法转化成+9,因为整数最大只能到8
				// 所以,先让-9+1 = -8,让-8除以3,得到-2
				// 注意到,-2 * 3 = -6,而我们当初应该用-9来除
				// 所以还差着 -9 - (-6) = -3
				// 这就是应该补偿结果的,所以最终结果为
				// -2 + (-3 / 3) = -3得到正确结果
				// 例子2 :
				// 假设整数最小是-9,当前要除以4
				// -9无法转化成+9,因为整数最大只能到8
				// 所以,先让-9+1 = -8,让-8除以4,得到-2
				// 注意到,-2 * 4 = -8,而我们当初应该用-9来除
				// 所以还差着 -9 - (-8) = -1
				// 这就是应该补偿结果的,所以最终结果为
				// -2 + (-1 / 3) = -2得到正确结果
				int c = div(add(a, 1), b);
				// minus(a, multi(c, b)) -> a - b*c
				// a - b*c 就是就是应该补偿结果的,那个剩余
				// 剩余 / b -> 就是结果应该加上去的补偿
				return add(c, div(minus(a, multi(c, b)), b));
			}
		} else {
			return div(a, b);
		}
	}

}

四、异或运算

1.不使用额外变量实现交换两个元素

注意i j 不能相等,相等就等于指向数组同个索引,是同个地址值 相等异或为0 就导致i j都为0了  

//注意i j 不能相等,相等就等于指向数组同个索引,是同个地址值 相等异或为0 就导致i j都为0了	
public static void swap (int[] arr, int i, int j) {
		arr[i]  = arr[i] ^ arr[j];
		arr[j]  = arr[i] ^ arr[j];
		arr[i]  = arr[i] ^ arr[j];
	}

2.一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数

偶数个相同数,异或之后值为0,即可以全部依次异或,最后就是0与奇数个的值异或,为自身值

	// arr中,只有一种数,出现奇数次
	public static void printOddTimesNum1(int[] arr) {
		int eor = 0;
		for (int i = 0; i < arr.length; i++) {
			eor ^= arr[i];
		}
		System.out.println(eor);
	}

3.怎么把一个int类型的数,提取出最右侧的1

// eor != 0
// eor最右侧的1,提取出来
// eor :     00110010110111000
// rightOne :00000000000001000
int rightOne = eor & (-eor); // 提取出最右的1

演示:

eor :                     00110010110111000    =>      rightOne :00000000000001000

~eor:                    11001101001000111

~eor+1                 11001101001001000       取反+1 等同于 相反数  -eor

eor & (~eor+1 )     00000000000001000  ,这里就把最右侧的1取出来了,其他位都为0

取反+1 等同于 相反数  -eor,所以把eor & (~eor+1 ) 转换为eor & (-eor)

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值