位运算的操作与算法

在上一次的博客中,我们实现了使用位操作去实现四则运算。实现整数的加减乘除。这次我们将讨论位运算在算法中的一些妙用。

位运算可以进行的骚操作

在这里我将使用题目进行示例

题1:找出唯一成对的数

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

这个题目有两个要注意的点

  1. 数的范围是1-1000,这个是确定的
  2. 不能使用辅助储存空间
  3. 只有一个数字g重复

那么我们应该怎么去解决这个题目呢?在这里我们既然讲了位运算,那么肯定是使用|&^等来解决这些问题。

首先我们得知道:

A ^ A = 0 , A ^ 0 = A

那么我们可以想想,假如我们将题目中的数组与 1~1000进行异或操作那么剩下的值就是那一个重复的值。

? 简单的来个示例,假如数组是[1,2,3,4,3]

1 ^ 2 ^ 3 ^ 4 ^ 3 ^ 1 ^ 2 ^ 3 ^ 4 = 1 ^ 1 ^ 2 ^ 2 ^ 3 ^ 3 ^ 4 ^ 4 ^ 3 = 0 ^ 3 = 3

import java.util.Arrays;
import java.util.Random;
public class SameWord {
    public static void main(String[] args) {
        // 不重复的数字有1000个
        int N = 1000;
        // 数组的容量为10,其中有一个为重复的
        int[] arry = new int[N + 1];

        for (int i = 0; i < N; i++) {
            arry[i] = i + 1;
        }
        Random random = new Random();
        // 产生1~N的随机数
        int same = random.nextInt(N)+1;
        int position = random.nextInt(N);
        // 将重复的值随机调换位置
        arry[N] = arry[position];
        arry[position] = same;
        // 前面一部分就是为了产生1001大小的数组,其中有一个是重复的

        // 进行异或操作 【1^2^3^4……】
        int x = 0;
        for (int i = 0; i < N; i++) {
            x = (x ^ (i+1));
        }

        // 对数组进行异或操作
        int y = 0;
        for (int i = 0; i < N + 1; i++) {
            y = (arry[i] ^ y);
        }
        // 打印重复的值
        System.out.println(x^y);
    }
}

题2:找出单个值

一个数组里除了某一个数字之外,其他的数字都出现了两次。请写程序找出这个只出现一次的数字。

emm,假如弄懂了上面一个题目,这个题目就轻而易举了

public void getSingle(){
    int[] a = {1,2,3,2,1,3,4};

    int single = 0;
    for (int i : a) {
        single = single^i;
    }
    System.out.println(single);
}

题三:找出1的个数

请实现一个函数,输入一个整数,输出该数二进制表示中1的个数

例:9的二进制表示为1001,有2位是1

这个题目挺简单的。有2个方向可以去解决

  1. 通过移位获得1的个数

1001 & 1 = 1 , 1001 >> 1 = 100,100 & 1 = 0

public void getNum(){
    int n = 255;
    int count = 0;
    while(n!=0){
        if((n & 1) == 1){
            count ++;
        }
        n = n>>1;
    }
    System.out.println("个数是:"+count);
}

? 这种解法其实有一定问题的,因为如果去移动负数的话就会凉凉,陷入死循环(负数右移,最左边的那个1会一直存在)。那么我们怎么解决这个方法呢?既然我们不能移动n,那么我们可以移动相与的那个数啊

1001 & 1 = 1, 1<<1 = 10,1001&10 = 0

public void getNum2(){
    int n = 222;
    int flag = 1;
    int count = 0;

    while(flag >=1){
        // 这个地方不是n&flag == 1了
        if((n&flag) > 0){
            count ++;
        }
        flag = flag << 1;
    }
    System.out.println("个数是:"+count);
}

