位图算法
楼主酷爱王者,但是由于忙于业务,王者有一段时间没玩了,待再次上线的时候,TM(天美)发来了一封邮件,亲爱的召唤师,欢迎回归王者荣耀,你已有88日没有登录过游戏,这是为你精心准备的回归大礼包,礼包是一些体验卡和砖石等。but作为一名程序猿,让楼主更在意的是88这个数字的统计方式。
我们知道王者荣耀用户数很多,假设有一亿用户,如何来记录用户的登录信息,如何来查询活跃用户(如一周内登录三次以上的),最常规的做法就是建一张用户登录信息表,有用户ID,有登录时间这样的,然后用户每登录一次就往表中插入一条数据,没毛病,那么假设一天之内有1亿用户登录,那么2天表中就会有2亿数据,这里会有很严重的问题,首先表中不可能承载这么多数据量,其次就算可以装得下这么多数据,那你怎么统计这么多数据的表?效率性能如何?所以在传统数据库存储层面是不好解决这个问题。
因此,我们不妨设置用一个1bit位来标识用户的登录状态,1/0,1是代表登录,0是代表没登录,那么可以建立如下的数字模型
假设有10个用户,统计一周之内用户的登录次数,模型假如是这样的
星期一:0000011111
星期二:1001011011
星期三:1001011111
星期四:1011000001
星期五:1001011001
横着来看:就标识着星期一这天后边5个用户登录了,前5个用户没登录,星期二1,4,6,7,9,10用户登录其余没有,其余同理,清晰可见。
竖着来看:就标识这同一个人一周之内的登录情况,比如第一个人,周二三五登录了游戏,二四就没有玩,其余同理,便于统计。
这里的数字模型可以是一个字符串或者是数组,这是简体思路。
下面进入主题,位图算法,了解一下!
数据库做持久化的时候,把数据做成数字模型这种形式来存储(比如只存用户ID),若有数据就标志为1或true,若无数据标志为0或false。
比如有一数字模型{5,2,1,2} 这里最大值为5,所以数组的长度就是5,而0到5中不存0,3,4数字
所以:Array[0]=0,Array[1]=1,Array[2]=2,Array[3]=0,Array[4]=0,Array[5]=1
数组模型如下 :int[] ={0,1,2,0,0,1}
上面数中由于2有两个,所以只能用int存数组的值,不用boolean型,这样如果有多个同样的数字可以用值表示个数。如上面Array[2]=2,就表示2有2个。
又如:
假设我们有{0,6,3,4}这数组,在位图中数据结构初始化状态应该就是这样的,首先最大是6,那我们申请l大小为6的数组
通过位图算法处理后,得到的位图是这样的
这种算法的缺点在于,最大值和最小值之间不能相差太大,否则浪费申请数组的空间。(蛋士可以优化滴~)
实际应用:
1.判断一个数是否存在某数据中,假如有40亿数据,我们如何快速判断指定一个数是否存在?
申请512M的内存 512M=512*1024*1024B*8=4294967296比特(bit) 这个空间可以装40亿了
一个bit位代表一个int值
读入40亿个数,设置相应的bit位
读入要查询的数,查看相应bit位是否为1,为1表示存在,为0表示不存在
2.判断整形数组是否重复
它的做法是按照集合中最大元素max创建一个长度为max+1的新数组,然后再次扫描原数组,遇到几就给新数组的第几位置上1,如遇到 5就给新数组的第六个元素置1,这样下次再遇到5想置位时发现新数组的第六个元素已经是1了,这说明这次的数据肯定和以前的数据存在着重复。它的运算次数最坏的情况为2N。如果已知数组的最大值即能事先给新数组定长的话效率还能提高一倍。
3.给数组排序
首先遍历数组,得到数组的最大最小值,然后根据这个最大最小值来缩小bitmap的范围。这里需要注意对于int的负数,都要转化,而且取位的时候,数字要减去最小值。
给出JAVA代码
public class WeiTu { public static int[] bitmapSort(int[] arr) { // 找出数组中最值 int max = arr[0]; int min = max; for (int i : arr) { if (max < i) { max = i; } if (min > i) { min = i; } } //初始化位图数组大小 int temp=0;//用于解决数组有负数的情况 int[] newArr=null; if(min<0){ temp=0-min; newArr = new int[max - min + 1]; }else{ newArr = new int[max+1]; min=0; } //构建位图 for(int i:arr){ newArr[i+temp]++;//算法体现 } // 重新调整arr数组中的元素 int index = 0; for (int i = 0; i < newArr.length; i++) { // 位图是1的就输出,对数组排序 while (newArr[i] > 0) { arr[index] = i + min; index++; newArr[i]--; } } return arr; 】 } public static void main(String[] args) { int[] arr={5,2,3,7,1}; //int[] arr={-5,2,-3,7,1}; int[] arrsort=bitmapSort(arr); for(int i:arrsort) System.out.println(i); } }
4.做交集和并集效率极高
举个例子,现有一位图0000101,代表喜欢吃苹果用户
另一位图0000111,代表喜欢吃西瓜用户
统计喜欢吃苹果或西瓜的用户,0000101|0000111=0000111
优化:
在谷歌实现的EWAHCompressedBitmap中,把Bitmap存在Long的数组中,Long数组的每个元素可以被当做64位二进制,也是Bitmap的元素,叫word
当创建一个空Bitmap的时候,初始化有4个word元素,不够就进行扩容
第一个w0不存入信息,当插入数字为1时候w1变为00000001,当插入为4的时候w1,000010001,当插入为64,超过了w1容量,w2,00000001
当存数字1000000时
1000001/64=15625 余1 那么按照正常思考的方式变成了这样
如愿以偿的浪费了15625个w
事实上,它是这样的
然后w0其实是LRW,存储分为2部分,高32位表示横跨多少个w,此处为15625,低32位表示后方有多少个连续的w,此处为0个
最终是这样
end