高效查找秘密武器一:位图

news2025/1/5 20:50:44

有这样的一个问题:

给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数 中。

那么我们一般会想到这样做的

1.遍历,时间复杂度O(n)

2.排序(N*logN),然后二分查找

那么此时此刻,我们再次细想一下,这些方法一般放在内存中进行的,那么40亿个不重复的无符号整数,有多大容量呢?

首先,10亿个字节大概是1个G

那么10亿个整数大约是4个G

然后呢40亿个整数大约是16个G

所以,如若这么大容量放到电脑内存上跑,恰好此时电脑内存也是16个G,那么不是刚刚好?

其实不然,电脑上不单单只是跑这个程序,后面还有很多,比如微信啊,爱奇艺什么的,所以一般来说,这么大容量放到16G的电脑内存是不太现实的。

那么还有什么解决办法呢?

那就请出今天的主角之一:位图

位图

那么什么又是位图呢?
这里说的位图指的是数据结构当中的,不是那个指图像那个位图哦!

位图:所谓的位图,就是用每一位来存放某种状态的。

适用于海量数据,整数,数据无重复的场景。通常用来判定某个数据存不存在的。

那么举个例子来看看

我这里,假设有了一个字节数组,然后有三个字节,那么一个字节呢,又对应8个比特位

一个比特位呢,又可以代表0/1两种状态。

所以数组arr中,每个数字可以通过某个算式来确定某个位置,然后将某个位置的比特位设置为1,说明,此数字存在。

比如,就11来说

11/8=1

那么我们就放到这个1下标

11%8=3

那么我们就在数组1下标下的第三个比特位设置为1,

然后可以说明数字是存在过的

这里值得注意下,为什么比特位标明的数字从右到左呢?

这里是为了方便,自己也是可以左到右的。

那么回过头细想一下,一个字节有8个比特位,且8个比特位都可以表示状态。

那么10亿个字节大概是0.9G,接近1G

10亿个比特位大概是119兆,看作128兆

那么40亿个比特位,那么就是可以看作512兆。

所以内存量一下子大大缩小了。

那么你看这个位图是不是很强大呢!

ok,那么接下来讲讲如何实现它吧。

由上述刚刚的例子可以看得出,底层还是一个数组来的,而且还是一个字节数组。

然后呢,我们创建它的时候,默认给定一个容量。

但是,有时候,需要用户指定它需要范围是多大,那么可以这样写。

  //位图底层还是一个数组
    public byte [] elem;
    //记录存放数据多少
    public int Usize;
    public MyBitMap(){
        //默认给定的数组大小是1个字节
        this.elem=new byte[1];
    }
    public MyBitMap(int n){
        this.elem=new byte[n/8+1];
    }

在给定参数的构造方法中,假设我们给定数字范围是10个,那么10/8=1,但是还余下两个,9 10

所以我们直接给多一个字节是可以的。set

那么接着讲讲set吧

set:

思路:

当然这是核心思路,然后代码实现方面还是需要考虑一些细节

比如,我们set方法,参数传入一个数字的话,如若数字给到的是<0的数字,那么此时是不符合我们位图的

再者如果是给定的数字,如若在找数组下标时超出数组范围,这时候也是需要处理的,比如扩容操作。

那么代码如下:

 public void set(int val){
        if(val<0){
            //需要抛出异常,因为会导致数组越界
            throw new IndexOutOfBoundsException();
        }
        //放到字节数组哪个位置
        int arrIndex=val/8;
        if(arrIndex>elem.length){
            //超出数组范围,进行扩容
            elem= Arrays.copyOf(elem,arrIndex+1);
        }

        //放到字节数组位置的哪个比特位
        int bitIndex=val%8;
        elem[arrIndex] |=(1<<bitIndex);
        Usize++;
    }

这个找哪个比特位进行修改,其实最开始时讲过了

即val/8=字节数组哪个下标,

val%8=下标内哪个比特位。

所以这里我们就可以找到,哪个字节哪个比特位要进行修改状态,比如举的例子,设置5这个数字

5/8=0,即在数组0下标

5%8=5,即在比特位第六个位置

get:

