【海量数据算法】如何判断一个数是否在40亿个整数中

如何判断一个数是否在40亿个整数中

2.1《编程珠玑》给出的方案

我们把40亿个数中的每一个用32位的二进制来表示,假设这40亿个数开始放在一个文件中。

然后将这40亿个数分成两类:1.最高位为0;2.最高位为1。

并将这两类分别写入到两个文件中,其中一个文件中数的个数

与要查找的数的最高位比较并接着进入相应的文件再查找

再然后把这个文件为又分成两类:1.次最高位为0;2.次最高位为1。

并将这两类分别写入到两个文件中,其中一个文件中数的个数

以此类推,就可以找到了,而且时间复杂度为O(logn)。

此方案不是本文要讲的重点,只是把思路放在这边供大家参考。

2.2 位图法

我们之所以无法将40亿个数字直接读取到内存去进行处理,是因为40亿个unsigned int的整数大约要占15G内存,正常情况下,没有这么大的内存,也不可能这样做。

这种情况是因为一个整数占用了4个字节(Byte),而在本题中,我们其实只关心某个数字是否存在,在这种情况下,我们可以通过位图法来解决该问题。

位图法思想:对于40亿个unsigned int的整数,每个数字用1个二进制数(一个二进制数占用1Bit,1Byte = 8Bit)来表示该数字是否存在,0为不存在,1为存在。从低位开始数:

第1个二进制数表示整数0是否存在,
第2个二进制数表示整数1是否存在,
第3个二进制数表示整数2是否存在,
依次类推… …

第4294967296个二进制数用于表示整数4294967295是否存在。

unsigned int在32&64位编译器的范围为0~4294967295,4294967296个二进制数大约占用512M内存,是一个可以接受的范围。

题目:我们读取了3个整数:1、2、4,要判断整数3是否被读取过了。

解答过程:
1)对于40亿个unsigned int的整数,我们可以定义4294967296个二进制数,并且全部初始化值为0。
2)读取整数1:将第2个二进制数赋值为1,表示整数1是存在的,此时值为:10(高位还有4294967294个0,为了方便阅读不写出来,下文同)
3)读取整数2:将第3个二进制数赋值为1,表示整数2是存在的,此时值为:110
4)读取整数4:将第5个二进制数赋值为1,表示整数4是存在的,此时值为:1 0110
5)判断整数3是否被读取过,因为第1个二进制数表示整数0是否存在,因此整数3则通过第4个二进制数表示,此时的值为:1 0110,第4个二进制数为0,所以得出结论:整数3没有被读取过。

由于Java中无法直接操作二进制数,因此我们可以通过int来实现。1个二进制数占用1Bit;1个int占用4Byte,也就是32Bit。因此,我们可以使用1个int来表示32个二进制数。
所以,我们有以下思路:
第1个int表示:整数0~31是否存在,
第2个int表示:整数32~63是否存在,
第3个int表示:整数64~95是否存在,依此类推。
因此,我们最终可以使用一个int数组来表示4294967296个二进制数,通过数组的下标来指示第几个int。
代码如下

/**
 * 位图法
 *
 * @author joonwhee
 * @date 2019/1/22
 */
public class BitMap {
    /**
     * 位图提供的最大长度,
     * 比如unsigned int的最大值为4294967295, 则需要的length为4294967296
     */
    private long length;

    /**
     * 位图桶
     */
    private static int[] bitmapBucket;

    /**
     * int用来表示32位二进制数,
     * BIT_VALUE[0]表示第1个二进制数存在、
     * BIT_VALUE[1]表示第2个二进制数存在,以此类推
     *
     * BIT_VALUE[0] = 00000000 00000000 00000000 00000001
     * BIT_VALUE[1] = 00000000 00000000 00000000 00000010
     * BIT_VALUE[2] = 00000000 00000000 00000000 00000100
     * ...

     * BIT_VALUE[31] = 10000000 00000000 00000000 00000000
     */
    private static final int[] BIT_VALUE = {
            0x00000001, 0x00000002, 0x00000004, 0x00000008,
            0x00000010, 0x00000020, 0x00000040, 0x00000080,
            0x00000100, 0x00000200, 0x00000400, 0x00000800,
            0x00001000, 0x00002000, 0x00004000, 0x00008000,
            0x00010000, 0x00020000, 0x00040000, 0x00080000,
            0x00100000, 0x00200000, 0x00400000, 0x00800000,
            0x01000000, 0x02000000, 0x04000000, 0x08000000,
            0x10000000, 0x20000000, 0x40000000, 0x80000000};

    /**
     * length为1 - 32: 需要1个桶
     * length为33 - 64: 需要2个桶
     * ...

     * 以此类推
     *
     * @param length
     */
    public BitMap(long length) {
        this.length = length;
        // 根据长度算出,所需位图桶个数
        bitmapBucket = new int[(int) (length >> 5) + ((length & 31) > 0 ? 1 : 0)];
    }

    /**
     * 查找number是否存在于位图桶中
     *
     * @param number 要查询的数字
     * @return true: number在位图桶中, false: number不在位图桶中
     */
    public boolean getBit(long number) {
        if (number < 0 || number > length) {
            throw new IllegalArgumentException("非法参数");
        }
        // 计算该number在哪个桶
        int belowIndex = (int) (number >> 5);
        // 求出该number在桶里的下标,(求出该值在32位中的哪一位, 下标0 - 31)
        int offset = (int) (number & 31);
        // 拿到该桶的值
        int currentValue = bitmapBucket[belowIndex];
        // 计算该number是否存在
        return ((currentValue & BIT_VALUE[offset])) == 0 ? false : true;
    }

    /**
     * 将number在位图桶中标记为存在
     *
     * @param number 要标记的数字
     */
    public void setBit(long number) {
        // 合法性校验
        if (number < 0 || number >= length) {
            throw new IllegalArgumentException("非法参数");
        }
        // 计算该number在哪个桶
        int belowIndex = (int) (number >> 5);
        // 求出该number在桶里的下标,(求出该值在32位中的哪一位, 下标0 - 31)
        int offset = (int) (number & 31);
        // 拿到该桶的当前值
        int currentValue = bitmapBucket[belowIndex];
        // 将number在桶里标记
        bitmapBucket[belowIndex] = currentValue | BIT_VALUE[offset];
    }

    public static void main(String[] args) {
        BitMap bitMap = new BitMap(4294967296L);
        bitMap.setBit(4294967295L);
        System.out.println(bitMap.getBit(4294967295L));
        System.out.println(bitMap.getBit(4294967294L));
    }
}

了解了思路后,代码就比较简单了。

1)通过构造函数传的length值,构建一个足够大的int数组bitmapBucket,对于题目的unsigned int的整数,length为4294967296刚好够用。
2)将40亿个给定的数通过setBit方法,将对应的位置的二进制数标记为1(1代表存在)。
3)通过getBit方法判断给定的number是否存在于之前的40亿个数中。

setBit例子:例如我们要将整数32标记为存在。

1)首先计算出整数32所在的位图桶下标为1,也就是bitmapBucket[1]。
2)接着计算出整数32在bitmapBucket[1]桶中的下标0(下标0代表该桶的第1个二进制数)。
3)拿到bitmapBucket[1]当前值currentValue。
4)最后我们要将bitmapBucket[1]桶中第1个二进制数标记为1,并且不影响之前已经标记的二进制数,因此将currentValue与0000 0000 0000 0000 0000 0000 0000 0001进行’或’运算即可。而对于每一个二进制数,我们通过BIT_VALUE来表示,这边的0000 0000 0000 0000 0000 0000 0000 0001,可以通过BIT_VALUE[0]来表示。

Original: https://www.cnblogs.com/ciel717/p/16673920.html
Author: 夏尔_717
Title: 【海量数据算法】如何判断一个数是否在40亿个整数中

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/598926/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Redis-切片集群

    扩容的思路 纵向扩展 scale up: 一台8G的变成一台24G的 👍 简单 👎 受硬件条件的限制 👎 单机容量大对性能的影响,如Redis的fork操作耗时是和内存数据量正相关…

    数据库 2023年6月11日
    072
  • Java面试题(三)–虚拟机

    1 内存结构 1、简述一下JVM的内存结构?(高频) JVM在执行Java程序时,会把它管理的内存划分为若干个的区域,每个区域都有自己的用途和创建销毁时间。如下图所示,可以分为两大…

    数据库 2023年6月16日
    077
  • 启程——博客之路

    憋了这么久还是忍不住开始写自己的博客了。。。之前总是看别人的博客,伸手党一个,但是时间久了,总有一些自己想说的话,想想分享一些技术、经验,也能记录自己的学习历程,毕竟编程这条路还很…

    数据库 2023年6月9日
    077
  • 部署tomcat

    tomcat tomcat 一、tomcat是什么 二、tomcat部署 1.实现访问java测试网页 2.能够成功登录到tomcat首页中的host manager、server…

    数据库 2023年6月14日
    050
  • 通过.frm和.idb文件恢复mysql数据库

    本文对该文章进行参考,地址https://baijiahao.baidu.com/s?id=1675966756498698574&wfr=spider&for=p…

    数据库 2023年5月24日
    0101
  • vim+vundle配置

    Linux环境下写代码虽然没有IDE,但通过给vim配置几个插件也足够好用。一般常用的插件主要包括几类,查找文件,查找符号的定义或者声明(函数,变量等)以及自动补全功能。一般流程都…

    数据库 2023年6月9日
    092
  • MySQL完整版详解

    一、数据库的操作 1.创建数据库 若在可视化软件上创建数据库,参考如下图 如果要创建的数据库不存在,则创建成功 create database if not exists west…

    数据库 2023年6月16日
    064
  • eclipse连接MySQL 8.0.29.0

    推荐文章: eclipse导入JDBC MySQL详细安装 菜鸟java MySQL连接教程 步骤: 找到MySQL的连接Java的jar文件; 如下图: 在eclipse项目文件…

    数据库 2023年5月24日
    0110
  • 优化 JS 程序的一个小方法

    就像在学习之前先要识字,我想在介绍优化 JavaScript 代码之前,先介绍一下自己对编程语言的理解。故事要从一只叫做 Theseus 的机械鼠和其发明人克劳德-香农(Claud…

    数据库 2023年6月14日
    091
  • 浅谈GTID及简单测试

    今天简单介绍一下GTID,并有部分相关实验。 GTID相信大家都不陌生,GTID的英文全称为Global Transaction Identifier,在MySQL主从架构中应用广…

    数据库 2023年6月16日
    070
  • mysql解压版简洁式本地配置方式

    1. 设置全局变量 解压mysql压缩包到指定位置, 然后配置全局变量, 在 path 中添加全局变量, 值为 mysql 根目录下 bin 目录路径, 比如: D:\code_s…

    数据库 2023年5月24日
    0130
  • 用户后台管理

    User Management 这是通过SpringBoot完成的用户后台管理系统 一些解释说明也在代码里面, 源码及资源 会放在文末哦!!! – 这是效果图 大概就这…

    数据库 2023年6月16日
    0114
  • MySQL扩展

    1、行转列 源数据: 目标数据: &#x6570;&#x636E;&#x51C6;&#x5907; — 建表插入数据 drop table if …

    数据库 2023年5月24日
    071
  • Mysql的读写分离中间件该怎么写?听我来说。

    网上有很多读写分离的中间件,像proxy,mycat等等,由于本人比较懒,懒得去读各种开源的东西,还是想造轮子来得快。 1、了解mysql通信协议,其中有分4.1之前和4.1版本的…

    数据库 2023年5月24日
    0123
  • asyncio 异步编程

    首先了解一下协程,协程的本质就是一条线程,多个任务在一条线程上来回切换,协程的所有切换都是基于用户,只有在用户级别才能感知到的 IO 才会用协程模块来规避,在 python 中主要…

    数据库 2023年6月9日
    063
  • Linux Shell 自动交互功能

    需求背景: 近日,在安装某软件过程,发现在安装过程需要输入一些信息才能继续下一步操作,在机器数量较少情况下,我们可以单台登录上去完成安装操作,但当机器数量超过一定时,如果再手动登录…

    数据库 2023年6月14日
    093
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球