算法通过村第十一关-位运算|青铜笔记|初始位运算

文章目录

  • 前言
  • 1. 数字在计算中的表示
    • 拓展:为什么要有原码、反码和补码?
  • 2. 位运算规则
    • 2.1 与、或、异或和取反
    • 2.2 位移运算
    • 2.3 位移运算和乘除的关系
    • 2.4 位运算的常用技巧
  • 总结


前言


提示:我的父亲从我出生起便认识我,可他对我的了解却那么少,真实奇怪啊。 --C·S·路易斯《惊喜之旅》

位运算是计算机的核心基础,数据的便是和计算几乎都少不了,在JVM以及很多高性能代码中大量使用,甚至很多算法本身就是基于位进行的。在算法方面,很多位相关的算法都很有技巧,不学不知道。另外很多算法看起来与位运算无关,但是通过位运算操作优化一下,性能会提高不少,感兴趣的同学,接着往下看吧。

在学习位操作之前,我们先明确数据在计算机中怎么表示的。我们明确了,原码、反码和补码的概念和便是方法之后我们再来看位运算的相关问题。

1. 数字在计算中的表示

机器数:一个数载计算机中的二进制表示形式,叫做这个数的机器数。机器数是导游符号的,在计算机用一个数的最高位存放符号,正数为0,负数为1。比如,十进制中数+3,在计算机中长度为8位,转换成二进制就是0000 0011.如果是-3,就是1000 0011。这里的0000 0011和1000 0011就是机器数。

真值:因为机器数第一位是符号位,所以机器数的形式值就不等于真正的数值。比如上面说的有符号数的1000 0011,其最高位1代表负,其真值是 - 3而不是形式值(131【1000 0011】)。所以,为了号区别,将带有符号位的机器数对应的真值数称为机器数的真值。例:1000 0011的真值 = - 000 0011 = -3,0000 0011的真值 = + 000 011 = + 3.

计算机对机器数的表示进一步细化:原码、反码、补码。

原码:就是说符号位加上真值的绝对值,即用第一位表示符号,其余位表示值,比如如果是8位二进制:

[+1]= 0000 0001
[-1]= 1000 0001

第一位是符号位,因为第一位是符号位,所以8位二进制的取值范围就是:

[1111 1111,0111 1111],也就是【-127,127】

反码:就是说正数的反码是其本身,而负数的反码是其原码的基础上,符号位不变,其余各个位取反。例如:

[+1] = [0000 0001]= [0000 0001][-1] = [1000 0001]= [1111 1110]

可见如果一个反码便是的是负数,人脑无法直观的看出它的数值,通常要将其转化成原码再计算。

在应用中,因为补码能保持加和减运算的统一,一次应用更广泛,便是方法就是:

  • 正数的补码就是其本身
  • 负数的补码就是再其原码的基础上,符号位不变,其余各位取反,最后+1(即再反码的基础上+1)
[+1] = [0000 0001]= [0000 0001]= [0000 0001][-1] = [1000 0001]= [1111 1110]= [1111 1111]

对于负数,补码表示方式也是人脑无法直观的看出其数值的,通常也需要转换成原码再计算器数值。

拓展:为什么要有原码、反码和补码?

既然原码就能表示数据,为什么实际软件中更过使用的是补码呢?我们接着往下看。

现在我们知道了计算机可以有三种编码方式表示一个数,对于正数因为有三种编码方式的结果都相同:

[+1] = [0000 0001]= [0000 0001]= [0000 0001]

但是对于负数:

[-1] = [1000 0001]原 = [1111 1110]反 = [1111 1111]补

可见原码、反码和补码完全不同。既然原码才能被人脑直接识别并用于计算表示方式,可我为什么还有反码和补码呢?

首先,因为人脑可以知道第一位是符号位,在计算的时候会根据符号位选择对真值区域的加减。但是计算机要辨别“符号位”就必须获取全部的位的数据才可以,显然会让计算机的基础电路设计变得十分复杂!于是人们相处了将符号位也参与运算的方法。我们知道,根据计算法则减去一个正数等于加上一个负数,即 1 - 1 = 1 + (-1) = 0,所以机器可以只有加法而没有减法,这样计算机运算的设计就更简单了,于是人们开始探索让符号参与运算,并且只保留加法的方法。

看一个栗子🌰,计算十进制的表达式:1 - 1 = 0,首先看原码的表示:

1 - 1 = 1 + (-1) = [0000 0001]+ [1000 0001]= [1000 0010]= -2

如果用原码便是让符号位也参与计算,显然对于减法来说,结果是不正确的,这也是为何计算机内部不能使用原码表示一个数。