这个get方法是返回这个数字是否是存在过的。

所以呢,我们还得是找到这个数字在哪先,然后判断下这个数字的比特位是否是1

举例:

代码:
 

public boolean get(int val){

        if(val<0){
            throw new IndexOutOfBoundsException();
        }
        int arrIndex=val/8;
        int bitIndex=val%8;
        if((elem[arrIndex]&(1<<bitIndex))!=0){
            return true;
        }
        return false;
    }

reset:

reset方法呢,是把传入参数,对应的比特重新置为0

举例说明:

代码:

 public void reset(int val){
        if(val<0){
            throw new IndexOutOfBoundsException();
        }
        int arrIndex=val/8;
        int bitIndex=val%8;
        elem[arrIndex] &= ~(1<<bitIndex);
        Usize--;
    }
    public int getSize(){
        return Usize;
    }

值得注意的是,进行set的时候,Usize++了

所以写一个方法方法来返回当前设置的个数。

那么位图一些基本方法写完了,这里还差最后一个方法:排序

排序

其实我们位图是可以排序的

拿这里来说:

只要我们从右边开始,遍历当前比特位是1的就可以输出当前的值

那么判断当前位是不是为1,是get方法里的思路是一致的,用上&

那么如何输出当前的数字呢?

比如我要输出的是5,那么此时我们可以这样val=i*8+j

当前的i=0,因为我们输出的数字是5,在字节数组的0下标,当j从下标开始遍历,j=5时,就可以输出这个数字5了

代码:
 

 public static void main(String[] args) {
        int [] arr=new int[]{3,10,9,5,7,24,18};
        MyBitMap myBitMap=new MyBitMap(24);
        //先把数据放进字节数组中
        for(int i=0;i<arr.length;i++){
            myBitMap.set(arr[i]);
        }
        //排序
        for(int i=0;i< myBitMap.elem.length;i++){
            for(int j=0;j<8;j++){
                if((myBitMap.elem[i]&(1<<j))!=0){
                    System.out.print(i*8+j+" ");
                }
            }
        }

    }

ok,那么位图这里,小编分享的差不多了。

顺带提一句,一开始给的那道题是腾讯曾经的面试题来的。

源码:

public class MyBitMap {
    //位图底层还是一个数组
    public byte [] elem;
    //记录存放数据多少
    public int Usize;
    public MyBitMap(){
        //默认给定的数组大小是1个字节
        this.elem=new byte[1];
    }
    public MyBitMap(int n){
        this.elem=new byte[n/8+1];
    }
    /**
     * set操作,对添加进来的数据置为1,即为存在的意思
     * @param val
     */
    public void set(int val){
        if(val<0){
            //需要抛出异常,因为会导致数组越界
            throw new IndexOutOfBoundsException();
        }
        //放到字节数组哪个位置
        int arrIndex=val/8;
        if(arrIndex>elem.length){
            //超出数组范围,进行扩容
            elem= Arrays.copyOf(elem,arrIndex+1);
        }

        //放到字节数组位置的哪个比特位
        int bitIndex=val%8;
        elem[arrIndex] |=(1<<bitIndex);
        Usize++;
    }

    /**
     * get方法返回比特位是否设置过1
     * @param val
     * @return
     */
    public boolean get(int val){

        if(val<0){
            throw new IndexOutOfBoundsException();
        }
        int arrIndex=val/8;
        int bitIndex=val%8;
        if((elem[arrIndex]&(1<<bitIndex))!=0){
            return true;
        }
        return false;
    }
    public void reset(int val){
        if(val<0){
            throw new IndexOutOfBoundsException();
        }
        int arrIndex=val/8;
        int bitIndex=val%8;
        elem[arrIndex] &= ~(1<<bitIndex);
        Usize--;
    }
    public int getSize(){
        return Usize;
    }

    public static void main(String[] args) {
        int [] arr=new int[]{3,10,9,5,7,24,18};
        MyBitMap myBitMap=new MyBitMap(24);
        //先把数据放进字节数组中
        for(int i=0;i<arr.length;i++){
            myBitMap.set(arr[i]);
        }
        //排序
        for(int i=0;i< myBitMap.elem.length;i++){
            for(int j=0;j<8;j++){
                if((myBitMap.elem[i]&(1<<j))!=0){
                    System.out.print(i*8+j+" ");
                }
            }
        }

    }
}

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

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

