数据结构与算法--位运算

位运算

  • 记得大学编译原理刚开始面对的就是机器语言,而位运算就是讲数字用二进制标识后,对每一位上的0, 或者1 进行运算。二进制以及位运算都是计算机学科的基础,很多底层技术都离不开位运算,因此我们在实际开发过程中可能会遇到。由于我们人类熟悉的是十进制,因此接触到二进制的时候可能难以适应。
  • 除了二进制,我们还可以将数字表示成其他的进制,比如,表示分秒的六十进制等。针对不太熟悉的进制,可能会有一些奇奇怪怪的问题,比如下面这种:
/**在Excel中,用A表示第一列,B表示第二列......Z表示第26 列,AA表示27 列,AB表示28列......以此类推那么我们用一个函数,输入字母,输出列的编码是多少号*/
/*** @author liaojiamin* @Date:Created in 16:30 2021/3/17*/
public class NumberOfBit {//字母转数字  A-Z :1-26public static int letterToNumber(char letter) {int num = (int)(letter - 'A' + 1) ;return num;}public static Long base26(String baseStr){if(baseStr == null){return -1L;}char[] charArray = baseStr.toCharArray();Long baseNumber = 26L;Long resultNum = 0L;double position = 0;for (int i = charArray.length -1; i >=0; i--) {Double temp = Math.pow(baseNumber, position) * letterToNumber(charArray[i]);resultNum += temp.longValue();position++;}return resultNum;}public static void main(String[] args) {System.out.println(base26("AA"));System.out.println(base26("AB"));System.out.println(base26("AZ"));System.out.println(base26("ZZ"));System.out.println(base26("AAA"));}
}
  • 如上其实就是讲A~Z理解成一个26进制,只要理解了二进制的计算规则,26进制也是同样的算法。
  • 二进制中位运算也就只有五种与, 或,异或,左移,右移。前三种用如下规则
与(&):0&0 = 01&0 = 00&1 = 01&1=1
或(|):0|0 = 01|0 = 1. 0|1=11|1 = 1
异或(^):0^0 = 01^0=10^1=11^1 = 0

-左移运算符m << n表示吧m左移n位,左移n位的时候,最左边的n位将被丢弃,同时最右边补充n个0,例如:

0000 1010 << 2 = 0010 1000
1000 1010 << 3 = 0101 0000 
  • 右移运算符m >> 表示把m右移n位, 右移n为的时候,最右边的n为将被丢弃,当右移时候,处理最左边为的情形要稍微复杂一点
    • 如果是无符号数值,则用0 填补最左边的n为。
    • 如果是有符号数值,则用数字的符号位补充最左边的n位,也就是说如果数字是一个正数,右移后补充n个0 在左边,如果原数字是负数,则右移后最左边补充n个1.
    • 如下对两个8 为有符号数值做右移操作
0000 1010 >>2 == 0000 0010
1000 1010 >>3 = 1111 0001
具体案例分析
  • 题目:实现一个函数,输入一个整数,输出该整数二进制标识中1 的个数。例如把 9 标识成二进制后四位1001,有2位是1,因此如果输入9 ,函数输出2
分析
  • 上题中考察二进制的知识,基本思路也就是利用那五种运算规则的组合来解答,可以先判断二进制数的最低位是不是1 ,将数值右移一位,接着判断最低位是否是1 ,继续如此直到数值为0 为止,
  • 现在需要确定怎么确定首位是1,:将整数与1 做 与操作,看结果是否是0 ,如果是0 则表示最低位是0 ,否则是1
  • 用9 为案例如下:
1001 & 1 = 1
1001 >> 1 = 0100
0100 & 1 = 0
0100 >> 1 = 0010
0010 & 1 = 0
0010 >> 1 = 0001
0001 & 1 = 1
0001 >> 1 = 0000
  • 以上我们可以得到如下代码:
