位运算

第一章

00课程

推荐参考书:

image-20210306204311399

image-20210306204401462

01位运算的奇艺技巧

1.位运算与进制基础

  • 判断奇偶数

    x&1 = 1 //奇数
    
    x&1 = 0 // 偶数
    

    对于任何一个数 & 1,因为1的二进制是1,在它的补码中,除了最后一位为1,其它全部为0,前面提到,按位与运算&只有当两个数都为1时结果才是1,因此,任何一个数与1相与,因此最终的结果只有0和1两种情况,而且结果取决于另外那个数二进制中的最后一位(若为1,最终结果便为1,若为0,结果便是0)

    image-20210306213017928

  • 获取二进制位是0还是1(两种解决 方案)

     // 先将1左移到指定的第i位,然后在用与&判断是否是1。最后右移第i位回去。
            return (1 <<  & num) >> (i-1);
    
  • 交换两个整数变量的 值

    用三次异或运算

    1,首先需要明确的是异或运算满足交换律和结合律,即有如下公式

    a^b=b^a;
    b^a^b=a^b^b;
    

    2,其次,异或运算还满足下面的公式

    a^a=0;
    a^0=a;
    

    3,使用异或运算实现两个变量交换变量值的代码如下

    a=a^b;
    b=a^b;
    a=a^b;
    

    4,对第三步的代码进行分析如下

    a=a^b;
    b=a^b=a^b^b=a^(b^b)=a^0=a;
    a=a^b=a^b^a=b^(a^a)=b^0=b;
    

    从而实现了使用异或运算将两个变量值互换。

    5,在日常编程情况下,不建议使用这种方法,首先是速度不一定能够得到提高。

    6,对这种方法的另一种理解:

    1 a^b=diff;//diff为a,b两个变量的差值
    2 a^diff=b;//a减去差值为b
    3 b^diff=a;//b减去差值为a
    
  • 不用判断语句,求整数的绝对值

    public static int abs(int num) {
    return num * (1 - ((num >>> 31)<<1));
    }
    

    num>>>31 得到的是num的符号位。num为负则num>>>31=1;否则num>>>31=0;

    (num>>>31) < <1 相当于将 num>>>31 乘以2.

    1- ((num>>>31) < <1) 的作用是将 ((num>>>31) < <1)由0变为1,或者由1变为-1;

    经过1- ((num>>>31) < <1) 的变换后,如果Num为负,则表达式的结果为-1;否则为1。

    num* (1- ((num>>>31) < <1))的意思也就明确了……

异或,可以理解为不进位加法:1+1 =0 ,0 + 0 = 0,1 + 0 =1

性质:

  1. 交换律 可以任意交换因子的位置结果不变。

  2. 结合律:即(a^b)^c = a^(b^c)

  3. 对于任何数x,都有x^x = 0,x^0 =x,同自己求异或是0(自己和自己一定相同),同0求异或是自己(异或只有不同才出1,不同的时候肯定是因为原数中有1 ,异或的结果中才有1)

  4. 自反性 A^B^B = A^0 =A,连续和一个因子做异或运算,最终结果为自己

位运算符

在处理整形数值时,可以直接将组成整形数值的各个位进行操作。这意味着可以用屏蔽技术获得整数中的各个位。

&(与) |(或) ^(异或) ~(非、取反)

>>和>>>运算符将二进制位进行右移或者左移操作。

>>>运算符将0填充高位,>>运算符用符号位填充高位,没有<<<运算符

对于int型,1<<35和1<3的结果是相同的,而左边的操作数是long型时候需要对右侧的操作数做模64

因为int型只有32位,35会对32取模,所以35和3的结果相同

与:都为1结果为1,或:有一个为1结果为1,异或:二者不同时为1

image-20210306212204774

题解

找出唯一成对的数
image-20210306213902567

不重复的数给他消去

利用连续异或消除重复

思路

利用两组【1~1000】异或,正好两组之间相同的异或消去最后剩下三个,俩个异或消去,最终剩余

image-20210306214229281
import java.util.Random;

import com.sun.corba.se.impl.javax.rmi.CORBA.Util;