相关文章

爬虫运行后数据如何存储?

爬虫运行后获取的数据可以存储在多种不同的存储系统中&#xff0c;具体选择取决于数据的规模、查询需求以及应用场景。以下是一些常见的数据存储方法&#xff1a; 1. 文件系统 对于小型项目或临时数据存储&#xff0c;可以直接将数据保存到本地文件中。常见的文件格式包括&…

ultralytics-YOLOv11的目标检测解析

1. Python的调用 from ultralytics import YOLO import os def detect_predict():model YOLO(../weights/yolo11n.pt)print(model)results model(../ultralytics/assets/bus.jpg)if not os.path.exists(results[0].save_dir):os.makedirs(results[0].save_dir)for result in…

PowerShell install 一键部署postgres17

postgres 前言 PostgreSQL 是一个功能强大的开源对象关系数据库系统,拥有超过 35 年的积极开发经验 这为其赢得了可靠性、功能稳健性和性能的良好声誉。 通过官方文档可以找到大量描述如何安装和使用 PostgreSQL 的信息。 开源社区提供了许多有用的地方来熟悉PostgreSQL, 了…

Elasticsearch数据迁移(快照)

1. 数据条件 一台原始es服务器&#xff08;192.168.xx.xx&#xff09;&#xff0c;数据迁移后的目标服务器&#xff08;10.2.xx.xx&#xff09;。 2台服务器所处环境&#xff1a; centos7操作系统&#xff0c; elasticsearch-7.3.0。 2. 为原始es服务器数据创建快照 修改elas…

学习threejs,使用VideoTexture实现视频Video更新纹理

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️VideoTexture 视频纹理 二、…

Next.js 实战 (二):搭建 Layouts 基础排版布局

前言 等了许久&#xff0c;Next.js 终于迎来了 v15.x 版本&#xff0c;刚好 Github 上面的旧项目重构完&#xff0c;终于可以放心大胆地去研究 Next.js了。 搭建最新项目可以参考官方文档&#xff1a;Installation 最新的 Next.js 版本&#xff0c;使用的是 React19.x 内测版…

小红薯x-s算法最新补环境教程12-06更新(下)

在上一篇文章中已经讲了如何去定位x-s生成的位置&#xff0c;本篇文章就直接开始撸代码吧 如果没看过的话可以看&#xff1a;小红薯最新x-s算法分析12-06&#xff08;x-s 56&#xff09;&#xff08;上&#xff09;-CSDN博客 1、获取加密块代码 首先来到参数生成的位置&…

Microi吾码产品深度测评:轻量级企业管理应用的全方位剖析

开源低代码平台-Microi吾码-平台简介 技术框架&#xff1a;.NET8 Redis MySql/SqlServer/Oracle Vue2/3 Element-UI/Element-Plus 平台始于2014年&#xff08;基于Avalon.js&#xff09;&#xff0c;2018年使用Vue重构&#xff0c;于2024年10月29日开源 Vue3试用地址&am…

unity打包web,发送post请求,获取地址栏参数,解决TypeError:s.replaceAll is not a function

发送post请求 public string url "http://XXXXXXXXX";// 请求数据public string postData "{\"user_id\": 1}";// Start is called before the first frame updatevoid Start(){// Post();StartCoroutine(PostRequestCoroutine(url, postData…

专业140+总分420+上海交通大学819考研经验上交电子信息与通信工程,真题,大纲,参考书。博睿泽信息通信考研论坛,信息通信考研Jenny

考研结束&#xff0c;专业819信号系统与信号处理140&#xff0c;总分420&#xff0c;终于梦圆交大&#xff0c;高考时敢都不敢想目标&#xff0c;现在已经成为现实&#xff0c;考研后劲很大&#xff0c;这一年的复习经历&#xff0c;还是历历在目&#xff0c;整理一下&#xff…

mac port 安装redis 并设置为系统服务 自定义配置方法

