BitMap概述
本文介紹 BitMap 算法的應用背景,算法思想和相關實現細節。
概括而言,BitMap 主要用來解決海量數據中元素查詢,去重、以及排序等問題。這里對海量數據場景的強調,似乎暗示了這個算法對空間的利用相當的精巧和經濟,事實確實如此。
BitMap算法
本來數據序列的排序是一個平凡的任務,現有的多種排序算法,都有各自擅場能適應不同情形的具體要求。但我們考慮這樣一個場景:有一台內存為 4 GB 的 PC,其硬盤中的一個存儲了 30 億個無符號整型數據文件,這些整數一行一個且無重復。現在需要我們對這個文件中的數據進行排序后輸出。
簡單計算不難得到,這個數據文件的大小為 \(4\cdot3\cdot10^9/2^{30}\) 約為 11.2 GB,顯然將這個數據文件直接讀入內存是辦不到的。能否強行利用現有的內存 size 來存儲這些數據呢?答案是可能的,此時 BitMap 算法就該 C 位亮相了。BitMap 的想法相當精妙,它對整型數據作了一種轉化,使得這個辦不到的存儲成為可能。我們這里忽略不同語言的設定,假設一個 int 整數占 4 個字節,即32 bit,如果我們能用一個 bit 位來標示一個 int 整數,那么需要的存儲空間將大大減少,估算一下可知,30億個整數需要的內存空間為 \(3\cdot10^9/8/2^{20}\) 大概為 357.6 MB,這樣,我們可以輕易將這 30 億個 int 數放到內存中進行處理。
具體而言,BitMap 對數據的轉化可簡述如下:
一個整型 int 占 4 bytes,共32位,我們申請一個 int 長度為 N//32 + 1 的數組,即可存儲完這些數據,其中 N 表示要進行查找的最大整數,這可以經讀取遍歷一輪數據獲得。通過數組中的每個元素在內存在占 32 位對應表示十進制數 0-31,故可得到 BitMap 表:
array[0] 可表示 0-31
array[1] 可表示 32-63
array[2] 可表示 64-95
...
下面就只剩下如何將十進制數轉換為對應的二進制 bit 位,實現以 1 當 32 的效果,顯然,這部分實現只需用到一些位運算操作,具體細節見下面的代碼示例。不難看出,Bitmap 排序需要的時間復雜度和空間復雜度依賴於數據中最大的數字。
代碼實現
from array import array
class BitMap:
def __init__(self):
self.n = 5
self.bitsize = 1 << self.n
self.typecode = 'I' # 32位unsighed整型
self.lowerbound = 0 # 若數組中有負數,則所有數都減去最小的那個負數
@staticmethod
def greater_power2n(x):
i = 1
while True:
y = x >> i
x |= y
if y == 0:
break
i <<= 1
return x + 1
def load(self, inp):
'''
一般情形,數據應該是流式讀取,這里簡化起見不失一般性,將數據直接全部讀完
'''
mini = min(inp)
if mini < 0:
self.lowerbound = -mini # 如果數組中有<0的數,則所有數都要減去最小的那個負數
inp = [i + self.lowerbound for i in inp]
maxi = max(inp)
num_arr = max(self.greater_power2n(maxi) >> self.n, 1) # 至少應該使用一個數組
self.arr = array(self.typecode, [0] * num_arr)
for x in inp:
self._set(x)
def _set(self, x, set_val=True):
'''
將x在數組中對應元置為1
'''
arr_idx = x >> self.n # 元素在第幾個數組中,等價於x // 2**self.n
bit_idx = x & (self.bitsize - 1) # 元素在相應數組中的第幾個bit位,等價於x % 2**self.n
if set_val:
self.arr[arr_idx] |= 1 << bit_idx
else:
self.arr[arr_idx] &= ~(1 << bit_idx)
def search(self, x):
if self.lowerbound != 0:
x += self.lowerbound
arr_idx = x >> self.n
bit_idx = x & (self.bitsize - 1)
existence = True if self.arr[arr_idx] & (1 << bit_idx) else False
return existence
def sort(self):
sorted_seq = []
for arr_idx, a in enumerate(self.arr):
for bit_idx in range(self.bitsize):
if a & (1 << bit_idx):
sorted_seq.append(arr_idx * self.bitsize + bit_idx - self.lowerbound)
return sorted_seq
def show_bitmap(self):
for i, a in enumerate(self.arr):
print('The {}th array elements: {:032b}'.format(i, a))
測試結果
>>> bitmap = BitMap()
>>> bitmap.load([-3, 2, 56, -34, 40, 21, 99, 25])
>>> bitmap.search(21), bitmap.search(3)
(True, False)
>>> bitmap.sort()
[-34, -3, 2, 21, 25, 40, 56, 99]
參考資料
[1] 黑胡同里の貓,詳解BitMap算法,https://www.520mwx.com/view/59057
[2] Joe Bentley,Programming Pears(second edition)