public class 唯一成对的数 {

	public static void main(String[] args) {
		int N = 11;
		int arr[] = new int[N];
		int index = new Random().nextInt();
		for (int i = 0; i < arr.length - 1; i++) {
			arr[i] = i + 1;
		}
//	最后一个数是随机数 产生是从0到10
		arr[arr.length - 1] = new Random().nextInt(N);
		// 随机下标

		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i]);
		}
		int x1 = 0;
        //将其补为两个相同的
		for (int i = 1; i <= N - 1; i++) {

			x1 = (x1 ^ i);
		}
        
		for (int i = 0; i < N; i++) {
			x1 = x1 ^ arr[i];
		}
		System.out.println(x1);
		System.out.println("============");


	}

}
//开一个空间存出现次数

另外开辟辅助空间

		int [] helper = new int[N];
		for (int i = 0; i < N; i++) {
			helper[arr[i]]++;
//			arr[i]中存的是一到一千这些数,我们把这些数变成下标
//			下标放到helper中去,对计数进行helper+
		}
		for (int i = 0; i < N; i++) {
			if(helper[i] == 2) {
				System.out.println(i);
			}
		}
找出落单的数
image-20210306222927968

思路:

连续的进行异或,成对出现的就会消除,单个的就会保留。保留的即为答案。

二进制中1的个数

image-20210306223158765

思路一:

image-20210311202339149
if((x&(1<<i))==(1<<i)) {
				count++;
			}

代码:

import java.util.Scanner;

public class 一的个数 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		int x = sc.nextInt();
		//将int转换为二进制
		System.out.println(Integer.toString(x, 2));
		int count = 0;
		for(int i = 0;i<32;i++) {
			if((x&(1<<i))==(1<<i)) {
				count++;
			}
		}
		System.out.println(count);
	}

}

Tips:

Integer.toString(int par1,int par2),par1表示要转成字符串的数字,par2表示要转成的进制表示,如:

Integer.toString(22,2),表示把22转成2进制表示的字符串,

思路2:

不带符号将原来的数字往右边挪动,挪动高位就补0

image-20210311202821194

代码:

int count2 = 0;
		for (int i = 0; i < 32; i++) {
			if(((x>>>i)&1)==1) {
				count2++;
			}
		}
System.out.println(count2);

思路3:

想办法干掉1,1被干掉了几次就说明有几个1。

image-20210311203758730 image-20210311203815325 image-20210311204332402
int count3 = 0;
		//因为此时不知道具体次数,所以使用while循环
		while(x!=0) {
			x = ((x-1)&x);
			count3++;
		}
		System.out.println(count3);
是不是2的整数次方

要求:只要一条语句

思路:

一个数是2的整数次方,说明这个数的二进制数的表示中只有一个1。

并且要考虑1。无需考虑负数,因为2的负数次方都是浮点数,比如2^(1/2)是浮点数。

image-20210311205331613

image-20210311205014082

代码:

import java.util.Scanner;

public class 是不是2的整数次方 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		int x = sc.nextInt();
		System.out.println(Integer.toString(x,2));
//		(x-1)&x)==0起到的作用就是消掉最低位的1
		if(((x-1)&x)==0) {
			System.out.println(x+"是2的整数次方 ");
		}else {
			System.out.println(x+"不是2的整数次方 ");
		}
		
	}

}
将整数的奇偶位互换

思路:

image-20210311212156877 image-20210311212225728 image-20210311212245581

代码:


public class 交换奇偶位 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int a = 6;
		int b =m(a);
		System.out.println(Integer.toString(a,2));
		System.out.println(Integer.toString(b,2));
		System.out.println(b);
	}
	public static int m(int x) {
		int ou = x&0xaaaaaaaa;//和1010 1010 。。。。做与运算取出偶数位
		int ji = x&0x55555555;//和0101 0101.。。。。做与运算取出奇数位
		return (ou>>1)^(ji<<1);
	}

}

0~1之间的浮点数和二进制之间的实数
image-20210311214708471

整数部分的十进制转二进制是除以2。取余留商。

