基础算法:二分查找

news2024/11/29 2:48:54

目录

  • 1. 二分查找
  • 2. 补充:二进制运算
    • 2.1 十进制与二进制的相互转换
      • 2.1.1 十进制转二进制
      • 2.1.2 二进制转十进制
    • 2.2 机器数 真值
    • 2.3 原码 补码 反码
    • 2.4 二进制的加减乘除
    • 2.5 移位运算

1. 二分查找

思想: 有序数组,从中找值

实现:

  • while 循环:时间复杂度:log(n)

        public static int binarySearch01(int[] arr, int target) {
            int i = 0;
            int j = arr.length - 1;
            // <= 是因为目标值可能在 0 或 arr.length - 1 索引处
            while (i <= j) {
            	// 用移位运算防止溢出
                int mid = (i + j) >>> 1;
                if ( target < arr[mid]) {
                    j = mid - 1;
                } else if (arr[mid] < target){
                    i = mid + 1;
                } else {
                    return mid;
                }
            }
            return -1;
        }
    

    优化:把 j 只作为一个右边界,不能指向目标值

        public static int binarySearch02(int[] arr, int target) {
            int i = 0;
            int j = arr.length;
            // 这里如果使用 i <= j,会导致查找不存在的元素的时候出现死循环
            while (i < j) {
                int mid = (i + j) >>> 1;
                if (arr[mid] > target) {
                    j = mid;
                } else if (arr[mid] < target){
                    i = mid + 1;
                } else {
                    return mid;
                }
            }
            return -1;
        }
    

    分析: 往左查找的消耗是往右查找消耗的 1/2,不均衡。(因为往右查找需要比较两次 if)
    优化:平均两侧查找消耗

        public static int binarySearch04(int[] arr, int target) {
            int i = 0;
            int j = arr.length;
            while (1 < j - i) {
                int mid = (i + j) >>> 1;
                if (target < arr[mid]) {
                    j = mid;
                } else {
                	// 这里不能 + 1了,如果目标值就在中间,就找不到了
                    i = mid;
                }
            }
            if (arr[i] == target) {
                return i;
            }
            return -1;
        }
    

    利用 api 写个二分查找,找到目标值就返回索引 + 数组,找不到就插入再返回

        public static Map<Integer, int[]> binarySearch05(int[] arr, int target) {
            int i = Arrays.binarySearch(arr, target);
            Map<Integer, int[]> res = new HashMap<>();
            if (0 < i) {
                res.put(i, arr);
                return res;
            }
            int targetIndex = abs(i + 1);
            int[] arr0 = new int[arr.length + 1];
            System.arraycopy(arr, 0, arr0, 0, targetIndex);
            arr0[targetIndex] = target;
            System.arraycopy(arr, targetIndex, arr0, targetIndex + 1, arr.length - targetIndex);
            res.put(targetIndex, arr0);
            return res;
        }
    
  • Arrays.binarySearch 包含多种类型的数据,这里看 int 的:
    binarySearch0 的返回值 = - 插入点 - 1
    为什么不直接返回 -插入点?答:为了防止插入点为0出现歧义

        public static int binarySearch(int[] a, int key) {
            return binarySearch0(a, 0, a.length, key);
        }
    
        private static int binarySearch0(int[] a, int fromIndex, int toIndex,
                                         int key) {
            int low = fromIndex;
            int high = toIndex - 1;
    
            while (low <= high) {
                int mid = (low + high) >>> 1;
                int midVal = a[mid];
    
                if (midVal < key)
                    low = mid + 1;
                else if (midVal > key)
                    high = mid - 1;
                else
                    return mid; // key found
            }
            return -(low + 1);  // key not found.
        }
    
  • 二分查找返回最左/最右的索引(相同元素情况)

        /**
         * @Desc falg = -1 返回最左索引,flag = 1 返回最右索引
         */
        public static int binarySearchLeftOrRight(int[] arr, int target, int flag) {
            int i = 0;
            int j = arr.length - 1;
            int index = -1;
            while (i <= j) {
                int mid = (i + j) >>> 1;
                if (target < arr[mid]) {
                    j = mid - 1;
                } else if (arr[mid] < target) {
                    i = mid + 1;
                } else {
                    index = mid;
                    if (1 == flag) {
                        i = mid + 1;
                    } else {
                        j = mid - 1;
                    }
                }
            }
            return index;
        }
    
  • 优化: 二分查找返回最左/最右的索引(相同元素情况)

        /**
         * @param flag  = -1 返回最左索引, = 1 返回最右索引
         * @return int 存在,返回对应索引,不存在,返回 - 插入点 - 1
         */
        public static int binarySearchLeftOrRight(int[] arr, int target, int flag) {
            int i = 0;
            int j = arr.length - 1;
            while (i <= j) {
                int mid = (i + j) >>> 1;
                if (flag == 1 ? target < arr[mid] : target <= arr[mid]) {
                    j = mid - 1;
                } else {
                    i = mid + 1;
                }
            }
            int index = flag == 1 ? i - 1 : i;
            if (target == arr[index]) {
                return index;
            } 
            return - index - 1;
        }
    

