ContainerHelpers之二分查找算法详解

news2024/12/23 10:23:45

目录

  • 前言
  • 一、JAVA移位运算符
    • 1.1 >> 带符号右移位运算符
    • 1.2 >>> 无符号右移位运算符
    • 1.3 << 左移位运算符
    • 1.4 Java 中没有 <<<
    • 1.5 ~取反操作
  • 二、ContainerHelpers二分查找算法
  • 总结

前言

安卓SparseArray中多次用到了ContainerHelpersbinarySearch方法,其中的核心在于>>>:无符号右移位运算符,所以首先了解一下>>>无符号右移运算位


一、JAVA移位运算符

1.1 >> 带符号右移位运算符

在 Java 中,>> 是带符号右移位运算符,用于将一个数的二进制表示向右移动指定的位数,并在左侧用原来的符号位填充。>> 的工作原理如下:

  1. 对于正数:
  • 右移操作会将二进制数向右移动指定的位数,高位用 0 填充。
  • 例如,对于 5 >> 1,二进制表示为 00000101,右移 1 位后变为 00000010,即十进制为 2。
  1. 对于负数:
  • 如果是负数,右移操作会保留符号位,即最高位的符号位(1 表示负数,0 表示正数)会在移位过程中保持不变。
    例如,对于 -5 >> 1,二进制表示为 11111011(-5 的补码形式),右移 1 位后变为 11111101,即十进制为 -3。
  1. 注意事项:
  • 右移操作会丢弃被移出的位,所以右移可能导致数据丢失。
  • 右移一位相当于除以 2 的整数部分,右移 n 位相当于除以 2 的 n 次方的整数部分。
  • 对于负数的右移操作,可能会导致结果不同于直接除以 2 的整数部分,因为符号位的影响。

>> 是 Java 中的带符号右移位运算符,用于将二进制数向右移动指定的位数,保留符号位并用原符号位填充。这个操作在处理二进制数时非常有用,可以实现快速的位运算操作。

1.2 >>> 无符号右移位运算符

在 Java 中,>>> 是无符号右移位运算符,用于将一个数的二进制表示向右移动指定的位数,高位用 0 填充。相比之下,>> 是有符号右移位运算符,高位用原符号位填充。 >>> 的用法和特点:

>>>的使用
>>> 是 Java 中的无符号右移位运算符,用于将一个数的二进制表示向右移动指定的位数,高位用 0 填充。
例如,5 >>> 1,二进制表示为 00000101,右移 1 位后变为 0000 0010,即十进制为 2。
例如,-5 >>> 1 ,二进制表示为 11111011,右移 1 位后变为 0111 1101,即十进制为 125。
无符号右移的特点
无符号右移操作会将二进制数向右移动指定的位数,高位用 0 填充。
无符号右移操作可以用于处理无符号数,避免有符号右移可能导致的符号位扩展问题。
对于负数的无符号右移操作,仍然会将二进制数向右移动,高位用 0 填充,不会受到符号位的影响。

>>> 是 Java 中的无符号右移位运算符,用于将一个数的二进制表示向右移动指定的位数,并在高位用 0 填充。与有符号右移位运算符 >> 相比,无符号右移操作不受符号位的影响,适用于处理无符号数。

1.3 << 左移位运算符

在 Java 中,<< 是左移位运算符,用于将一个数的二进制表示向左移动指定的位数,并在右侧用 0 填充。

左移位运算符 <<
对于正数:
左移操作会将二进制数向左移动指定的位数,低位用 0 填充。
例如,对于 5 << 1,二进制表示为 00000101,左移 1 位后变为 00001010,即十进制为 10。
对于负数:
左移操作同样会将二进制数向左移动指定的位数,低位用 0 填充。
例如,对于 -5 << 1,二进制表示为 11111011(-5 的补码形式),左移 1 位后变为 11110110,即十进制为 -10。
注意事项
左移操作会在右侧用 0 填充,所以左移可能导致高位溢出。
左移一位相当于乘以 2,左移 n 位相当于乘以 2 的 n 次方。
对于负数的左移操作,可能会导致结果不同于直接乘以 2,因为补码的符号位的影响。

<< 是 Java 中的左移位运算符,用于将一个数的二进制表示向左移动指定的位数,并在右侧用 0 填充。左移操作可以实现快速的位运算操作,对于乘以 2 的整数倍的操作特别有用。

1.4 Java 中没有 <<<

1.5 ~取反操作