小数部分:乘以2,然后取出整数部分,将剩下的小数部分继续乘以2,然后再取整数部分,一直取到小数部分为零为止。就是乘以二然后判断小数点前面,扣掉这个整数部分再次乘二。


public class 二进制小数 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		double num = 0.5;
		StringBuffer sb = new StringBuffer("0.");
		
		while(num>0) {
//			乘2:挪整
			double r = num*2;
//			判断整数部分
			if(r>=1) {
//				消掉整数部分
				num = r-1;
				sb.append("1");
			}else {
				sb.append("0");
				num = r;
			}
			if(sb.length()>34) {
				System.out.println("Error");
				return;
			}
		}
		System.out.println(sb.toString());
	}

}

出现k次与出现1次

题干:

image-20210311222612234

代码:

第一种解法:一个键值对数组储存,存储出现的数字以及其对应出现的次数。


public class 出现k次与出现1次 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int arr[] = {3,3,3,4,4,4,5,5,5,6};
		//暴力破解
		int count[] = new int [arr.length];
		for (int i = 0; i < arr.length; i++) {
			++count[arr[i]];
		}
		for (int i = 0; i < count.length; i++) {
			if(count[arr[i]]==1) {
				System.out.println(arr[i]);
			}
		}
		
	}

}

第二种解法:做不进位加法

首先要知道一个结论,2个相同的二进制数,做不进位加法,结果为0;

10个相同的十进制数,做不进位加法,结果为0;

k个相同的k进制数,做不进位加法,结果为0;

思路就是:全部转为k进制的数,相加,最后剩下的就是只出现1次的数。

十进制转k进制,java的api提供的有现成的方法,即Integer.toString(i,radix); i 为十进制的数,radix为进制。或着自己手动取余也是一种方法,只不过比较麻烦。


import java.util.*;

public class 出现k次和出现1次 {
	public static void main(String[] args) {
		Scanner cin = new Scanner(System.in);
		int[] arr = { 3, 3, 3, 5, 7, 7, 7, 8, 8, 8, 6, 6, 6 };
		int len = arr.length;
		char[][] kRadix = new char[len][];
		int k = 3;
		int maxlen = 0;
//转换成k进制字符数组
//对于每个数字
		for (int i = 0; i < len; i++) {
			kRadix[i] = new StringBuilder(Integer.toString(arr[i], k)).reverse().toString().toCharArray();
			if (maxlen < kRadix[i].length)
				maxlen = kRadix[i].length;
		}
		
		int[] resArr = new int[maxlen];
		for (int i = 0; i < len; i++) {
//不进位加法
			for (int j = 0; j < maxlen; j++) {
				if (j > kRadix[i].length)
					resArr[j] += 0;
				else
					resArr[j] += (kRadix[i][j] - '0');
			}
		}
		int res = 0;
		for (int i = 0; i < maxlen; i++)
			res += (resArr[i] % k) * (int) (Math.pow(k, i));
		System.out.println(res);
	}
}

思路:

所有数先转成k进制,然后做k进制的不进位加法,做完之后剩下就是我们答案的k进制,然后将其还原想要的结果

image-20210315200435303

1.任意进制互转

手工取余法

Integer.toString(i,radix);
位运算实现加减乘除

思路1:

首先观察十进制加法的规律

image-20210320094028887

类比得到二进制加法

image-20210320094059627

如果想得出每一位上的值,需要将这一位和1进行与运算

image-20210320094149437

得到这一位是0还是1

	long flag = 0x1;
	long bita = a & flag;
	long bitb = b & flag;

不断移动flag 的位置:

flag <<= 1;
//			等价于flag=flag<<1,flag<<1表示b左移1位(二进制),等价于flag乘以2的1次方,				flag<<=1的含义就是,flag等于flag乘以2的1次方

设置每一位上的求和得到的值

long sum = 0;
//如果这两位都是1
如果之前没有进位sum = 0,carry = true。
之前有进位,那么sum此时为1+1(本次的两个1)+1(之前两位的进位)
sum|=flag;sum为1,carry也为1
//如果这两位其中一个为1
如果之前有进位,那么sum = 0(本次的一个1和之前进位的一个1),产生一个进位carry。
如果之前没有进位那么sum = 1,不产生进位。
//如果当前都是0
如果之前有进位那么sum = 1,把进位给取消掉
    