我们可以去考虑下这个的时间复杂度。实际上,无论你要求解的数值有多小,它都要循环32次(因为int为4个字节,需要循环32次)。

  1. 最高效的解法

    这边有个规律:n&(n-1)能够将n的最右边的1去掉。

    那么根据这个规律,如果我们将右边的1去掉,去掉的次数也就是二进制中1的个数

    public void getNum3(){
        int n = 233;
        int count = 0;
        while(n>0){
            count ++;
            n = (n -1)&n;
        }
        System.out.println("个数是:"+count);
    }

题四:保证不溢出地取整数平均值

求平均值我们一般是使用相加来进行操作的,但是如果值比较大呢,造成溢出怎么办?实际上我们知道溢出就是因为进位造成的,那么我们就可以使用位来解决这个方法。

10 二进制 1010
14 二进制 1110
公共部分: 1010
不同部分的和: 0100
不同部分除以2:0010
平均数 = 1010(相同部分) + 0010(不同部分的平均数) = 1100
因此二者平均数为12

以上的操作我们可以用位运算来替代:

公共部分 = a & b
不同部分的平均值 = (a ^ b) >> 1
平均值 = 公共部分 + 不同部分的平均值 = (a & b) + ((a ^ b) >> 1)

public void aver(){
    int a = 10;
    int b = 220;
    int averNum = (a&b) + ((a^b)>>1);
    System.out.println("平均值是:"+averNum);
}

题五:高低位交换

给出一个16位的无符号整数。称这个二进制数的前8位为“高位”,后8位为“低位”。现在写一程序将它的高低位交换。例如,数34520用二进制表示为:
10000110 11011000
将它的高低位进行交换,我们得到了一个新的二进制数:
11011000 10000110
它即是十进制的55430

A | 0 = A

在这个题目(以34520为例)中我们可以先将 10000110 11011000 >> 8右移动8位得到A = 00000000 1000011010000110 11011000 << 8得到B = 11011000 00000000,然后A | B = 11011000 10000110

原文地址:https://www.cnblogs.com/xiaohuiduan/p/11117528.html

时间: 2024-09-28 06:46:35

位运算的操作与算法的相关文章

位运算常用操作总结位运算应用口诀清零取反要用与,某位置一可用或若要取反和交换,轻轻松松用异或移位运

来源:http://www.educity.cn/wenda/381487.html 位运算常用操作总结位运算应用口诀 清零取反要用与,某位置一可用或 若要取反和交换,轻轻松松用异或 移位运算 要点 1 它们都是双目运算符,两个运算分量都是整形,结果也是整形.     2 " $amp;     3 "$amp;>amp;>quot;$右移:右边的位被挤掉.对于左边移出的空位,如果是正数则空位补0,若为负数,可能补0或补1,这取决于所用的计算机系统.     4 "

通过位运算来解决一些算法题

在刷pat的1073 多选题常见计分法题目时,发现如果需要判断每一个学生对应每道题的多选题是否错选,漏选,以及选对是比较麻烦的一件事,因为这涉及到两个集合的判断,判断一个集合是否是另一个集合的子集(即漏选,得一半的分),或者说两个集合是否完全相等(即题目得满分). 刚开始通过set容器来保存每一道题的正确答案,以及学生选择的答案,然后比较两个集合的大小,大小一致则for循环判断每一个元素是否都存在.结果发现这种思路过于复杂,且易超时. 联想到每一个选项是否一致,可以通过异或运算判断两个集合,如果

基于DP+位运算的RMQ算法

来源:http://blog.csdn.net/y990041769/article/details/38405063 RMQ算法,是一个快速求区间最值的离线算法,预处理时间复杂度O(n*log(n)),查询O(1),所以是一个很快速的算法,当然这个问题用线段树同样能够解决. 问题:给出n个数ai,让你快速查询某个区间的的最值. 算法分类:DP+位运算 算法分析:这个算法就是基于DP和位运算符,我们用dp[i ][j]表示从第 i 位开始,到第 i + 2^j -1 位的最大值或者最小值. 那么

