用少於2MB內存存下5百萬個介於0到1千萬之間的整數
使用bitmap,對每個bit為進行映射、
把每個bit位映射成一個整數,
在2.5億個整數中找出不重復的整數,註,內存不足以容納這2.5億個整數。
轉載自https://itimetraveler.github.io/2017/07/13/%E3%80%90%E7%AE%97%E6%B3%95%E3%80%9110%E4%BA%BFint%E5%9E%8B%E6%95%B0%EF%BC%8C%E7%BB%9F%E8%AE%A1%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0/
10億int整型數,以及一臺可用內存為1GB的機器,時間復雜度要求O(n),統計只出現一次的數?
首先分析多大的內存能夠表示10億的數呢?一個int型占4字節,10億就是40億字節(很明顯就是4GB),也就是如果完全讀入內存需要占用4GB,而題目只給1GB內存,顯然不可能將所有數據讀入內存。
- 位圖法:用一個bit位來標識一個int整數。
- 分治法:分批處理這10億的數。
int整型數是4字節(Byte),也就是32位(bit),如果能用一個bit位來標識一個int整數那麽存儲空間將大大減少。
另一種是分治法,內存有限,分批讀取處理。
int型數據是 -2^31~2^31-1個數,共有2^32個數。接近43億
位圖法:int型是4字節(byte)=32位(bit)。每個int用1bit來表示,表示所有的int型數字需要2^32bit的空間,也就是2^32/8=512MB,即用512M就可以存儲所有int型的數。
具體做法:因為每個int型數有32bit,所以申請int num[N/32+1]大小的數組即可,N=2^32為要查找的總數。
num中的每個元素在內存中都有32bit可以表示對應的十進制數0-32,所以相應的bitmap表如下:
num[0]:0~31
num[1]:32~63
num[2]:64~95
~~
假設這10億int數據為:6,3,8,32,36,……,那麽具體的BitMap表示為:
(1):判斷int型數字target在哪個位置:將數字target/32,取整數部分,來得到在數組中的位置,然後target%32來得到在具體哪個bit上
如:8 8/32=0 即在num[0]的內部,8%32=8,則它在num[0]的第8個bit位
如何統計只出現一次的數:
可以分為3種情況:出現0次,出現1次,多於1次,需要使用2個bit位來表示,分別表示為00,01, 10, 11(無效)
然後順序掃描10億個數,在對應的雙bit位標記該數出現的次數,最後取出所有雙bit位為01的就是了
優點:
-
運算效率高,不許進行比較和移位;2
-
占用內存少,比如N=10000000;只需占用內存為N/8=1250000Byte=1.25M
缺點:所有的數據不能重復。即不可對重復的數據進行排序和查找。
Bit-Map可作為數據的查找、去重、排序等操作。比如以下幾個例子:
1、在3億個整數中找出重復的整數個數,限制內存不足以容納3億個整數
對於這種場景可以采用2-BitMap來解決,即為每個整數分配2bit,用不同的0、1組合來標識特殊意思,如00表示此整數沒有出現過,01表示出現一次,11表示出現過多次,就可以找出重復的整數了,其需要的內存空間是正常BitMap的2倍,為:3億*2/8/1024/1024=71.5MB。
具體的過程如下:掃描著3億個整數,組BitMap,先查看BitMap中的對應位置,如果00則變成01,是01則變成11,是11則保持不變,當將3億個整數掃描完之後也就是說整個BitMap已經組裝完畢。最後查看BitMap將對應位為11的整數輸出即可。、
2、對沒有重復元素的整數進行排序
對於非重復的整數排序BitMap有著天然的優勢,它只需要將給出的無重復整數掃描完畢,組裝成為BitMap之後,那麽直接遍歷一遍Bit區域就可以達到排序效果了。
舉個例子:對整數4、3、1、7、6進行排序:
直接按Bit位輸出就可以得到排序結果了。
3、已知某個文件內包含一些電話號碼,每個號碼為8位數字,統計不同號碼的個數
8位最多99 999 999,大概需要99m個bit,大概10幾m字節的內存即可。可以理解為從0-99 999 999的數字,每個數字對應一個Bit位,所以只需要99M個Bit==1.2MBytes,這樣,就用了小小的1.2M左右的內存表示了所有的8位數的電話。
4、2.5億個整數中找出不重復的整數的個數,內存空間不足以容納這2.5億個整數
將bit-map擴展一下,用2bit表示一個數即可:0表示未出現;1表示出現一次;2表示出現2次及以上,即重復,在遍歷這些數的時候,如果對應位置的值是0,則將其置為1;如果是1,將其置為2;如果是2,則保持不變。或者我們不用2bit來進行表示,我們用兩個bit-map即可模擬實現這個2bit-map,都是一樣的道理。
最後放一個使用Byte[]數組存儲、讀取bit位的示例代碼,來自利用位映射原理對大數據排重:
由於最大值可能是2^32,故用long接收。
long bitIndex = num + (1l << 31);
計算在轉化為byte[]數組的索引,由於上面定義的bitIndex 索引是非負數,故無需引入位運算去符號。
int index = (int) (bitIndex / 8);
計算bitIndex 在byte[]數組索引index 中的具體位置。
int innerIndex = (int) (bitIndex % 8);
引入位運算將byte[]數組索引index 的各個位按權值相加
dataBytes[index] = (byte) (dataBytes[index] | (1 << innerIndex));
這樣就解決了整個大數據讀取排重的問題。
http://yacare.iteye.com/blog/1969931
class BitmapTest { private static final int CAPACITY = 1000000000;//數據容量 // 定義一個byte數組緩存所有的數據 private byte[] dataBytes = new byte[1 << 29]; public static void main(String[] args) { BitmapTest ms = new BitmapTest(); byte[] bytes = null; Random random = new Random(); for (int i = 0; i < CAPACITY; i++) { int num = random.nextInt(); System.out.println("讀取了第 " + (i + 1) + "\t個數: " + num); bytes = ms.splitBigData(num); } System.out.println(""); ms.output(bytes); } /** * 讀取數據,並將對應數數據的 到對應的bit中,並返回byte數組 * @param num 讀取的數據 * @return byte數組 dataBytes */ private byte[] splitBigData(int num) { long bitIndex = num + (1l << 31); //獲取num數據對應bit數組(虛擬)的索引 int index = (int) (bitIndex / 8); //bit數組(虛擬)在byte數組中的索引 int innerIndex = (int) (bitIndex % 8); //bitIndex 在byte[]數組索引index 中的具體位置 System.out.println("byte[" + index + "] 中的索引:" + innerIndex); dataBytes[index] = (byte) (dataBytes[index] | (1 << innerIndex)); return dataBytes; } /** * 輸出數組中的數據 * @param bytes byte數組 */ private void output(byte[] bytes) { int count = 0; for (int i = 0; i < bytes.length; i++) { for (int j = 0; j < 8; j++) { if (!(((bytes[i]) & (1 << j)) == 0)) { count++; int number = (int) ((((long) i * 8 + j) - (1l << 31))); System.out.println("取出的第 " + count + "\t個數: " + number); } } } } }
2、分治法
分治法目前看到的解決方案有哈希分桶(Hash Buckets)和歸並排序兩種方案。
哈希分桶的思想是先遍歷一遍,按照hash分N桶(比如1000桶),映射到不同的文件中。這樣平均每個文件就10MB,然後分別處理這1000個文件,找出沒有重復的即可。一個相同的數字,絕對不會誇文件,有hash做保證。因為算法具體還不甚了解,這裏先不做詳細介紹。
用少於2MB內存存下5百萬個介於0到1千萬之間的整數