算法训练第一章:位运算的妙用

第一节   如何找数组中唯一成对的那个数?

知识点1 位运算符

在处理整形数值时,可以直接对组成整形数值的各个位进行操作。因此,可以利用“屏蔽技术”获得整数中的各个位

Java中的位运算符主要有:&(与),|(或),^(异或),~(非),这里需要注意,位运算与平常用的比较多的逻辑运算(如:逻辑与&&,逻辑或||)的区别。个人认为,可以这样进行记忆:计算机处理数据常以字节(8位)为单位,位运算相当于对每个字节中单个位进行操作,而逻辑与与逻辑或,实际上可以理解为对一整组数据进行操作的结果。

此外,常用的位运算符还有>>,<<,>>>。这里需要注意,>>运算符用符号位填充高位,而>>>用0填充高位。

注意,对于int型,1<<35与1<<3的结果是相同的。因为int型变量只有32位进行存储,超过32位的移动将会按照取模运算进行截取。同理,如果是long型,模数则为64。

那么说了这么多?位运算究竟有什么妙用呢?下面让 笔者带大家一一揭晓。

知识点2 位运算符的一些基础“妙用”

1.判断奇偶数

如果x & 1=1,则x为奇数。那么相反,如果x & 1=0,则x为偶数。这是因为所有的数字都有这么样的一个规律:如果它是奇数,那么转换为二进制数字,它的最低位必然为1;如果它是偶数,那么它的最低位必然为0。这里不再赘述原理。

2.获取二进制位是1还是0

可以这么说,1.判断奇偶数就是2的特殊情况,1位于最低位。如果想要判断一个二进制数的某一位到底是0还是1,只需要将数字1通过左移或者右移,到需要比较的位置。因为通过此番操作,这个工具‘1’的其它位均为0,而数字0具有“屏蔽效果”。何为“屏蔽效果”?即,0与任何数做与运算,结果均为0,相当于将其它位置上的数字进行了屏蔽。通过“屏蔽效果”,我们需要比较的位置得到了保留,再反向通过位移运算,到达最低位,通过判断结果是0还是1即可知道需要判断的位置是0还是1了。

3.交换两个证书变量的值

在vs2013环境中交换两个整形变量的值

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int a = 10, b = 20, temp = 0;
	printf("a=%d,b=%d\n", a, b);
	temp = a;
	a = b;
	b = temp;
	printf("a=%d,b=%d", a, b);
	system("pause");         //停止程序,否则在vs2013及以上版本中无法看到结果
	return 0;
}

上面是最基本的交换两个整形变量的值的方法,添加一个中间变量,来进行交换。

而若不添加中间变量来进行两个整形变量的交换,可以使用两个变量间的关系来进行加减,得到交换的目的。
如上面代码中 a = 10 , b = 20 则可以使用 a = a+b , b = a-b ,a = a-b ,这样也可以达到效果。

但这种方法的缺陷还是很大的,如果数字过于大,则会超出 int 型变量 的范围,所以我们使用 异或运算符“^” ,来达到交换的目的。

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int a = 10, b = 20;
	    printf("a = %d,b=%d\n",a, b);
	    a = a^b;                                      // ^为二进制异或运算符"相同为0,相异为1"
		b = a^b;                                      // 10的二进制为  1010 -a
		a = a^b;                                      // 20的二进制为 10100 -b
		printf("a = %d,b=%d\n", a, b);                // a = a^b   为 11110    a,b中谁与这个值异或则出现另一个数
		system("pause");                              // b = a^b   为 01010   -10
 		return 0;                                     // a = a^b   为 10100   -20

}

其实现原理是进行异或操作的数学性质。如下

1.任一变量X与其自身进行异或结果为0,即 X^X=0
2.任一变量X与0进行异或结果不变,即 X^0=X
3.异或运算具有可结合性,即 a^b^c = (a^b)^c = a^(b^c)
4.异或运算具有可交换性,即 a^b = b^a。

在异或性质的基础之前进行操作,实现交换的执行过程为:

 a = a ^ b   ==>   a = a ^ b;//获得异或值,赋值给a
 b = a ^ b   ==>   (a ^ b) ^ b = a ^ (b ^ b) = a ^ 0 = a,//将上一步异或后的a带入,获得b
 a=  a ^ b   ==>   (a ^ b) ^ b =  (a ^ b) ^ a = a ^ b ^ a = (a ^ a) ^ b = 0 ^ b = b, 该值赋值给a,即 a = b

最后这里再强调一下异或运算,异或可以理解为不仅为加法,1+1=0,0+0=0,1+0=1

言归正传,通过以上知识点的铺垫,那么如何通过位运算,找出数组中那成对的数呢?

下面给出一道例题:

1-1000这1000个数放在含有1001个元素的数组中,只有唯一的一个元素重复,其它均只出现一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一个算法实现?

解决此题的方法有很多,下面讲解用异或运算的性质来解决此题。由以上的铺垫我们可以知道,A&A=0,B&0=B。我们可以将1-1000这些数字先进行异或,然后通过循环操作,遍历这个1001个数组中的每个元素,与其进行异或运算,这样,最后留下来的结果就是重复的数字。

package com.company;

import java.util.Random;

public class Main {
    public static void main(String[] args) {
        //构造符合题目条件的数组
        int N=1001;
        int[] arr=new int[N];
        for(int i=0;i<arr.length-1;i++){//将前面不重复的数字对应初始化
            arr[i]=i+1;
        }
        arr[arr.length-1]=new Random().nextInt(N-1)+1;//将随机生成的数字暂时存放在数组最后的一个位置
        int index=new Random().nextInt(N);//再找一个随机下标
        //将最后一个数字与随机下标进行调换,通过异或运算的方法
        arr[index]=arr[index]^arr[arr.length-1];
        arr[arr.length-1]=arr[index]^arr[arr.length-1];
        arr[index]=arr[index]^arr[arr.length-1];
        //将数组输出,查看重复的数字
        for(int i=0;i<arr.length;i++){
            System.out.print(arr[i]+" ");
        }
        System.out.println("--------------------------");
        int x1=0;
        //将1-N-1异或起来
        for (int i=1;i<N;i++) {
            x1=(x1^i);
        }
        for (int i=0;i<N;i++){
            x1=x1^arr[i];//将x1与数组中全部元素依次进行异或,最后得到的结果x1就是我们想要的重复的值
        }
        System.out.println("重复的数字为"+x1);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值