1. 程式人生 > >bitmap計數,求TopK最快的方法

bitmap計數,求TopK最快的方法

上篇介紹了TopK的四種解法,其中隨機選擇 (randomized select) 最為經典,用減治法 (Reduce & Conquer) 的思想,將資料規模急速降低,總體複雜度為O(n)。

 

結尾挖了一個坑:求TopK,有沒有比隨機選擇更快的方法呢?

 

空間換時間,是演算法優化中最常見的手段,如果有相對充裕的記憶體,可以有更快的演算法。

畫外音:即使記憶體不夠,也可以水平切分,使用分段的方法來操作,減少每次記憶體使用量。

 

TopK問題描述

從arr[1, 12]={5,3,7,1,8,2,9,4,7,2,6,6} 這n=12個數中,找出最大的k=5個。

 

位元點陣圖(bitmap)

bitmap,是空間換時間的典型代表。它是一種,用若干個bit來表示集合的資料結構。

 

例如,集合S={1,3,5,7,9},容易發現,S中所有元素都在1-16之間,於是,可以用16個bit來表示這個集合:存在於集合中的元素,對應bit置1,否則置0。

畫外音:究竟需要多少存儲存空間,取決於集合中元素的值域,在什麼範圍之內。

 

上述集合S,可以用1010101010000000這樣一個16bit的bitmap來表示,其中,第1, 3, 5, 7, 9個bit位置是1。

 

假設TopK的n個元素都是int,且元素之間沒有重複,只需要申請2^32個bit,即512M的記憶體,就能夠用bitmap表示這n元素。

 

掃描一次所有n個元素,以生成bitmap,其時間複雜度是O(n)。生成後,取TopK只需要找到最高位的k個bit即可。演算法總時間複雜度也是O(n)。

 

虛擬碼為:

bitmap[4G] = make_bitmap(arr[1, n]);

return bitmap[top k bits];

 

bitmap演算法有個缺點,如果集合元素有重複,相同的元素會被去重,假設集合S中有5個1,最終S製作成bitmap後,這5個1只對應1個bit位,相當於4個元素被丟掉了,這樣會導致,找到的TopK不準。該怎麼優化呢?

 

位元點陣圖計數

優化方法是,每個元素的1個bit變成1個計數。

如上圖所示,TopK的集合經過位元點陣圖計數處理後,會記錄每個bit對應在集合S中出現過多少次。

 

接下來,找TopK的過程,就是bitmap從高位的計數開始,往低位的計數掃描,得到count之和等於k,對應的bit就是TopK所求。

如上圖所示,k=5:

(1)第一個非0的count是1,對應的bit是9;

(2)第二個非0的count也是1,對應的bit是8;

(3)第三個非0的count是2,對應的bit是7;

(4)第四個非0的count是2,對應的bit是6,但TopK只缺1個數字了,故只有1個6入選;

故,最終的TopK={9, 8, 7, 7, 6}。

 

結論:通過位元點陣圖精準計數的方式,求解TopK,演算法整體只需要不到2次掃描,時間複雜度為O(n),比減治法的隨機選擇會更快。

 

為了鞏固今天的內容,例行挖個坑。

 

面試中,還有個問題問得比較多:求一個正整數的二進位制表示包含多少個1?

例如:7的二進位制表示是111,即7的二進位制表示包含3個1。

畫外音:我面試過程中從不問這個問題。

 

最常見的解法是:

uint32_t count_one(uint32_t n){

    uint32_t count=0;

    while(n){

        count ++;

        n &= (n-1);

    }

    return count;

}

關注微信公眾號和今日頭條,精彩文章持續更新中。。。。。