1. 程式人生 > >大資料排序演算法總結

大資料排序演算法總結

一、查詢

1、點陣圖法10億個正整數,只有其中1個數重複出現過,要在O(n)的時間裡面找出這個數,記憶體要儘可能少(小於100M)

(1)首先看一下10億個正整數,正整數可以表示的範圍為1到2的31次方-1。

10億也就是1*10^9,2^31次方=2*1024*1024*1024>20億,再想起int為32位。再想起點陣圖法。點陣圖法也就是對於出現的數,其中每1bit代表這個數,如果該位為1,則說明該數出現;如果該位為0,則說明該數沒有出現。那多大的記憶體能夠表示10億的數呢?

1 byte = 8 bit
1024 byte = 8*1024 bit = 1k
1024 k = 8*1024*1024 bit = 1M = 8388608 bit

     將10,0000,0000處以8388608得到119.20928955078125,也就是差不多120M的記憶體,可以表示全10億的數。所以可以建立120M的一個位圖,將其所有位設定為0,然後開始遍歷這10億個整數,每遍歷一個,則對應到點陣圖中相應的位置1,如果對應到點陣圖中相應的位已經置1了,則說明這個數是要找的那個重複的數。用這種方法,最多就是遍歷一遍,將這個10億個正整數遍歷完。而使用的記憶體為120M左右。
     當然,題目中要求是小於100M。其實寫到這裡,似乎感覺這個題目是在哪裡看到過。似乎是《程式設計珠璣》或者類似的書中,當然,最初的來源肯定是程式設計珠璣,關於電話號碼的部分。於是下一步我就是將這本書翻出來,結果就是在開篇就是關於這個問題。


不過我們遇到的問題是10億個數,100M記憶體。而書中的問題是10^7個正整數,1M的可用主存。書中的問題乘以100,就正好是我們遇到的問題了。不過書中的問題是去掉所有重複的數,並將結果是一個有序的排列。
     如果嚴格的使用100M以下記憶體的話,我們只能利用磁碟作為虛擬儲存空間。如果使用磁碟的話,應該就會涉及到外排序之類的。或者是虛擬記憶體的管理,頁面的換入換出?
     其實我們這裡的問題並不需要完全排序,而只是需要找出重複的數就可以。是否可以不用排序就得到?再想想,其實題目出的有問題,應該是最大不會超過10億,不然點陣圖法也不行。或者就需要做hash來得到對應關係了。

(2)100M的記憶體=100*1024*1024*8bit=8,3886,0800 bit。我們做個取整,那麼100M可以用的bit數將由8億

     那麼用每個bit來表示1個數,0表示沒有出現,1表示出現,我們用取模的方式num%8億來將數對映到8億個bit中去,最前的2億個bit會被重複對映,而剩下的6億個bit只被對映一次(step1,遍歷一次);由於10億個正整數,只有其中1個數重複出現過,因此如果在對映到這後6億個bit中,若發現某bit位已經是1,那麼我們就提前找到這個數了;否則我們可以認為重複的數是那些被對映到前2億位的數。因此只要在第一遍對映中沒有發現重複的數,則接下來我們只需要用4億個bit來判斷重複的數,即,前2億個bit用來記錄num/2億=0的數,而後2億個位用來記錄num/2億=4的數,這樣同樣若發現某bit位已經是1,那麼我們就提前找到這個數了(step2,遍歷一次)。一共遍歷了2遍,時間複雜度O(2n)=O(n)。

2、分割法10億個不重複的整數,查詢數值大小在中間的數,要求給出效率最高的演算法

/*   我的思路是這樣的,假設是32位的無符號整數,共有2^32 = 2^16 * 2^16 個整數,把全部整數等分成2^16個區間
然後統計每個區間裡面的數的個數,確定中位數是在哪個區間裡面,再對10億個數中落在該區間裡面每個數上的個數進行計數
這樣進行從小進行加和就可以確定中位數了
*/

//這裡沒有考慮10億個數是怎麼儲存,怎麼讀的,只是假設他存在陣列a[N]中, N在這裡等於10億

int SelectMedian()
{
int zone[2^16] = { 0 };
int MediumZone[2^16] = { 0 };
int i = 0;

for(; i< N ; i++)
   zone[a/(2^16)]++;

int sum = 0; 
i = 0;

do
   sum += zone[i++];
while(sum < N/2)

sum -= zone[i-1];
izone = i-1;  //這裡,我們把中位數所在區間叫中位組,儲存中位組的標號
int floor = 2^16 * izone, ceiling = 2^16 * (izone + 1);
//floor 和ceiling 用來儲存 中位組的下限和上限
for(i=0; i<N; i++)
    if(a >= floor && a <= ceiling)
          MediumZone[a-floor]++;

i=0;
do
   sum += MediumZone[i++];
while(sum < N/2)

return a[floor + i -1];

}   

//這裡遍歷了10億數兩次,用了兩個2^16 = 16k大小的陣列, 時間複雜度 為o(n), 空間用了32k
//這是對32位數而言的,對於64位數 2^64 = 2^16  * 2^16 * 2^16  * 2^16  分四次就可以了, 演算法複雜度依然是o(n), 空間用 16k * 4 = 64k


3、建堆法從1億個整數裡找出100個最大的數(用哪種演算法效率高)

我的思路:
(1)讀取前100個數字,建立最大值堆。
(2)依次讀取餘下的數,與最大值堆作比較,維持最大值堆。可以每次讀取的數量為一個磁碟頁面,將每個頁面的資料依次進堆比較,這樣節省IO時間。
(3)將堆進行排序,即可得到100個有序最大值。

二、億級排序

輸入:一個最多含有n個不重複的正整數(也就是說可能含有少於n個不重複正整數)的檔案,其中每個數都小於等於n,且n=10^7。
輸出:得到按從小到大升序排列的包含所有輸入的整數的列表。

《程式設計珠璣》中提出的問題,有三種解法:

(1)磁碟合併排序

先將所有資料分成多個小檔案,多個小檔案採用內部排序後,再用多路合併排序完成排序輸出。

        總資料為n, 記憶體中採用內部排序最多m。先分成n/m個小檔案,再內部排序,第三部讀取所有小檔案,每次將最小的數輸出即可。

(2)多通道

0~10^k-1

10^k~2*10^k-1

...

分成m個通道,讀m次,每次讀取在通道範圍內的數,按順序寫到對應的輸出檔案,完成排序。

(3)bitmap排序

在記憶體中開10^7位元,均初始化為0,若出現則設定為1,輸出為1的數即可。

題目:

如果有一個20g的日誌檔案,日誌檔案記錄著使用者訪問過的url,每一行為一個url,給你一臺512M的主機,找出出現次數最多的10個url

參考答案及思路:

1. Top K演算法:使用堆排序演算法+大頂堆+10個元素的陣列。

2.

  • IP地址最多有2^32=4G種取值情況,所以不能完全載入到記憶體中處理;
  • 可以考慮採用“分而治之”的思想,按照IP地址的Hash(IP)%1024值,把海量IP日誌分別儲存到1024個小檔案中。這樣,每個小檔案最多包含4MB個IP地址;
  • 對於每一個小檔案,可以構建一個IP為key,出現次數為value的Hash map,同時記錄當前出現次數最多的那個IP地址;
  • 可以得到1024個小檔案中的出現次數最多的IP,再依據常規的排序演算法得到總體上出現次數最多的IP;