mac系统中&#xff0c;port 包管理工具比brew的速度快N倍&#xff0c;今天就给大家分享一下在macos系统中如何使用 port安装 redis数据库并配置为服务自动启动和自定义redis.conf配置的方法。 1. 安装redis sudo port install redis 2. 启动redis服务 sudo port load redis …

【多线程-第一天-NSThread-互斥锁和自旋锁的区别-练习-异步下载网络图片 Objective-C语言】

一、互斥锁和自旋锁的区别 1.刚刚我们看过了,互斥锁和自旋锁,下边我们来看,互斥锁和自旋锁的一个区别, 1)互斥锁:如果发现其他线程正在执行锁定代码,线程会进入休眠(就绪状态),等其他线程时间片到了,打开锁后,线程会被唤醒(执行) 它是被唤醒的,相当于什么呢,…

Ubuntu环境安装RabbitMQ

1.安装Erlang RabbitMq需要Erlang语⾔的⽀持&#xff0c;在安装rabbitMq之前需要安装erlang # 更新软件包 sudo apt-get update # 安装 erlang sudo apt-get install erlang 查看erlang版本 : erl 退出命令:halt(). 2. 安装RabbitMQ # 更新软件包 sudo apt-get update # 安装 …

基于Huffman编码的GPS定位数据无损压缩算法

目录 一、引言 二、霍夫曼编码 三、经典Huffman编码 四、适应性Huffman编码 五、GPS定位数据压缩 提示&#xff1a;文末附定位数据压缩工具和源码 一、引言 车载监控系统中&#xff0c;车载终端需要获取GPS信号&#xff08;经度、纬 度、速度、方向等&#xff09;实时上传…

智慧油客:从初识、再识OceanBase,到全栈上线

今天&#xff0c;我们邀请了智慧油客的研发总监黄普友&#xff0c;为我们讲述智慧油客与 OceanBase 初识、熟悉和结缘的故事。 智慧油客自2016年诞生以来&#xff0c;秉持新零售的思维&#xff0c;成功从过去二十年间以“以销售产品为中心”的传统思维模式&#xff0c;转向“以…

如何查看电脑的屏幕刷新率?

1、按一下键盘的 win i 键&#xff0c;打开如下界面&#xff0c;选择【系统】&#xff1a; 2、选择【屏幕】-【高级显示设置】 如下位置&#xff0c;显示屏幕的刷新率&#xff1a;60Hz 如果可以更改&#xff0c;则选择更高的刷新率&#xff0c;有助于电脑使用起来界面更加流…

数据结构——有序二叉树的删除

在上一篇博客中&#xff0c;我们介绍了有序二叉树的构建、遍历、查找。 数据结构——有序二叉树的构建&遍历&查找-CSDN博客文章浏览阅读707次&#xff0c;点赞18次&#xff0c;收藏6次。因为数据的类型决定数据在内存中的存储形式。left right示意为左右节点其类型也为…

深入理解 SQL 注入:原理、攻击流程与防御措施

深入理解 SQL 注入&#xff1a;原理、攻击流程与防御措施 在当今数字化的时代&#xff0c;数据安全已成为每个企业和开发者必须面对的重要课题。SQL 注入&#xff08;SQL Injection&#xff09;作为一种常见的网络攻击方式&#xff0c;给无数企业带来了巨大的损失。本文将深入…

android WebRtc 无法推流以及拉流有视频无声音问题

最近在开发使用WebRtc进行视频通话和语音通话&#xff0c;我使用的设备是MTK的手机&#xff0c;期间后台的技术人员几乎没法提供任何帮助&#xff0c;只有接口和测试的web端&#xff0c;有遇到不能推流。推流成功网页端有画面有声音&#xff0c;但是安卓端有画面&#xff0c;没…

微信小程序之手机归属地查询

微信小程序之手机归属地查询 需求描述 API申请和小程序设置 API申请 第一步&#xff1a;完整账号注册 我们需要来到如下网站&#xff0c;注册账号&#xff1a;万维易源 第二步&#xff1a;账号注册完成以后&#xff0c;点击右上角的控制台信息。 第三步&#xff1a;在控制…