当n为正数时,~(n) = -(n+1)  ~(2)=-3

当n为负数时,~(-n) = n - 1,忽略负号。 ~(-3)=2

二、ContainerHelpers二分查找算法

有了上面的基础我们可以更快的分析ContainerHelpers的二分查找算法

ContainerHelpers.java源码如下:

package android.util;

class ContainerHelpers {

    // This is Arrays.binarySearch(), but doesn't do any argument validation.
    static int binarySearch(int[] array, int size, int value) {
        int lo = 0;
        int hi = size - 1;

        while (lo <= hi) {
            final int mid = (lo + hi) >>> 1;
            final int midVal = array[mid];

            if (midVal < value) {
                lo = mid + 1;
            } else if (midVal > value) {
                hi = mid - 1;
            } else {
                return mid;  // value found
            }
        }
        return ~lo;  // value not present
    }

    static int binarySearch(long[] array, int size, long value) {
        int lo = 0;
        int hi = size - 1;

        while (lo <= hi) {
            final int mid = (lo + hi) >>> 1;
            final long midVal = array[mid];

            if (midVal < value) {
                lo = mid + 1;
            } else if (midVal > value) {
                hi = mid - 1;
            } else {
                return mid;  // value found
            }
        }
        return ~lo;  // value not present
    }
}

这段代码是一个经典的二分查找算法实现,用于在一个已排序的数组中查找给定的值。