为了解决原码做减法的问题就出现了反码,此时计算十进制的表达式位:1 - 1 = 0

1 - 1 = 1 + (-1) = [0000 0001]+ [1000 0001]= [0000 0001]+ [1111 1110]= [1111 1111]= [1000 0000]= - 0

可以看到用反码计算结果的真值部分是正确的,但是“0”的表示有点奇怪,+0和-0是一样的,而且0带符号是没有任何意义的,而且还要浪费[0000 0000]原和[1000 0000]原两个编码来表示0。于是补码的出现,解决了0的符号以及两个编码的问题:

1 - 1 = 1 + (-1) = [0000 0001]+ [1000 0001]= [0000 0001]+ [1111 1111]= [0000 0000]= [0000 0000]

这样0用[0000 0000]表示,而以前出现的-0则不存在了,而且可以用[1000 0000]表示-128:

-1 - 127 = -1 + (-127) = [1000 0001]+ [1111 1111]= [1111 1111]+ [1000 0001]= [1000 0000]

-1-127的结果应是-128,我们正好可以用[1000 0000]来表示-128,这样使用补码的范围就可以扩大为[-128,127],这里刚好比原码的[-127,127]好。拓展一下,对于编程中常用到的32位int类型,可以表示的范围是[-231,231 - 1],这是我们经常见到的定义方式。

2. 位运算规则

这个章节主要学习一下二进制,这里要好好学上面的知识,牢记掌握。⭐⭐⭐⭐

计算机采用的是二进制,二进制包括两个数码:0,1。在计算机的底层,一切运算都是基于位运算实现的,所以研究清楚位运算可以帮助我们对于基础原理有进一步加深。

在算法方面,不少题目都是基于位运算拓展而来的,而且还有一定的技巧,如果不是提前学一学,面试的时候是很难想到的。

位运算主要有:与,或、异或、取反,左移和右移。其中左移和右移统称为移位运算,移位运算又称为算术移位和逻辑移位。

2.1 与、或、异或和取反

与运算的符号是&,运算规则是:对于二进制位,当两个数对应的位都为1时,结果才为1,否则结果为0。

0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1

或运算的符号是|,运算规则是:对于每个二进制位,当两个数对应的位都为0时,结果才是0,否则结果为1。

0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1

异或运算的符号时^(在代码中表示),运算规则时:对于每个二进制位,当两个数对应的位相同时,结果为0,否则结果为1。

0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0

取反运算的符号时~,运算规则是:对于一个数的每个二进制位进行取反操作,0 变成1,1变成 0。

~ 0 = 1 
~ 1 = 0

我们做一下测试,来检测一下你的掌握程度:

46的二进制表示[0010 1110], 51的二进制表示是[0011 0011]。考虑以下的位运算结果。

  • 46 & 51 的结果是 34

    • [0010 1110]
      [0011 0011][0010 0010]   34
      
  • 46 | 51 的结果是 63

    • [0010 1110]
      [0011 0011][0011 1111]   63
      
  • 46 ^ 51 的结果是 29

    • [0010 1110]
      [0011 0011][0001 1101]   29
      
  • ~ 46 的结果是

    • [0010 1110][0010 1110][0010 1110]补 (运算)
      [1101 0001]
      [1101 0000][1010 1111]-47  
      
  • ~ 51 的结果是

    • [0011 0011][0011 0011][0011 0011]补 (运算)
      [1100 1100] 
      [1100 1011][1011 0100]原   有问题
      

2.2 位移运算

移位运算按照移位的方向可以划分为左移和右移,按照是否带有符号分类可以分为算数移位和逻辑移位。

原始:[0000 0110] 6

右移一次:[0000 0011] 3 相当于除以2

左移一次:[0000 1100] 12 相当于乘以2

6*3 = 6*(2 + 1) = 6 * 2 + 6 * 1
66 * 33 = 66 * (32 + 1) = 66*32 + 66 * 1

左移运算的符号是<<,左移运算时,将全部二进制位向左移动若干位,高位丢弃,低位补0.对于左移运算,算术移位和逻辑移位时相同的。

右移运算的符号时>>,右移运算时,将全部的二进制位向右移动若干位,低位丢弃,高位的补位由算数移位和逻辑移位决定:

  • 算术右移时,高位补最高位
  • 逻辑右移时,高位补0