2. 补充:二进制运算

2.1 十进制与二进制的相互转换

2.1.1 十进制转二进制

  • 方法:短除法
    • 整数:
      逆序排列
    • 小数:
      顺序排列
      原则: 一直转换到小数部分清零为止

思考: 有些小数部分是无法清零的该怎么处理?
这时候就要引入误差/精度了,比如说:0.33转换成二进制,误差小于1‰,那么我假设转换后的结果为 x,那么要求就是: |x - 0.33| < 1‰,即 0.319 < x < 0.331。
那么如何快速计算出结果呢?
进制转换工具

所以 x 取 0.0101010001 就OK了
验证一下:

没问题。

2.1.2 二进制转十进制

  • 整数:10101101 → 173
    向右减权
    1*2^7 + 0*2^6 + 1*2^5 + 0*2^4 + 1*2^3 + 1*2^2 + 0*2^1 + 1*2^0
    
  • 小数:0.1101 → 0.8125
    向右减权
    1*2^-1 + 1*2^-2 + 0*2^-3 + 1+2^-4
    

2.2 机器数 真值

机器数: 数字在计算机中的二进制表示形式。在计算机中,数字以二进制形式存储和处理。机器数的最高位是符号位,用于表示数字的正负。正数的符号位为0,负数的符号位为1。机器数的大小受到计算机字长的限制,字长决定了机器数的表示范围。

// 不同位的计算机中表示 5
00000101   																# 800000000 00000000 00000000 00000101  									# 3200000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101	# 64

64位计算机能一次读8byte,32位能一次读 4byte,可通过 cmd 输入 systeminfo 查看计算机参数
比如某机器的机器数大小是 2 byte,那么它存储数字 5 的机器数就是:0000 0101,最左边的 0 表示正数,其表示 -5 的机器数就是 1000 0101

真值: 带符号位的机器数对应的真正数值,可以通过将机器数转换为十进制数来获得。

比如:机器数 1000 0101 的真值是 -5,0000 0101 的真值是 5。

2.3 原码 补码 反码

原码: 最高位表示符号位,其余位表示数值的绝对值,和机器数一个意思。

反码: 正数的反码和原码相同,负数的反码是在其原码的基础上,符号位不变,其余各位取反。

比如:正数+5的反码和原码都是00000101,负数-5的反码是11111010 。

补码: 正数的补码和原码相同,负数的补码是在其反码的基础上加1。

比如:正数+5的补码和原码都是00000101,负数-5的补码是11111011。

小结一下:对于正数:原码 = 反码 = 补码;对于负数:反码 = 原码标志位不变,其余取反(1变成0,0变成1),补码 = 反码 + 1。

补充:负数的补码转原码

  • 先取反
  • 然后 + 1

2.4 二进制的加减乘除

加法: 正数 + 负数 我们放到减法里面说

  • 方法:从右往左加,逢二进一

减法:

  • 方法:都换成补码,然后相加,相加的结果就是答案

    因为我们是在 8 位二进制里面做加减,所以超出(溢出)的 1 要舍去

乘法:

  • 正数:与十进制一样,按位相乘,然后相加
  • 负数:摘掉标志位,然后相乘,最后根据两个符号位来判断正负

除法:

  • 正数:正数:与十进制一样,按位相除(如果被除数的最高位小于除数的最高位,则除法运算结束。被除数的剩余位数即为余数)
  • 负数:负数取补码,然后相除
    注意:进制负数除法的结果可能是有限的,也可能是无限循环的。在实际计算中,可能需要根据具体情况进行舍入或截断

2.5 移位运算

二进制移位运算:

  • << 左移:将一个数的二进制表示向左移动指定的位数,右侧用0填充

    可以把 << 看做是 *2 操作
  • >> 右移:将一个数的二进制表示向右移动指定的位数,正数高位用0填充,负数高位用1填充

    可以把 >> 看做是 /2 操作,一直右移,正数会变成 0,负数会变成 -1(为了保证它是负数,所以会补个 1)
  • >>> 无符号右移位,不管正数还是负数,高位都用0补齐

