<BitMap>大名鼎鼎的bitmap算法
BitMap
抛砖引玉
首先,我们思考一个问题:如何在3亿个整数(0~2亿)中判断某一个数是否存在?现在只有一台机器,内存只有500M
这个问题像不像我们之前提到过的一个在0-10个数中,判断某一个数是否存在的问题呢?当时我们采取的做法是,建立一个长度是11的数组,下标从0开始,如果1存在则data[1] = 1,数字作为数组的下标,若该数字存在则在data[数字] = 1,将其赋值为1。那么我们这个是否可以这么做呢?
明显不行。为什么呢?因为我们如果判断2亿个数字是否存在,建立一个2亿长度的数组,疯了么?想想就知道不可能。那么我们应该怎么做呢?
这时候我们就需要变换我们的思路了,一个int类型整型,我们占用4个字节,一个字节我们占用8位:1 byte = 8 bit4 byte = 32 bit那么,我们如果将这2亿个数据不按照int的形式存储,而是按照位的形式存储,我们原来存储一个数字(4个字节)现在不就是可以存储32个数字(4个字节 = 32位)了么。秒啊~
什么是BitMap
位图(BitMap),又称作栅格图或者点阵图,是使用像素阵列来表示的图像---------来自百度百科。
明显上面说的什么,我们也不清楚,反正很高深了就是,其实我觉得简单理解就是我们用位(bit)来操作数据,以此来操作大数据量
至于我们为什么用位来操作呢?因为计算机最小的单位就是bit(位)
类型的基础讲解
常用的数据类型和位的转换
1 byte = 8 bit
1 int = 4 byte = 32 bit
1 float = 4 byte = 32 bit
1 long = 8 byte = 64 bit
1 char = 2 byte = 16 bit
我们这里就不做详细的解释,只要记住:1 byte = 8 bit,即1字节 = 8位
常用的位操作运算符
我们这里讲述0. 常用的6种常用的位运算符(1)按位或(2)按位与(3)按位异或(4)求反(5)左移运算(6)右移运算
按位或运算
按位或运算符,记做“|”,是一个双目运算符。
简单来说就是二进制的位中只要有一个是1,其计算结果就是1,否则就是0
举例说明:
11 | 7
11 0000 0000 0000 0000 0000 0000 0000 1011 | 7 0000 0000 0000 0000 0000 0000 0000 0111 结果 0000 0000 0000 0000 0000 0000 0000 1111 11 | 7 = 1011 | 0111 = 1111 = 15
按位与运算
按位与运算符,记做“&”,是一个双目运算符。
简单来说就是二进制的位中只有都是1,才会是1,否则就是0
举例说明:11 & 7
11 0000 0000 0000 0000 0000 0000 0000 1011 & 7 0000 0000 0000 0000 0000 0000 0000 0111 结果 0000 0000 0000 0000 0000 0000 0000 0011 11 &7 = 1011 | 0111 = 0011 = 3
按位异或运算
按位异或运算符,记做“^”,是一个双目运算符。
简单来说就是二进制的位中只要两个数字不一样就是1,否则就是0
举例说明:
11 ^ 7
11 0000 0000 0000 0000 0000 0000 0000 1011 ^ 7 0000 0000 0000 0000 0000 0000 0000 0111 结果 0000 0000 0000 0000 0000 0000 0000 1100 11 &7 = 1011 | 0111 = 1100 = 12
按位异或运算
public class Main1 { public static void main(String[] args) {
System.out.println("11 | 7 的结果是 : "+(11|7));
System.out.println("11 & 7 的结果是 :"+(11&7));
System.out.println("11 ^ 7 的结果是 :"+(11^7));
}
}
//----------------------------输出如下------------------------------
11 | 7 的结果是 : 15
11 & 7 的结果是 :3
11 ^ 7 的结果是 :12
按位求反运算
按位求反运算符,记做“~”,是一个单目运算符。
简单来说就是二进制的位中,原来是1,结果就是0,原来是0,结果就是1
左移运算
左移运算符“<<”是一个双目运算符,其功能就是把"<<"左边的运算数的各个二进位全部左移若干位。
举例说明:5 << 4
5 0000 0000 0000 0000 0000 0000 0000 0101 <<4 结果 0000 0000 0000 0000 0000 0000 0101 000 5 << 4 = 0101 << 4 = 0101 0000 = 5* 2^4 = 5*16 =80
右移运算
右移运算符 ">>" 是一个双目运算符,其功能就是把">>"左边的运算数的各个二进位全部右移若干位。
举例说明:16 >> 3
16 0000 0000 0000 0000 0000 0000 0001 0000 >>3 结果 0000 0000 0000 0000 0000 0000 0000 0010 16 >> 3 = 0001 0000 >> 3 = 0010 = 16 / 2^3 = 16/8 =2
如何巧妙的运用
a >> x
这个就可以记做是:a / 2 ^ x比如说:8 >> 2 = 8 / 2 ^ 2 = 8 / 4 = 2
a << x
这个就可以记做是: a * 2 ^ x比如说: 8 << 2 = 8 * 2 ^ 2 = 8 * 4 = 32
a % 2^n
这个就可以记做是:a & 2^n-1比如说:7 % 4 = 7 & (4-1) = 3
实际问题分析
问题描述
现在我们假设我们有64个数字,最大的数字就是64,那么我们用Bitmap的思想应该怎么解决呢?
分析
首先我们这里用int的数组来进行存储,一个int是4个字节,1个字节是8位,因此一个int型的数字可以存32位
我们这里最大的数是64
数组 | 存储位数 | 能存多少 |
---|---|---|
data[0] | 0-31 | 32位 |
data[1] | 32-63 | 32位 |
data[2] | 64-95 | 32位 |
由此可以推到出来:我们存取n个数字,其中最大的数字是MAX,则需要data[MAX/32 + 1]长度的数组,这里的32是因为1 int = 4 字节 = 32位
假设我们需要判断64是否在列表中(以int数组存储),我们就应该这样来计算
第一步我们判断声明多大的int型的数组
因为我们最大的数是64,所以根据我们的公式:data[MAX/32 + 1] ,由此可以计算出,我们声明的数组长度是data[64/32 + 1] = data[3],也就是说我们应该声明数组长度是3,即data[0] data[1] data[2]
先定位到数组,判断是第几个数组中存储着
因为我们一个数组长度是1个int,就是32位,查询的数字是64,所以64 / 32 = 2,因此定位到了data[2]
这里除以32是因为,我们以int数组为例,1 int = 32 bit
再定位这个元素是数组中的第几位
同理,我们一个数组的长度是一个int,也就是4个字节,32位,查询的数字是64,64 % 32 = 0,因此定位到了我们是存在data[2]数组中的第0位
这里取余32是因为,我们是以int数组为例,1 int = 32 bit
此时我们只需要判断data[2]数组中的第0位是否为1,为1表示已经存在,否则就是不存在。
这就是BitMap的算法的核心思想
公式小结
我们以int数组为例,一个int占用4个字节,也就是32位,存储数据的最大值是MAX(比如存1-2000,最大值就是2000)
判断总共需要多大的int数组 :
MAX/32 + 1
判断当前这个数字n在第几个数组中 :
n / 32
判断当前这个数字n在数组中的第几位上 :
n % 32
我们上面的32就是int占用的位数,可以换成其他的类型,如果用byte数组,则将32换成8,因为1个byte是一个字节,是8位。
计算存储空间
假设我们现在要存储2亿个数字,如果直接用int数组来存,一个int是一个数字,则需要:2亿 * 4字节 / 1024 / 1024 = 762M
假设我们现在用BitMap的思想存储,也是使用int数组,只不过一个int存储32个数组,则需要:2亿 / 32 + 1(需要开的int数组) * 4字节 / 1024 / 1024 = 23M
看看,是不是空间就是这么省下来了。
实战举例
题目
假设我们有序列 2 9 33 12 11 65 14 , 我们开一个int类型的数组,将其存储进去,然后判断其是否存在,并可以实现某一个数字的删除,并用位运算符实现。
分析
一: 首先我们通过上面的三个公式已经可以很快的知道数组定义多大,在哪个数组中放值(哪一个数组,数组中的哪一位),但是这里我们的要求是使用位运算符,这时候结合我们最开始讲的位运算符将其进行简单的转换
判断数组定义多大
a / 2^n = a >> ndata[MAX/32 + 1] = data[MAX / 2 ^ 5 + 1] = data[MAX >> 5 + 1]
数字n存在哪一个数组中
a / 2^n = a >> ndata[n/32] = data[n / 2 ^ 5] = data[n >> 5]
数字n在数组的哪一位
a % 2^n = a & 2^n - 1data[n%32] = data[n % 2 ^ 5] = data[n & (2 ^ 5 -1)] = data[n & 31]
二: 其次我们在存入数组中需要将数组的第loc位由0变为1
将下标为loc的位由0变为1,可以把1左移loc位,然后使用或操作(只要有1个为1就是1)
所以新增元素公式就是: data[X] = data[X] | (1 << loc),loc为当前数组的哪一位,data[X]就是当前的数组
三: 下一个问题就是我们需要查找一个数字是否已经存在
这时候我们就可以考虑&运算符,即都是1才是1,否则就是0判断下标loc的位是否已经存在,可以把1左移loc位,然后做与操作(都为1才是1,否则是0)
如下图,判断loc位上是否存在:(下图表示已经存在)
如下图,判断loc位上是否存在:(下图表示不存在)
所以查找元素公式就是:0 == data[X] & 1 << loc,loc为当前数组的哪一位 ,这时候用0判断是因为我们除了移位过去的那位上的数字是1,其他位上的数字都是0,所以如果最后结果是0,表示就是不存在,否则就是存在
四: 最后一个问题就是我们要删除一个数字呢?就是将这个数字由1变为0(这里我们假设删除的数字是肯定存在的,不允许做不存在就删除)
这时候考虑我们异或操作:相同则为0,不同则为1,那如果我当前位上存在则肯定是1,如果我拿一个当前位是1的数和其做异或操作不就可以了假设我们需要删除下标是lco上的数我们的异或操作是相同为0,不同为1,所以即使数组原来位置上有1,我们也不用害怕,因为原来位子上是1,但是我们1进行左移loc后,除了loc上的数是1,其他位上的数肯定是0,参考上面的图解
所以删除元素公式:data[X] = data[X] ^ (1 << loc),loc为当前数组的哪一位
代码实现
package com.demo.bitsmap;
public class Main2BitsMap { private int[] vals; public Main2BitsMap(int size) {
this.vals = new int [size];
} public static void main(String[] args) {
int [] t = {1,2,3,4,5,33,34,45,77,108};
Main2BitsMap map = new Main2BitsMap((108>>5)+1);
for (int i :t) {
map.add(i);
}
map.print();
map.printBirany();
System.out.println("添加数据109");
map.add(109);
System.out.println("是否包含 77 这个数据 ? " +map.find(77));
System.out.println("是否包含 109 这个数据 ? " +map.find(109));
System.out.println("删除数据77");
map.delete(77);
System.out.println("是否包含 77 这个数据 ? " +map.find(77));
} public void add(int t) { int index = t>>5; int loc = t&31; vals[index] = vals[index] | 1 << loc;
} public boolean find(int t) {
int index = t>>5; int loc = t&31; int result = (vals[index]>>loc ) &1; return result ==0 ?false:true;
}
public boolean delete(int t ) {
int index = t>>5; int loc = t&31; if(!find(t)) {
return false;
}
vals[index] = vals[index] ^ (1<<loc); return true; } public void print() {
for (int i : vals) {
System.out.print(i+" ");
}
} public void printBirany() {
int index =0;
System.out.println();
for (int i : vals) {
for (int j = 31; j >=0; j--) {
System.out.print((i>>j)&1 );
index++;
if(index==5) {
System.out.print(" ");
index=0;
} }
index =0;
System.out.println(); }
}
}
结果显示
62 8198 8192 4096
00000 00000 00000 00000 00000 01111 10
00000 00000 00000 00010 00000 00001 10
00000 00000 00000 00010 00000 00000 00
00000 00000 00000 00001 00000 00000 00
添加数据109
是否包含 77 这个数据 ? true
是否包含 109 这个数据 ? true
删除数据77
是否包含 77 这个数据 ? false
总结
BitMap的时间复杂度是O(1)
BitMap的优点(1)可以解决数据判重的问题(2)可以对没有重复的数据进行排序(3)存储巧妙,节约空间,效率高
BitMap的缺点(1)数据不允许重复。因为只有0和1没有其他了(2)数据量少的时候相对于普通的hash没有优势(因为存储数据量小,我们直接用正常的map或者其他数据结构存储就行)
(3)无法处理字符串(4)无法解决Hash冲突(因此我们的前提条件就是数据不重复)
<BitMap>大名鼎鼎的bitmap算法的更多相关文章
- BitmapUtil【缩放bitmap以及将bitmap保存成图片到SD卡中】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 用于缩放bitmap以及将bitmap保存成图片到SD卡中 效果图 代码分析 bitmapZoomByHeight(Bitmap s ...
- 关于bitmap recycle trying to use a recycled bitmap android.graphics.Bitmap
在开发中,一直使用4.0以上手机作为測试机所以一直没有出现这个问题,今天换了2.3版本号的手机.出现了这个错误: trying to use a recycled bitmap android.gra ...
- Canvas: trying to use a recycled bitmap android.graphics.Bitmap@XXX
近期在做和图片相关显示的出现了一个问题,整理一下思路.分享出来给大家參考一下: Exception Type:java.lang.RuntimeException java.lang.RuntimeE ...
- 浅谈Android下的Bitmap之大Bitmap加载
引言 我们常常提到的“Android程序优化”,通常指的是性能和内存的优化,即:更快的响应速度,更低的内存占用.Android程序的性能和内存问题,大部分都和图片紧密相关,而图片的加载在很多情况下很用 ...
- java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@412d7230
近期遇到了如标题这种错误,再次记录解决方法.本文參考帖子: http://bbs.csdn.net/topics/390196217 出现此bug的原因是在内存回收上.里面用Bitamp的代码为: t ...
- Flash 矢量图和位图性能对比 导出为位图/缓存为位图 export as bitmap / cache as bitmap
大家都知道Flash处理矢量图比位图要慢,而具体的性能上对比也有不少的前人已经做过.http://bbs.9ria.com/forum.php?mod=viewthread&tid=2282 ...
- c# bitmap和new bitmap(bitmap)及在System.Drawing.Image.get_RawFormat()报错“参数无效”
问题情境: 给picturebox赋image属性,我用一下代码,出错: Bitmap theBitmap = convertCameraData.display(rawDataArray, heig ...
- Android java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@412d7230
近期遇到了如标题这种错误,再次记录解决方法.本文參考帖子: http://bbs.csdn.net/topics/390196217 出现此bug的原因是在内存回收上.里面用Bitamp的代码为: t ...
- BitMap算法详解
所谓的BitMap就是用一个bit位来标记某个元素所对应的value,而key即是该元素,由于BitMap使用了bit位来存储数据,因此可以大大节省存储空间. 基本思想: 这此我用一个简单的例子来详细 ...
随机推荐
- [运维] 请求 nginx 出现 502 Bad Gateway 的解决方案!
环境: 云服务器镜像 Linux CentOS 7.6 已经安装并成功配置 SSL 的 nginx 1.16.1 成功安装并且可以正常运行的 apache-tomcat-9.0.26 遇到的问题: 在 ...
- python知识点总结以及15道题的解析
先看知识点总结 一.序列操作符x in s 如果x是列表s的元素,返回True,否则Falses + t 连接两个序列s和ts*n或者n*s 将序列s复制n次s[i] 返回s中第i元素s[i:j]或s ...
- RuntimeError: cuda runtime error (10) : invalid device ordinal
This is caused by the unmatching of gpu device number when loading a saved model. torch.load('my_fil ...
- selenium webdriver 实现百度贴吧自动签到
public static void main(String[] args) { //TestUtils.killProcess("javaw.exe"); TestUtils.k ...
- get方法和load方法的区别
get方法的特点 get方法采用的是立即检索策略(查询):执行到这行的时候,马上发送SQL查询 get方法查询后返回的是真实对象的本身 load方法的特点 load方法采用的是延 ...
- Lightroom中几个重要名词术语的解释
Lightroom是照片管理.处理.发布的综合性智能软件,里面有几个重要的专有技术名词,通过我的理解做一个总结: 一.目录(Catalog) 就是Lightroom的数据库,会把用户的照片的信息.照片 ...
- gcc/g++/make/cmake/makefile/cmakelists的恩恩怨怨
以前在windows下用VS写代码,不管有多少个文件夹,有多少个文件,写完以后只需要一键就什么都搞定了.但是当移步linux下时,除非你使用图形界面,并且使用Qt creater这类的IDE时,才可以 ...
- 寒假所做事情日志-Office重新激活
日期:2020.01.18 博客期:127 星期六 好吧,今天出了一趟远门,将近傍晚才回来.任务目标其实相当于什么也没做,但回来发现Office居然过期了,老师给的那些文件居然无法修改了,于是乎剩下的 ...
- VUE框架下安装自带http协议
在控制台CMD 中输入 npm install vue-resource --save-dev
- English-Phonics
English-Phonics 1. 音节 1.1 字组 1.2 音节概述及分类 1.3 音节的划分 2. 元音字组的自然发音 2.1 元音字母 2.2 元音字母的长音 2.3 元音字母+r 2.4 ...