一下栗子🌰显示位移运算的结果,检验你是否过关,参与运算的数组都采用有符号8位二进制表示:

  • 29的二进制表示[0001 1101]。29左移2位的结果是 116,对应的二进制表示[0111 0100];29左移3位的结果是-24表示[1110 1000]

    • [1110 1000][1110 0111][1001 1000]-24
      
  • 50的二进制表示是[0011 0010]。右移1位的结果是25[0001 1001];右移2位的结果是12[0000 1100]。所以对于正数和0,算术右移和逻辑右移的结果是相同的。

  • -50的二进制表示是[1011 0010] 补码[1100 1011],右移2位的结果是-13[1111 0011]; -50逻辑右移2位的结果是51,对应的二进制表示[0011 0011].【没完全理解】

右移运算中的算术移位和逻辑移位是不同的,计算机内部的右移运算采用的是哪一种呢?

  • 对于Java而言,不存在无符号类型,所有便是整数的类型都是有符号类型,此时需要区分算术右移和逻辑右移。在Java中,算数右移的符号是>> ,逻辑右移的符号是>>>
  • 对于C/C++而言,数据类型包含由符号类型和无符号类型,其中有符号类型使用关键字signed声明,无符号类型使用关键字unsigned声明,两个关键字都不适用时,默认有符号类型。对于有符号类型,右移运算为算数右移,对于无符号类型,右移运算为逻辑右移。

2.3 位移运算和乘除的关系

从上面的栗子🌰观察,移位运算可以实现乘除的操作。由于计算机的底层的一切运算都是基于位运算实现的,因此,使用移位运算实现乘除的效率显著高于直接乘除法的。

左移运算对应乘法运算。将一个数左移k位,就等价于将这个数乘以2^k。例如:29左移2位的结果是116,等价于29*4。当乘数不是2的整数次幂,可以将乘数拆成若干项2的整数次幂之和,例如:a * 6 等价于(a << 2)+(a << 1),乘法运算都可以用左移运算实现,但是需要注意溢出的情况,例如8位二进制表示下29左移3位,就会出现溢出。

算数右移运算对应除法运算,将一个数右移k位,相当于将这个数除以2^k。例如。50右移2位的结果是12,等价于50/4,结果是向下取整。

从程序实现的角度,考虑程序中的整数除法,是否可以说将一个数(算数)右移k位,和将这个数除以2^k等价?对于0和正数,上述的说法是成立的,整数除法是向0取整,右移运算是向下取整,也是向0取整。但是对于负数,上述的说法就不成立了,整数除法是向0取整,右移运算时向下取整,两者就不相同了。比如:(-50) >> 2 的结果时-13,而(-50)/ 4 的结果时 -12,两者是不等的。因此要考虑一个数(算术)右移k位,和将这个数除以2^k是不等价的。算法出题也想到了这一点,因此大部分算法题目都是将测试数据限制在正数和0的情况,因此可以放心的左移和右移。

2.4 位运算的常用技巧

位运算的性质有很多,此处记录一些常见的性质,假设一下出现的变量都是有符号的整数

  • 幂等律:a & a = a , a | a = a (注意异或不满足幂等律)
  • 交换律:a & b = b & a , a | b = b|a , a ^ b = b ^ a
  • 结合律:(a & b) & c = a & (b & c), (a | b) | c = a | (b | c), (a ^ b) ^ c = a ^ (b ^ c)
  • 分配律: (a & b) | c = (a | c) & (b | c), (a | b) & c = (a & c) | (b & c) ,(a ^ b) & c = (a & c) ^ (b & c)
  • 摩尔根律:~(a & b) = (~a ) | (~b), ~(a | b) = (~a) & (~b)
  • 取反运算性质:- 1 = ~0, -a = ~(a - 1)
  • 与运算性质:a & 0 = 0, a & ( - 1) = a , a & ( -a ) = 0
  • 或运算性质: a | 0 = a;
  • 异或运算性质:a ^ 0 = a , a ^ a = 0;

根据上面的性质呢,还可以有很多小工具:

  • a & (a - 1)的结果是将a 的二进制表示的最后一个1 变成 0;
  • (补码)a & (-a)的结果只保留a的二进制最后一个1,其余的1都变成0;

处理位操作时,还有很多技巧,切莫死记硬背,理解其原理解决相关问题才有用。举个例子:

1s和0s分别代表与x等长的一串1和一串0:

x ^ 0s = x    x ^ 1s = ~x   x ^ x = 0
x & 0s = 0    x & 1s =  x   x & x = x
x | 0s = x    x | 1s = 1s   x | x = x

而获取、设置、更新某个数数据,也有固定的套路的。

获取:

该方法将1左移i位,得到形如[0001 0000]的值。接着对这个数与num执行“位于”操作,从而将i位之外的所有位清零,最后检查该结果是否位零,不为零说明i位为1,否者说明i位为0

public boolean getBit(int num, int i){return ((num & (1<<i)) != 0);
}