static int binarySearch(int[] array, int size, int value)
这是一个静态方法,接受一个 int 类型的数组 array、数组的大小 size 和要查找的值 value 作为参数。
方法返回要查找的值在数组中的索引位置,如果值不存在,则返回一个负数,表示值应该插入的位置。
初始化和循环
int lo = 0;
int hi = size - 1;
while (lo <= hi) {
首先,初始化两个int类型位置 lo 和 hi,分别指向数组的起始位置和结束位置。
进入一个 while 循环,条件是 lo <= hi,即当搜索范围不为空时继续搜索。
计算中间位置和中间值
final int mid = (lo + hi) >>> 1;
final long midVal = array[mid];
在每次循环中,计算中间位置 mid,这里使用了无符号右移位运算符 >>> 替换 /2来避免溢出。
获取中间位置对应的值 midVal,即数组中间位置的元素值。
比较中间值和目标值
if (midVal < value) {
lo = mid + 1;
} else if (midVal > value) {
hi = mid - 1;
} else {
return mid; // value found
}
如果 midVal 小于目标值 value,则更新 lo 为 mid + 1,缩小搜索范围到右半部分。
如果 midVal 大于目标值 value,则更新 hi 为 mid - 1,缩小搜索范围到左半部分。
如果 midVal 等于目标值 value,则直接返回 mid,表示找到了目标值的索引位置。
结果返回
return ~lo; // value not present
如果循环结束时仍未找到目标值,说明值不存在于数组中,此时返回 ~lo。

二分查找法中为什么使用>>>1 而不是/2呢?

在二分查找算法中,我们需要找到数组的中间位置来比较中间值和目标值,以确定搜索范围的缩小方向。在这里使用无符号右移位运算符 >>> 来计算中间位置的原因是为了避免溢出,并且确保取到的是中间位置。
在计算中间位置时,我们通常使用 (lo + hi) / 2 来获取中间位置,但是这种方式可能存在溢出的风险,尤其是当 lohi 非常大时。为了避免这种情况,我们可以使用无符号右移位运算符 >>>,它会将二进制数向右移动指定的位数,高位补0。
具体来说,假设 lohi 都是正整数,无符号右移位运算符 >>> 可以确保取到的中间位置是两者之和的一半,即 (lo + hi) / 2。这样就能够有效地计算中间位置,而且不会出现溢出的情况。
因此,使用无符号右移位运算符 >>> 来计算中间位置可以确保取到正确的中间值,同时避免溢出的问题,从而保证二分查找算法的正确性和效率。

如果数组中没有传入的value,那么返回值是多少呢?
示例:

假设有一个有序数组 array = {2, 4, 6, 8, 10},我们要在这个数组中查找值为 5 的元素。我们使用给定的二分查找算法进行查找,以下是具体的步骤和结果:

  1. 初始时,lo = 0hi = 4,数组大小为 size = 5
  2. 第一次循环迭代:
  • 计算中间索引 mid = (0 + 4) >>> 1 = 2,即中间元素为 6
  • 由于 6 > 5,所以更新 hi = 2 - 1 = 1
  1. 第二次循环迭代:
    - 计算中间索引 mid = (0 + 1) >>> 1 = 0,即中间元素为 2
    - 由于 2 < 5,所以更新 lo = 0 + 1 = 1
  2. 第三次循环迭代:
    - 此时 lo = 1hi = 1,计算中间索引 mid = (1 + 1) >>> 1 = 1,即中间元素为 4
    - 由于 4 < 5,所以更新 lo = 1 + 1 = 2
  3. 循环结束,此时 lo = 2hi = 1。因为 lo > hi,表示整个数组已经被搜索完毕,但没有找到目标值 5
  4. 最终返回 ~lo,即取反 2 的结果为 -3

如果索引值为3的话,那岂不是要插入到6-8之间了
看一下SparseArray的put方法:
如果是负值,还会再取反回来再按照索引2去插入的,即插入到4-6之间。
在这里插入图片描述


总结

ContainerHelpers这段代码实现了一个二分查找算法,通过不断缩小搜索范围来查找目标值在已排序数组中的位置。如果值存在,则返回其索引位置;如果值不存在,则返回应该插入的位置。这是一个高效的查找算法,时间复杂度为 O(log n),适用于已排序的数组。

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

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

相关文章

Android java中包的使用

一.包的使用 为了更好的实现项目中类的管理&#xff0c;提供包的概念。 package语句作为Java源文件的第一条语句&#xff0c;指明该文件中定义的类所在的包。(若缺省该语句&#xff0c;则指定为无名包)。 它的格式为&#xff1a;package 顶层包名.子包名 ; 二.java中主要的包…

Leetcode3039. 进行操作使字符串为空

Every day a Leetcode 题目来源&#xff1a;3039. 进行操作使字符串为空 解法1&#xff1a;哈希 排序 操作的定义&#xff1a;每次操作依次遍历 ‘a’ 到 ‘z’&#xff0c;如果当前字符出现在 s 中&#xff0c;那么删除出现位置最早的该字符&#xff08;如果存在的话&…

【ArcGIS】利用DEM进行水文分析:流向/流量等

利用DEM进行水文分析 ArcGIS实例参考 水文分析通过建立地表水文模型&#xff0c;研究与地表水流相关的各种自然现象&#xff0c;在城市和区域规划、农业及森林、交通道路等许多领域具有广泛的应用。 ArcGIS实例 某流域30m分辨率DEM如下&#xff1a; &#xff08;1&#xff09…

机器学习模型的过拟合与欠拟合

机器学习模型的训练过程中&#xff0c;可能会出现3种情况&#xff1a;模型欠拟合、模型正常拟合与模型过拟合。其中模型欠拟合与模型过拟合都是不好的情况。下面将会从不同的角度介绍如何判断模型属于哪种拟合情况。 &#xff08;1&#xff09;欠拟合与过拟合表现方式 欠拟合…

适配器模式:转换接口,无缝对接不同系统

文章目录 **一、技术背景与应用场景****为什么使用适配器模式&#xff1f;****典型应用场景包括但不限于&#xff1a;** **二、适配器模式定义与结构****三、使用步骤举例****四、优缺点分析****总结** 一、技术背景与应用场景 适配器模式在软件设计中扮演着桥梁角色&#xff…

十一、Qt数据库操作

一、Sql介绍 Qt Sql模块包含多个类&#xff0c;实现数据库的连接&#xff0c;Sql语句的执行&#xff0c;数据获取与界面显示&#xff0c;数据与界面直接使用Model/View结构。1、使用Sql模块 &#xff08;1&#xff09;工程加入 QT sql&#xff08;2&#xff09;添加头文件 …

【SRE系列】--部署gitlab

1、部署gitlab 1.1下载gitlab安装包并安装 下载地址&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/pool/ rootk8s-gitlab:~# wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/pool/bionic/main/g/gitlab-ce/gitlab-ce_16.1.4-ce.0_amd64.d…

抖音视频提取软件使用功能|抖音视频下载工具

我们的抖音视频提取软件是一款功能强大、易于操作的工具&#xff0c;旨在解决用户在获取抖音视频时需要逐个复制链接、下载的繁琐问题。我们的软件支持通过关键词搜索和分享链接两种方式获取抖音视频&#xff0c;方便用户快速找到自己感兴趣的内容。 主要功能模块&#xff1a;…

Linux之JAVA环境配置jdkTomcatMySQL

目录 一. 安装jdk 1.1 查询是否有jdk 1.2 解压 1.3 配置环境变量 二. 安装Tomcat&#xff08;开机自启动&#xff09; 2.1 解压 2.2 启动tomcat 2.3 防火墙设置 2.4 创建启动脚本&#xff08;设置自启动&#xff0c;服务器开启即启动&#xff09; 三. MySQL安装&#xff08;…

【前端素材】推荐优质后台管理系统Sneat平台模板(附源码)

一、需求分析 后台管理系统是一种用于管理网站、应用程序或系统的工具&#xff0c;它通常作为一个独立的后台界面存在&#xff0c;供管理员或特定用户使用。下面详细分析后台管理系统的定义和功能&#xff1a; 1. 定义 后台管理系统是一个用于管理和控制网站、应用程序或系统…

Guitar Pro8.2吉他软件2024中文版功能特点介绍

Guitar Pro 8.2是一款功能强大的吉他乐谱软件&#xff0c;专为吉他手、音乐制作人和音乐爱好者设计。它提供了丰富的功能&#xff0c;帮助用户轻松创建、编辑、打印和分享吉他乐谱。以下是Guitar Pro 8.2的主要功能特点&#xff1a; Guitar Pro 2024 win-安装包下载如下&#x…

go interface{} 和string的转换问题

1.遇到的问题 问题来源于,我sql模版拼接遇到的问题。 首先&#xff0c;这样是没有问题的。 var qhx interface{} "qhx"s : qhx.(string)fmt.Println(s) 但是当我在这段代码里用:1.类型断言 var sqlStr "select * from tx_user where username %s" join…

leetcode——hot1

两数之和 class Solution {public int[] twoSum(int[] nums, int target) {int[] arrs new int[2];for(int i 0; i < nums.length - 1; i){for(int j i 1; j < nums.length; j){if(nums[i] nums[j] target){arrs[0] i;arrs[1] j;break;}}}return arrs;} }

sql注入 [极客大挑战 2019]FinalSQL1

打开题目 点击1到5号的结果 1号 2号 3号 4号 5号 这里直接令传入的id6 传入id1^1^1 逻辑符号|会被检测到&#xff0c;而&感觉成了注释符&#xff0c;&之后的内容都被替换掉了。 传入id1|1 直接盲注比较慢&#xff0c;还需要利用二分法来编写脚本 这里利用到大佬的脚…

C++——基础语法(3):内联函数、auto关键字、基于范围的for循环、空指针nullptr

6. 内联函数 在函数前加入inline修饰即可将函数变为内联函数。所谓内联函数&#xff0c;就是在编译时C编译器会将函数体在调用内联函数的地方展开&#xff0c;从而省去了调用函数的栈帧开销&#xff0c;提高程序运行效率。 inline int Add(int a, int b) {return a b; } int …

【电子书】设计制作

资料 wx&#xff1a;1945423050 整理了一些互联网电子书&#xff0c;推荐给大家 设计制作 3D打印建模&#xff1a;Autodesk 123D Design详解与实战 第2版.epub3ds Max 2016中文版标准教程.epubAfter Effects CS 6影视特效与栏目包装实战全攻略(第2版&#xff09;.epubAfter E…

欧瑞康真空TTR91莱宝ITR90真空计KA09420GA09使用说明接线图

欧瑞康真空TTR91莱宝ITR90真空计KA09420GA09使用说明接线图

RocketMQ快速实战以及集群架构原理详解

RocketMQ快速实战以及集群架构原理详解 组成部分 启动Rocket服务之前要先启动NameServer NameServer 提供轻量级Broker路由服务&#xff0c;主要是提供服务注册 Broker 实际处理消息存储、转发等服务的核心组件 Producer 消息生产者集群&#xff0c;通常为业务系统中的一个功…

操作系统访问控制机制

使用访问控制技术&#xff0c;可以设置用户对系统资源的访问权限&#xff0c;即限定用户只能访问允许访问的资源。访问控制还可以通过设置文件的属性&#xff0c;来保护文件只能被读而不能被修改&#xff0c;或只允许核准的用户对其进行修改等。 1.1 保护域 把一个进程能对某…

11-pytorch-使用自己的数据集测试

b站小土堆pytorch教程学习笔记 import torch import torchvision from PIL import Image from torch import nnimg_path ../imgs/dog.png imageImage.open(img_path) print(image) # imageimage.convert(RGB)transformtorchvision.transforms.Compose([torchvision.transforms.…