注意事项1: 在做移位运算的时候要考虑当前类型的大小,如下:

声明一个 int 整型 -11、11
-11(int) >>> 2 : 
	原二进制:11111111111111111111111111110101
	 二进制: 111111111111111111111111111101
	 十进制: 1073741821

11(int)  << 31 : 
	原二进制: 1011
	  二进制: 10000000000000000000000000000000
	  十进制: -2147483648
11(int)  << 32 : 
	原二进制: 1011
	  二进制: 1011
	  十进制: 11

注意事项2: Java 中会将最高位看做符号位!,所以当数值 > 2^31 的时候,最高位会被当做符号位 1,从而编程负数。

   public static void main(String[] args) {
       int i = (int) pow(2, 31);
       int j = 1;
       System.out.println(i);
       System.out.println(Integer.toBinaryString(i));
       System.out.println(i + j);
       System.out.println(Integer.toBinaryString(i + j));
       System.out.println( (i + j) / 2 );
       System.out.println( (i + j) >>> 1);
   }

  

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

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

相关文章

Qt 布局(QSplitter 类QDockWidget 类) 总结

一、QSplitter 类(窗口分割) QSplitter类是一个Qt框架提供的基础窗口控件类&#xff0c;用于分割窗口&#xff0c;使得用户可以通过拖动分隔条来调节子窗口的大小。QSplitter在用户界面设计中非常常见&#xff0c;经常用于划分窗口区域&#xff0c;使得程序可以同时显示多个子…

4x4矩阵键盘设计Verilog矩阵式键盘控制,视频/代码

名称&#xff1a;4x4矩阵键盘设计Verilog矩阵式键盘控制 软件&#xff1a;Quartus 语言&#xff1a;Verilog 代码功能&#xff1a; 键盘控制电路设计&#xff0c;设计一个4x4矩阵式键盘控制电路&#xff0c;并实现按键的显示。 演示视频&#xff1a;4x4矩阵键盘设计Verilo…

C与C++之间相互调用的基本方法

​ 在你的C语言代码中&#xff0c;不知能否看到类似下面的代码&#xff1a; 这好像没有什么问题&#xff0c;你应该还会想&#xff1a;“嗯⋯是啊&#xff0c;我们的代码都是这样写的&#xff0c;从来没有因此碰到过什么麻烦啊&#xff5e;”。 你说的没错&#xff0c;如果你的…

load initialize 浅析

load 调用顺序&#xff1a;父类->子类->分类 &#xff08;不会覆盖&#xff09;&#xff1b;多个分类情况下&#xff0c;compile sources <谁在前&#xff0c;先执行谁。多个分类都会被执行>&#xff1b;子类覆盖load方法&#xff0c;父类覆盖了仍然会被调用&…

01-spring源码概述

文章目录 1. Spring两大主要功能2. Bean的生命周期&#xff08;部分生命周期&#xff0c;不包括销毁&#xff09;2.1 两个重要接口及Aware接口2.2 创建对象的过程2.3 Bean的scope作用域2.4 Bean的类型2.5 获得反射对象的三种方式 3. 涉及的接口汇总4. 涉及设计模式 1. Spring两…

axios post 请求发送url 键值对参数

后端post 请求要求在 url 上携带参数&#xff0c;各种百度&#xff0c;最后发现了一种可行的方式&#xff0c;用formData对象构造请求参数&#xff0c;然后用URLSearchParams 把formdata 参数转换成键值对&#xff0c;最后发送 function getList() {const formdata new FormDa…

机器学习在工业机器人领域有哪些应用?

随着人工智能和机器学习的快速发展&#xff0c;工业机器人领域也迎来了新的机遇和挑战。本文综述了机器学习在工业机器人领域的应用&#xff0c;包括机器人视觉、运动控制、路径规划、故障诊断等方面。通过对相关研究和实际应用的分析&#xff0c;总结了机器学习在工业机器人领…

【JVM】JVM类加载机制

JVM类加载机制 加载双亲委派模型 验证准备解析初始化 JVM的类加载机制,就是把类,从硬盘加载到内存中 Java程序,最开始是一个Java文件,编译成.class文件,运行Java程序,JVM就会读取.class文件,把文件的内容,放到内存中,并且构造成.class类对象 加载 这里的加载是整个类加载的一…

ubuntu静态ip地址设置