/*** 正整数n 二进制表示中 1 的个数* */public static int numberOf1(int n){int count = 0;while (n > 0){int andOne = n & 1;if(andOne == 1){count ++;}n = n>>1;}return count;}
  • 问题,以上代码都只考虑正数情况,正数右移是补充0 在最高位,所以能得出正常的解。我们用一个负数来举例,例如 -9 。
  • 首先必须要明确的是:***负数在计算机中都是以补码来表示的。 负数的位运算也是在补码上进行的。***这一点很重要
    • 我们用int类型为案例分析,int 32位有符号整型(-9) 的二进制标识:
    • 原码:1000 0000 0000 0000 0000 0000 0000 1001
    • 反码:1111 1111 1111 1111 1111 1111 1111 0110
    • 补码:1111 1111 1111 1111 1111 1111 1111 0111
  • 用以上算法对-9 进行右移的时候,在最左边补充的是1 ,因此不可能为0 ,因此会有死循环。
正确解法
  • 上面的算法中我们思路是通过对求解的数进行右移来判断首位是否是1 ,既然不能通用,那么我们是否可能逐一判断数值的每一位二进制位上是否是1 。我们用flag表示 甄别是否是1 的数值 ,首先取flag = 1
  • 首先将数值与flag进行与操作,判断最低位是否是1 ,
  • 将flag 左移一位,继续与数值与操作,判断第二位是否为1
  • 继续如上操作,直到 flag的值为 0 ,(此时移动了32 位,超出int界限,所有位置都为0 )
  • 我们可以得到如下算法实现:
 /*** 通用二进制表示中 1 个数* */public static int numberOf1_1(int n){int count = 0;int flag = 1;while (flag > 0){if((n & flag) > 0){count ++;}flag = flag << 1;}return count;}
  • 以上算法能统一解决带符号,和不带符号的数值的问题,并且对所有数值的计算都需要循环32 次,但是其实其中只有几次是有效的判断,比如1001 只有2 次,我们是否有更精简的方法。有几个1 就几次判断?
最优解
  • 分析将一个数减1 , 如果是不为0 的数,那么二进制表示中必然有1 ,假设这个数最右边一位是1,那么减1 的时候,最右边(低位)就会1 变0,而其他所有位不变。相当于最后一位取反。例子:
0000 1000 0010 0000 0110 0000 0000 0001 - 1 = 0000 1000 0010 0000 0110 0000 0000 0000
  • 此时我们只能判断出第一位是不是1,此时我们还继续减1 ,那么假设第m位是1 ,这个时候,第m位 变为0 ,第m位以后的位都从0 变1 ,如下
0000 1000 0010 0000 0110 0000 0000 0000 - 1 = 0000 1000 0010 0000 0101 1111 1111 1111
  • 这个时候1 变多了,如果继续减,显然不能达到我们目的,我们需要将后面多出来的1 都变成0 ,如下
0000 1000 0010 0000 0101 1111 1111 1111  --> 0000 1000 0010 0000 0100 0000 0000 0000
  • 要达到如上目的,我们想到可以用与,或操作,两个都试试,如下:
//或操作
0000 1000 0010 0000 0101 1111 1111 1111 | 0000 1000 0010 0000 0100 0000 0000 0000 = 0000 1000 0010 0000 0100 0000 0000 0000
//与操作
0000 1000 0010 0000 0101 1111 1111 1111 & 0000 1000 0010 0000 0110 0000 0000 0000 = 0000 1000 0010 0000 0100 0000 0000 0000
  • 如上与,或,操作都能得到对应的值,但是与原值操作的对象,我们怎么得到呢,我们可以观察到,与操作中的操作对象的二进制值正好是原数据进行 -1 操作之前的二进制,那么问题就变的很简单了。
  • 我们将以上分析总结:
    • 将整数减去1,在和原整数做与操作,
    • 此时能将该整数最右边一个1 变成0 ,
    • 那么二进制表示中有多少个1 ,就可以进行多少次这种操作
    • 因此有如下算法
  public static int numberOf1_2(int n){int count = 0;while (n != 0){n = n&(n-1);if(n != 0){count ++;}}return count;}