设置(将某一位设置为1)

该方法将1左移i位,得到形如[0000 1000]的值,接着对这个值和num执行“位或”操作,这样只会改变i的数据。这样除i位以为均不发生变化,且i位被设置位1。

public int setBit(int num,int i){return num | (1 << i);
}

清零(将某一位设置为0)

该方法与setBit相反,首先将1左移i位获得形如[0000 1000]的值,对这个值取反而得到形如[1111 0111]的值,直接对该值和num执行“位于”操作,故不会影响到num的其他位,只会清零i位。

public int clearBit(int num,int i){int mask = ~(1 << i);return nums & mask;
}

更新

该方法是将setBit和clearBit合为一个方法,首先是将形如[1111 0111]的值将num的第i位清零。接着将待写入的值v左移i位,得到一个i位为v的但是其余位都为0的数,最后对之前的结果执行“位或”操作,将v位1的这个与num的i位更新位1,否则为0;

public int updateBit(int num,int i ,int v){int mask = ~(1 << i);return (num & mask) | (v << i);
}

总结

提示:认识位运算;位运算由来;计算机原理;计算机基础;位运算小技巧


博主创作不易,如果感觉文章内容对你有所帮助的话不妨三连一下再走呗。你们的支持就是我更新的动力!!!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

西北主要河流水系(绿洲)流域(山区)及高程分类数据集(一)

最近收集整理的了西北地区主要河流水系&#xff08;绿洲&#xff09;流域&#xff08;山区&#xff09;及高程分类数据&#xff0c;&#xff0c;本次主要是新疆的河流水系&#xff08;绿洲&#xff09;流域&#xff08;山区&#xff09;及高程分类数据&#xff08;矢量&#xf…

ThemeForest – Canvas 7.2.0 – 多用途 HTML5 模板

ThemeForest 上的 HTML 网站模板受到全球数百万客户的喜爱。与包含网站所有页面并允许您在 WP 仪表板中自定义字体和样式的 WordPress 主题不同&#xff0c;这些设计模板是用 HTML 构建的。您可以在 HTML 编辑器中编辑模板&#xff0c;但不能在 WordPress 上编辑模板&#xff0…

机器人过程自动化(RPA)入门 7. 处理用户事件和助手机器人

在UiPath中,有两种类型的Robot用于自动化任何流程。一个是后台机器人,它在后台工作。它独立工作,这意味着它不需要用户的输入或任何用户交互。另一个是前台机器人,也被称为助理机器人。 本章介绍前台机器人。在这里,我们将了解自动化过程中通过简单按键、单击鼠标等触发事…

【Vue】数据监视输入绑定

hello&#xff0c;我是小索奇&#xff0c;精心制作的Vue系列持续发放&#xff0c;涵盖大量的经验和示例&#xff0c;如有需要&#xff0c;可以收藏哈 本章给大家讲解的是数据监视&#xff0c;前面的章节已经更新完毕&#xff0c;后面的章节持续输出&#xff0c;有任何问题都可以…

Pikachu-xxe (xml外部实体注入漏洞)过关笔记

Pikachu-xxe过关笔记 有回显探测是否有回显file:///协议查看本地系统文件php://协议查看php源代码&#xff08;无法查看当前网页代码&#xff0c;只能看别的&#xff09;http://协议爆破开放端口&#xff08;两者的加载时间不同&#xff09; 无回显第一步第二步第三步 运行结果…

【面试题】2023前端面试真题之JS篇

前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 表妹一键制作自己的五星红旗国庆头像&#xff0c;超好看 世界上只有一种真正的英雄主义&#xff0c;那就是看清生活的真相之后&#xff0c;依然热爱生活。…

1.项目创建与角色移动

目录 1.创建项目 2.导入素材 3.搭建场景 4.创建玩家 1.创建项目 2.导入素材 3D GUNS | Guns Pack | 3D 武器 | Unity Asset Storehttps://assetstore.unity.com/packages/3d/props/weapons/3d-guns-guns-pack-228975 Prototyping Pack (Free) | 3D | Unity Asset S…

外包公司干了2个月,技术倒退两年...

先说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入杭州某软件公司&#xff0c;干了接近3年的功能测试&#xff0c;今年8月份&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了三年的功能测试…

87、Redis 的 value 所支持的数据类型(String、List、Set、Zset、Hash)---->List相关命令

本次讲解要点&#xff1a; List相关命令&#xff1a;是指value中的数据类型 启动redis服务器&#xff1a; 打开小黑窗&#xff1a; C:\Users\JH>e: E:>cd E:\install\Redis6.0\Redis-x64-6.0.14\bin E:\install\Redis6.0\Redis-x64-6.0.14\bin>redis-server.exe redi…

