這也是比較經典的算法之一
在存儲一些比較大的數據時十分有用,但是在使用的過程中,必須十分注意的就是數據的最大值,因爲這個值決定了
存儲的內存,在比較高效的結構中還有hash
在這個過程中,最好首先估計需要的內存的量
在所有具有性能優化的數據結構中,我想大家使用最多的就是hash表,是的,在具有定位查找上具有O(1)的常量時間,多麼的簡潔優美,
但是在特定的場合下:
①:對10億個不重複的整數進行排序。
②:找出10億個數字中重複的數字。
當然我只有普通的服務器,就算2G的內存吧,在這種場景下,我們該如何更好的挑選數據結構和算法呢?
一:問題分析
這年頭,大牛們寫的排序算法也就那麼幾個,首先我們算下放在內存中要多少G: (10億 * 32)/(1024*1024*1024*8)=3.6G,可憐
的2G內存直接爆掉,所以各種神馬的數據結構都玩不起來了,當然使用外排序還是可以解決問題的,由於要走IO所以暫時剔除,因爲我們
要玩高性能,無望後我們想想可不可以在二進制位上做些手腳?
比如我要對{1,5,7,2}這四個byte類型的數字做排序,該怎麼做呢?我們知道byte是佔8個bit位,其實我們可以將數組中的值作爲bit位的
key,value用”0,1“來標識該key是否出現過?下面看圖:
從圖中我們精彩的看到,我們的數組值都已經作爲byte中的key了,最後我只要遍歷對應的bit位是否爲1就可以了,那麼自然就成有序數組了。
可能有人說,我增加一個13怎麼辦?很簡單,一個字節可以存放8個數,那我只要兩個byte就可以解決問題了。
可以看出我將一個線性的數組變成了一個bit位的二維矩陣,最終我們需要的空間僅僅是:3.6G/32=0.1G即可,要注意的是bitmap排序不
是N的,而是取決於待排序數組中的最大值,在實際應用上關係也不大,比如我開10個線程去讀byte數組,那麼複雜度爲:O(Max/10)。
二:代碼
我想bitmap的思想大家都清楚了,這一次又讓我們見證了二進制的魅力,當然這些移位都是位運算的工作了,熟悉了你就玩轉了。
1:Clear方法(將數組的所有bit位置0)
比如要將當前4對應的bit位置0的話,只需要1左移4位取反與B[0] & 即可。
1 #region 初始化所用的bit位爲0 2 /// <summary> 3 /// 初始化所用的bit位爲0 4 /// </summary> 5 /// <param name="i"></param> 6 static void Clear(byte i) 7 { 8 //相當於 i%8 的功能 9 var shift = i & 0x07; 10 11 //計算應該放數組的下標 12 var arrindex = i >> 3; 13 14 //則將當前byte中的指定bit位取0,&後其他對方數組bit位必然不變,這就是 1 的妙用 15 var bitPos = ~(1 << shift); 16 17 //將數組中的指定bit位置一 “& 操作” 18 a[arrindex] &= (byte)(bitPos); 19 } 20 #endregion
2:Add方法(將bit置1操作)
同樣也很簡單,要將當前4對應的bit位置1的話,只需要1左移4位與B[0] | 即可。
1 #region 設置相應bit位上爲1 2 /// <summary> 3 /// 設置相應bit位上爲1 4 /// </summary> 5 /// <param name="i"></param> 6 static void Add(byte i) 7 { 8 //相當於 i%8 的功能 9 var shift = i & 0x07; 10 11 //計算應該放數組的下標 12 var arrindex = i >> 3; 13 14 //將byte中的 1 移動到i位 15 var bitPos = 1 << shift; 16 17 //將數組中的指定bit位置一 “| 操作” 18 a[arrindex] |= (byte)bitPos; 19 } 20 #endregion
2:Contain方法(判斷當前bit位是否是1)
如果看懂了Clear和Add,我相信最後一個方法已經不成問題了。