ifconfig命令显示 手动设置静态ip地址 1、ip地址 IP地址是唯一标识互联网上每个设备的地址&#xff0c;一根网线对应一个ip&#xff0c;一个计算机上可以有多个网卡&#xff0c;所以可以有多个ip地址。 私有地址的范围分别是&#xff1a; A类地址范围&#xff1a;10.0.0…

vue3后台管理框架之路由配置

pnpm install vue-router 在src新建文件夹views和router 1.1基本 路由配置 :hash 路由模式 // 对外配置路由 import Login from @/views/login/index.vue import Home from @/views/home/index.vue import Error from @/views/404/index.vue export cons

循环结构的运用

乘法口诀起源于中国&#xff0c;是古代人进行乘法、除法、开方等运算的基本法则&#xff0c;距今已经有两千多年的历史了&#xff0c;如何运用现代计算机技术快速写出九九乘法表呢&#xff1f; 循环结构可以用来重复执行一条或者多条语句&#xff0c;利用循环结构可以减少源程序…

【JAVA-Day43】Java常用类Calendar解析

Java常用类Calendar解析 Java常用类Calendar解析&#xff0c;科学处理Java日期数据摘要 &#x1f4dd;介绍java.util.Calendar类 &#x1f4c5;什么是java.util.Calendar&#xff1f;为什么需要使用它&#xff1f; Calendar类的基本用法 &#x1f4c6;如何创建一个Calendar对象…

nocos注册中心使用教程

1.下载和安装 进入到官网下载就好了 解压 启动 2.新建提供者模块 2.1新建提供者模块cloudalibaba-provider-payment9001 2.1.1在父项目中新加入依赖 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-depend…

<图像处理> 图像插值算法

图像插值算法 图像插值在图像处理中常用于调整图像尺寸或变形&#xff0c;其目标是根据给定像素点周围像素点的信息来预测该像素点的值。 常见的图像插值算法可以分为两类&#xff1a;自适应和非自适应。自适应的方法可以根据插值内容的特点来进行调整&#xff0c;而非自适应…

港科大提出适用于夜间场景语义分割的无监督域自适应新方法

跟大家分享港科大提出的无监督域自适应夜间场景语义分割方法&#xff0c;该方法对夜间的动态目标和小目标做了针对性的优化。 论文标题&#xff1a;Towards Dynamic and Small Objects Refinement for Unsupervised Domain Adaptative Nighttime Semantic Segmentation机构&…

UGUI交互组件ScrollBar

一.ScrollBar的结构 对象说明Scrollbar挂有Image和Scrollbar组件的主体对象Sliding Area表示滑动范围Handle滑块 二.Scrollbar的属性 属性说明Handle Rect控制柄对象的引用Direction拖动控制柄时滚动条值增加的方向Value滚动条的当前值&#xff0c;范围为 0.0 到 1.0Suze控制柄…

【Python搜索算法】深度优先搜索(DFS)算法原理详解与应用,示例+代码

目录 1 基本原理 2 DFS算法流程 3 时间复杂度 4 空间复杂度 5 DFS算法应用案例&#xff1a; 5.1 解决路径查找问题 5.2 解决图的连通性问题 5.3 拓扑排序 5.4 在树结构中进行深度遍历 深度优先搜索&#xff08;DFS&#xff09;是一种重要的图遍历算法&#xff0c;用…

容性耦合和距离的关系

容性耦合和距离的关系 在电路板设计中&#xff0c;耦合是指两个或多个电路元件之间的相互作用&#xff0c;这种相互作用会影响到电路的性能。而容性耦合是指通过电场进行耦合的一种方式&#xff0c;它可以通过电容器的作用使得信号在电路板中传递&#xff0c;但同时也会带来一些…

HBuilder创建uniapp默认项目导入uview(胎教)

1&#xff1a;更新HBuilder 建议更新 2&#xff1a;更新插件 我本人在没有更新插件的情况下报错了&#xff0c;找到了**这个大佬**解决问题&#xff0c;所以建议更新插件 先卸载uni-app&#xff08;Vue2&#xff09;编译 再重新安装 uni-app&#xff08;Vue2&#xff09;…

RabbitMQ从0到1完整学习笔记二:《高级篇》

目录 1. 发送者的可靠性 1.1.生产者连接重试机制 1.2.生产者确认机制&#xff08;发布确认&#xff09; 1.3.实现生产者确认 1.3.1.开启生产者确认 1.3.2.定义ReturnCallback 1.3.3.定义ConfirmCallback 拓展&#xff1a; confirm 模式细节处理 2.MQ的可靠性 2.1.数据持久化 2.…