R语言绘制环状柱状堆积图+分组+显著性

无叠加、显著性的代码&#xff1a; #设置工作环境 rm(listls()) setwd("D:/Desktop/0000/code-main/条形图")#加载R包 library(ggplot2) # Create Elegant Data Visualisations Using the Grammar of Graphics library(tidyverse) # Easily Install and Load the Ti…

HarmonyOS/OpenHarmony原生应用-ArkTS万能卡片组件Badge

可以附加在单个组件上用于信息标记的容器组件。该组件从API Version 7开始支持。 支持单个子组件。子组件类型&#xff1a;系统组件和自定义组件&#xff0c;支持渲染控制类型&#xff08;if/else、ForEach和LazyForEach&#xff09;。 一、接口 方法1&#xff1a; Badge(value…

【Python实战】-- 按条件提取所有目录下所有Excel文件指定行数据

系列文章目录 文章目录 系列文章目录前言一、背景二、使用步骤1.源码总结前言 一、背景 有多个目录,每个目录下有若干Excel文件,我们要提取每个Excel里面指定的行数据: 目录如下: 注:目录数量、名称不限,其中文件数量、名称不限 二、使用步骤 1.源码 解释: 每个文件…

计算机视觉: 可控的高质量人体生成

背景 关于人体动作的生成范式目前主流的方向可以分为以下两种: Sequence based motion generation: 给定控制信号然后一次性生成连续的动作&#xff0c;能生成一些连续高阶语义的动作信号&#xff0c;因为其能看到整个动作信号。eg: MDM: Human Motion Diffusion Model, Teve…

LongLoRA:不需要大量计算资源的情况下增强了预训练语言模型的上下文能力

麻省理工学院和香港中文大学推出了LongLoRA&#xff0c;这是一种革命性的微调方法&#xff0c;可以在不需要大量计算资源的情况下提高大量预训练语言模型的上下文能力。 LongLoRA是一种新方法&#xff0c;它使改进大型语言计算机程序变得更容易&#xff0c;成本更低。训练LLM往…

Elastic SQL 输入:数据库指标可观测性的通用解决方案

作者&#xff1a;Lalit Satapathy, Ishleen Kaur, Muthukumar Paramasivam Elastic SQL 输入&#xff08;metricbeat 模块和输入包&#xff09;允许用户以灵活的方式对许多支持的数据库执行 SQL 查询&#xff0c;并将结果指标提取到 Elasticsearch。 本博客深入探讨了通用 SQL …

RFID技术引领汽车零部件加工新时代

RFID技术的兴起引领了汽车零部件加工领域的新时代&#xff0c;作为一种利用无线电频率进行自动识别的技术&#xff0c;RFID技术能够快速、准确地识别物体并获取相关数据&#xff0c;在汽车零部件加工中&#xff0c;RFID技术具有重要的应用价值&#xff0c;可以提高生产效率、降…

ElementUI基本介绍及登录注册案例演示

目录 前言 一.简介 二.优缺点 三.Element完成登录注册 1. 环境配置及前端演示 1.1 安装Element-UI模块 1.2 安装axios和qs(发送get请求和post请求) 1.3 导入依赖 2 页面布局 2.1组件与界面 3.方法实现功能数据交互 3.1 通过方法进行页面跳转 3.2 axios发送get请求 …

Ubuntu性能分析-ftrace 底层驱动

1、框架介绍 ftrace内核驱动可以分为几部分:ftrace framework,RingBuffer,debugfs,Tracepoint,各种Tracer。 ftrace框架是整个ftrace功能的纽带,包括对内和的修改,Tracer的注册,RingBuffer的控制等等。 RingBuffer是静态动态ftrace的载体。 debugfs则提供了用户空间…

深度解读F5:从企业级负载均衡到云原生应用服务

上世纪九十年代&#xff0c;Internet 的快速发展催生了大量在线网站&#xff0c;Web 访问量迅速提升。在互联网泡沫破灭以前&#xff0c;这个领域基本是围绕如何对 Web 网站进行负载均衡与优化。因而在早期&#xff0c;也会有“Web 交换机”的说法。从1997年 F5 发布了 BIG-IP …

对负采样(negative sampling)的一些理解

负采样&#xff08;negative sampling&#xff09;通常用于解决在训练神经网络模型时计算softmax的分母过大、难以计算的问题。但在LightGCN模型论文的BPR LOSS中&#xff0c;负采样的概念可能与传统的softmax分母问题不完全一样。 在LightGCN模型中&#xff0c;不同于传统的协…