相关问题
  • 用一条语句判断一个整数是不是2 的整数次方
    • 一个整数是2 的整数次方,那么二进制表示中必然只有一个2,而其他所有位都是0
    • 同样依据解法三,将原数减1 后得到结果 ,将结果与原数进行与操作必然会变成0
  • 输入两个整数m,n。计算要改变m的二进制表中的多少位才能得到0,。例如10 的二进制 1010,与13的二进制1101
    • 此问题同样考察位操作,如上案例10, 和13,有三位二进制是不同的,我们如何通过二进制位操作甄别
    • 查看五种位操作,与,或,异或,左右移位,其中异或操作中两个二进制相同位数的值为0 ,不同的值为1
    • 利用这个特性, m ^ n 得到一个值x,我们只需要求解x 中1 的个数,就得到m与n中不同位数个数。

上一篇:数据结构与算法–再谈递归与循环(斐波那契数列)
下一篇:数据结构与算法–代码完整性案例分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310430.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EFCore.Sharding(EFCore开源分表框架)

简介本框架旨在为EF Core提供Sharding(即读写分离分库分表)支持,不仅提供了一套强大的普通数据操作接口,并且降低了分表难度,支持按时间自动分表扩容,提供的操作接口简洁统一.源码地址:EFCore.SHarding引言读写分离分库分表一直是数据库领域中的重难点,当数据规模达到单库极限的…

分布式事务 -- seata框架AT模式实现原理

Seata AT 模式 上一节中我们提到AT模式是基于XA事务模型演变过来的&#xff0c;所以他的整体机制也是一个改进版本的两阶段提交协议。 第一阶段&#xff1a;业务数据和回滚日志记录在同一个本地事务中提交&#xff0c;释放本地锁和链接资源第二阶段&#xff1a;提交异步化&…

[Java基础]数据输入

Scanner使用的基本步骤: 1.导包: import java.util.Scanner;2.创建对象: Scanner sc new Scanner(System.in);3.接收数据: int i sc.nextInt();代码如下: import java.util.Scanner;public class OperatorTest {public static void main (String[] args) {//创建对象Scan…

k8s中流量分离以及资源隔离实战

源宝导读&#xff1a;明源云客的终端用户越来越多&#xff0c;也涌现出线上流量活动的场景&#xff0c;大量的访问和接口请求导致服务器出现较高负载。本文将介绍云客团队为了缓解服务器压力&#xff0c;通过K8S进行分流与资源隔离的实践过程。一、背景PaaS和B2C的主要客户云客…

[Java基础]Random

Random的作用和使用步骤: 一、作用: 用于产生一个随机数。 使用步骤: 1.导包: import java.util.Random;2.创建对象: Random r new Random();3.获取随机数: int number r.nextInt(10);//获取数据范围:[0,10)&#xff0c;包括0&#xff0c;不包括10测试案列: import jav…

怎样实现WPF Prism Module的国际化和本地化?

English | 简体中文上一篇有简单介绍主工程的国际化&#xff0c;使用的资源字典(XAML)实现的。这几天我添加了几个Prism模块(Module)&#xff0c;发现子模块使用资源字典的方式实现国际化和本地化不好做&#xff0c;没有找到比较好的参考文章&#xff0c;所以换了一种方式&…

数据结构与算法--代码完整性案例分析

确保代码完整性 在撸业务代码时候&#xff0c;经常面对的是接口的设计&#xff0c;在设计之初&#xff0c;我们必然要先想好入参&#xff0c;之后自然会有参数的校验过程&#xff0c;此时我们需要把可能的输入都想清楚&#xff0c;从而避免在程序中出现各种纰漏。但是难免面面…

C++,Java编程中 标识符 常见命名约定

对方法&#xff0c;变量命名时: 约定1: 标识符是一个单词的时候&#xff0c;首字母小写。 范例: name 约定2: 标识符由多个单词组成的时候&#xff0c;第一个单词首字母小写&#xff0c;其他单词首字母大写 范例: firstName 对类命名时: 约定1: 标识符是一个单词的时候&#…

dotNET Core 3.X 使用 Jwt 实现接口认证

在前后端分离的架构中&#xff0c;前端需要通过 API 接口的方式获取数据&#xff0c;但 API 是无状态的&#xff0c;没有办法知道每次请求的身份&#xff0c;也就没有办法做权限的控制。如果不做控制&#xff0c;API 就对任何人敞开了大门&#xff0c;只要拿到了接口地址就可以…

数据结构与算法--代码鲁棒性案例分析

代码鲁棒性 鲁棒是robust的音译&#xff0c;就是健壮性。指程序能够判断输入是否符合规范&#xff0c;对不合要求的输入能够给出合理的结果。容错性是鲁棒的一个重要体现。不鲁棒的代码发生异常的时候&#xff0c;会出现不可预测的异常&#xff0c;或者程序奔溃。由于鲁棒性非…

【半译】两个gRPC的C#库:grpc-dotnet vs Grpc.Core

grpc-dotnet 是在2019年随着 .NET Core 3.0 一起发布的一个gPRC官方库。在ASP.NET Core 的 gRPC项目模板里面就使用了这个库。.NET Core 3.0之前难道不可以使用gRPC吗&#xff1f;目前&#xff0c;gRPC 在.NET上有两种官方实现&#xff1a;Grpc.Core&#xff1a;这个是原来的gR…

[Java基础]String对象的特点(易错点)

String对象的特点: 1.通过new创建的字符串对象&#xff0c;每一次new都会申请一个内存空间&#xff0c;虽然内容相同&#xff0c;但是地址值不同。 2.以""方式给出的字符串&#xff0c;只要字符串相同(顺序和大小写)&#xff0c;无论在程序代码中出现几次&#xff0…

数据结构与算法--解决问题的方法- 二叉树的的镜像

解决问题的思路 工作中遇到的问题可能用到的数据结构由很多&#xff0c;并且各种数据结构都不简单&#xff0c;我们不可能光凭借想象就能得到问题的解法&#xff0c;因此画图是在家具问题过程中用来帮助自己分析&#xff0c;推理的常用手段。很多问题比较抽象&#xff0c;不容…

使用dnSpy调试asp.net core源码

环境&#xff1a;window 10vs2019 16.5.1dnspy v6.1.4.netcore3.1参考&#xff1a;.Net反编译技术详解及4个反编译工具介绍一、关于dnSpydnSpy是近几年的新秀&#xff0c;功能远比ILSpy强大&#xff0c;甩.net Reflector几条街&#xff0c;被汉化、破解、逆向方面的人才奉为神器…

数据结构与算法--解决问题的方法-顺时针打印矩阵

顺时针打印矩阵 题目输入一个矩阵&#xff0c;按照从外向里顺时针的顺序依次打印每一个数字。例如下案例&#xff1a; 如上图矩阵&#xff0c;顺时针打印&#xff1a;1,2,3,4,8,12,16,15,14,13,9,5,6,7,1,10 以上问题看起来比较复杂&#xff0c;但是又没有涉及到复杂的数据结…

.NET与鲲鹏共展翅,昇腾九万里(二)

在上一篇文章 .NET与鲲鹏共展翅&#xff0c;昇腾九万里&#xff08;一&#xff09;中&#xff0c;我们通过在鲲鹏架构的Euler系统上跑Docker的方式把dotnet core 跑起来了&#xff0c;有读者反馈说“还是走docker喽&#xff0c;你这个标题应该改成鲲鹏和docker两条鲸鱼的故事”…

[Java基础]final和static修饰符

final: final修饰局部变量时: static&#xff1a; static访问特点:

数据结构与算法--举例分析法- 栈的压入弹出序列

举例分析 与上两篇问中画图方法一样&#xff0c;我们可以用举例模拟的方法思考分析复杂问题。当一眼不能看出问题的规律的时候&#xff0c;我们可以用几个具体的例子来模拟一下问题的过程。这样就和我们在程序出现问题时候的debug一样&#xff0c;走一下整个流程&#xff0c;可…

优化委托的 DynamicInvoke

优化委托的 DynamicInvokeIntro委托方法里有一个 DynamicInvoke 的方法&#xff0c;可以在不清楚委托实际类型的情况下执行委托方法&#xff0c;但是用 DynamicInvoke 去执行的话会比直接用 Invoke 的方法会慢上很多&#xff0c;差了两个数量级&#xff0c;所以在知道委托类型的…