位圖算法


樓主酷愛王者,但是由於忙於業務,王者有一段時間沒玩了,待再次上線的時候,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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM