1、前言
楼主酷爱王者,但是由于忙于业务,王者有一段时间没玩了,待再次上线的时候,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,那我们申请大小为6的数组
通过位图算法处理后,得到的位图是这样的
这种算法的缺点在于,最大值和最小值之间不能相差太大,否则浪费申请数组的空间。(蛋士可以优化滴~)
2、实际应用:
1、判断一个数是否存在某数据中,假如有40亿数据,我们如何快速判断指定一个数是否存在?
申请512M的内存 512M=51210241024B*8=4294967296比特(bit) 这个空间可以装40亿了
一个bit位代表一个int值
读入40亿个数,设置相应的bit位
读入要查询的数,查看相应bit位是否为1:为1表示存在,为0表示不存在
详细解释见:【面试现场】如何判断一个数是否在40亿个整数中?
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
3、有趣的位图排序算法
https://blog.51cto.com/realzjy/1317523
这几天在看《编程珠玑》,其中看到了一个非常有趣的排序算法,个人认为这个算法比较另类,在这里拿出来和大家分享。此算法代码量十分的少,排序效率又很高,但它是也有一些特定条件在里面。
先说说思路和特定条件,实际的问题是,有一个文件里面包含7位电话号码,对电话号码进行排序,电话号码之间不重复。我将其归纳为:对一个最多可以是1千万个数字的集合的数组进行排序,数组中最大的数字是1千万,数字之间不能重复。
算法的思路是(我这里以c#代码为例),创建一个**byte[n]**的比特数据,n=1千万:
1、关闭所有的位,将集合初始化为空
2、读取集合中的每一个整数,打开相应的位
3、检查每个位,如果打开就输出整数
代码:
public static int[] Sort(int[] arr)
{
int n = 10000000;
byte[] bytes = new byte[n];
for (int i = 0; i < arr.Length; i++)
{ bytes[arr[i]] = 1; }
List<int> result = new List<int>();
for (int i = 0; i < bytes.Length; i++)
{
if (bytes[i] == 1)
{ result.Add(i); }
}
return result.ToArray();
}
做一个简单的单元测试
[TestMethod()]
public void BitmapSortTest()
{
int[] i = new int[] { 10, 50, 90, 11, 51, 91, 2, 3, 4, 80, 5 };
i = BitmapSort.Sort(i);
Assert.IsTrue(i[0] == 2);
Assert.IsTrue(i[1] == 3);
Assert.IsTrue(i[2] == 4);
Assert.IsTrue(i[3] == 5);
Assert.IsTrue(i[4] == 10);
Assert.IsTrue(i[5] == 11);
Assert.IsTrue(i[6] == 50);
Assert.IsTrue(i[7] == 51);
Assert.IsTrue(i[8] == 80);
Assert.IsTrue(i[9] == 90);
Assert.IsTrue(i[10] == 91);
}
这个算法的思路很帅气,而且我们只在内存中开了一个byte[10000000]的比特数组,也就是说内存的消耗很小。当然我们还可以改进,如果中间数字有重复的怎么办呢?其实也很简单,用字符串数组代替比特数组,将设置为1的操作变成自增1,在进行输出的时候数字是几,这个数组输出几次,不就完美的解决问题了吗。