image-20210320102908277

代码(有错):


private static long add(int a, int b) {
		// TODO Auto-generated method stub
		if (a == b) {// 特殊情况
			return a << 1;
		}
		// 用一个标志表示当前在多少位
		long flag = 0x1;
		// 当前位的值
		boolean carry = false;
		long sum = 0;
//		重复做,所有的位都比较一下
		while (flag <= a || flag <= b) {// 比较flag是否变为1111...11当flag位数不够的时候肯定会比a和b小
			long bita = a & flag;
			long bitb = b & flag;
			if ((bita & bitb) == 1) {// 如果这两位上都是1那么一定会产生进位
//			如果之前有进位,1+1+1三个一相加
				if (carry == true) {
					sum |= flag;
				}
				carry = true;

			} else if ((bita | bitb) == 1) {
				if (carry) {// 如果之前有进位
					carry = true;
					sum |= flag;
				} else {
					sum |= flag;// 0(sum)和1(flag)相或赋给sum
				}
			} else {// 当前都是为0
//			如果有进位
				if (carry) {
					// 进位给它加进去
					sum |= flag;
//				然后取消掉
					carry = false;
				}
			}
			flag <<= 1;
//			等价于flag=flag<<1,flag<<1表示b左移1位(二进制),等价于flag乘以2的1次方,flag<<=1的含义就是,flag等于flag乘以2的1次方
		}
		// 如果到最后有一个进位的话,再把最后的标志位给设置上
		if (carry) {
			sum |= flag;
		}
		return sum;
	}



思路2:

使用位运算实现加法时,基本思路与计算机组成原理中的加法器类似,使用位运算a ^ b来实现没有进位的本位和用a & b来获得需要进位的位置,则(a & b << 1)则表示进位加1后的数,再用或运算求本位和,直至(a & b) == 0即没有进位为止。

image-20210320111235137

递归版本:

private static int add(int a, int b) {
		if(b==0||a==0) {//无进位的时候完成运算
			if(a==0) {
			return b;
			}else {
			return a;
			}
		}
		int sum = 0,carry = 0;
		sum = a^b;//完成第一步没有进位的加法运算
		carry = (a&b)<<1;//完成第二步的进位运算并左移运算
		System.out.println("sum = "+sum+"  carry = "+carry);
		return add(sum,carry);//进行递归,相加				
	}

先不计进位相加,然后再与进位相加,随着递归,进位会变为0,递归结束。

public static int Add(int a,int b)
	{
		return b!=0 ? Add(a^b,(a&b)<<1) : a;
		/*if(b)
			return Add(a^b,(a&b)<<1);
		else
			return a;*/
	}

非递归版本:

int Add(int a, int b)
{
	int ans;
	while(b)
	{   //直到没有进位
		ans = a^b;        //不带进位加法
		b = ((a&b)<<1);   //进位
		a = ans;
	}
	return a;
} 

乘法

image-20210320113534397 image-20210320113553698
//正数乘法运算
	public static int pos_Multiply(int a,int b)
	{
		int ans = 0;
		while(b!=0)
		{
			if((b&1)==1)
				ans = Add2(ans, a);
			a = (a<<1);
			b = (b>>1);
		}
		return ans;
	}	

除法

除法就是由乘法的过程逆推,依次减掉(如果x够减的)y(231),y(230),...y8,y4,y2,y1。减掉相应数量的y就在结果加上相应的数量。

public static int pos_Div(int x,int y)
	{
		int ans=0;
		for(int i=31;i>=0;i--)
		{
			//比较x是否大于y的(1<<i)次方,避免将x与(y<<i)比较,因为不确定y的(1<<i)次方是否溢出
			if((x>>i)>=y)
			{
				ans+=(1<<i);
				x-=(y<<i);
			}
		}
		return ans;
	}
posted @ 2021-03-28 16:00  记录学习Blog  阅读(253)  评论(0编辑  收藏  举报