Java基础-一文搞懂位运算

在日常的Java开发中,位运算使用的不多,使用的更多的是算数运算(+.-.*./.%).关系运算(<.>.<=.>=.==.!=)和逻辑运算(&&.||.!),所以相对来说对位运算不是那么熟悉,本文将以Java的位运算来详细介绍下位运算及其应用. 1. 位运算起源 位运算起源于C语言的低级操作,Java的设计初衷是嵌入到电视机顶盒内,所以这种低级操作方式被保留下来.所谓的低级操作,是因为位运算的操作对象是二进制位,但是这种低级操作对计算机而言是非常简单直接,友好高效

嵌入式C语言之位运算 &amp;..|.~.&gt;&gt;

在嵌入式编程中,掌握位运算在操作寄存器的时候很方便,由于之前在上位运算的时候没上,但是由于位运算的难度不是很大,自己编写程序,顺便做些总结. &   |   - 这三个位运算符号不难理解,但是要区别与逻辑运算符号&&  和|| 1.需要总结的是:假如要使寄存器的值为1的话,一般用 这个寄存器的值来| 上1 比如要将i的值变为1则可以使用    i   |=  1;    意思就是将i的值与上1的值再给i.同理要让一个变量的值变成0的话,将使用 &上0     例如   i&

NOJ---页面无法显示--位运算

好吧 先解决下 -- 页面无法显示 该死的Oj 不知道为什么突然无法打开了 =-= 我也就忘记题号了 主要现在这场乌拉圭-哥伦比亚 刚开始  忙里偷闲 把这题来写了 明天 不对 应该是今天 不知道要睡到什么时候 =-= 先来贴下关于位运算的操作要求及解释--------我好像上次贴过 我忘记了 orz 位运算是指按二进制进行的运算.在系统软件中,常常需要处理二进制位的问题.C语言提供了6个位操作 运算符.这些运算符只能用于整型操作数,即只能用于带符号或无符号的char,short,int与lon

10、C语言——位运算与文件

位运算与文件 一.位运算 位运算的操作对象只能是整型或字符型数据 c语言提供6种位运算符: & | ^ ~ << >> 复合赋值运算符: &= |= ^= <<= >>= 1.按位与运算(&) 两个相应的二进制都是1时,它们按位运算后的结果才为1,否则为0 作用:清零 2.按位或运算(|) 两个相应的二进制中只要有一个为1时,则它们按位或运算后的结果为1 作用:将特定位置1 3.按位异或运算(^) 当两个相应位同为1或同为0时,按位异

多用户角色权限访问模块问题”的解决思路( 位运算 + ActionFilterAttribute )

如果你还是不太懂位运算,请看我的文章:那些年我们一起遗忘的位运算! 下面是我在这次项目中学习到的,我眼中的位运算的应用!主要是实现 通知的3个操作: 1.  置顶 2.  设为首页 3.  同时为 “置顶”+ “设为首页” 效果如图: 我们要想简便的进行位运算,我们可以直接进行如下枚举定义,以2的次方定义,应为他们的值很特殊: 数      二进制值 1 1 2   10 4       100 8    1000 16     10000 32     100000 64     100000

Java 位运算超全面总结

1.原码.反码.补码 关于原码.反码.补码的相关知识作者不打算在这里长篇大论,相关知识已有别的大佬总结很好了,还请老铁自行 Google,不过有篇知乎回答是作者学编程以来见过对相关知识最通俗易懂,生动简洁的解释:对原码.反码.补码最通俗易懂,生动简洁的解释,墙裂建议大家先看完这篇科普文章.在继续讨论之前你要先明白一点:整数在计算机内部都是以补码形式存储的. 2.Java 位运算概览 OK 都看到这儿了那我就假定你已经掌握了原码.反码.补码相关知识(虽然上面那段几乎啥也没讲,纯凑字数) 不废话了.