【原創】海量數據處理問題(一) ---- 外排,堆排,K查找的應用


這篇博客源自對一個內存無法處理的詞頻統計問題的思考,最后給出的解決辦法是自己想的,可以肯定這不是最好的解法。但是通過和同學的討論,仍然感覺這是一個有意義及有意思的問題,所以和大家分享與探討。

如果有誤,請大家指正。如果有更好的方法,望不吝賜教。

 

1、提出問題

 

實際問題:

當前有10T中文關鍵詞數據,需要統計出詞頻最高的1000個詞。可用的只有1G內存和磁盤。那么如何提取?

大概估算一下這個問題,設中文詞匯平均長度2.3,每次漢字用utf-8編碼是3B,那么10T數據大概有 10T/7B ~ 1.4 * 2^40 條詞匯。

1G內存即便對詞匯一 一對應,再加上4B的整形記錄詞頻,那么1G內存可以最多紀錄 1G/4B * 2^8 ~ 2^36 條詞匯。所以即便最理想的hash(一 一對應)也無法將所有詞匯對應到1G內存中。

 

抽象問題:

假設當前內存只能存放1000個詞,但是一共有100w詞匯需要處理,問如何返回前100個高頻詞匯?(只能使用單機內存和磁盤)

 

下面我們以抽象的問題為基礎進行分析

 

2、提煉解決辦法

 

這個問題大的方向有兩步,1 、 統計詞頻(重點思考) ; 2 、返回topK。

如果這兩步中涉及的數據都能放入內存中,那么最不濟 O(N^2) 便可以解決。不過由於內存無法承受,這兩步的解決都出現了問題

 

2.1  統計詞頻

2.1.1  壓縮數據(不可行)

由上所訴,如果能壓縮放入內存便可以解決問題。所以先考慮能否找到一種壓縮數據的辦法。但是通過上面的分析,即使轉換為hash也無法存入內存,所以需要換一種思路。

2.1.2  歸並統計(不可行)

既然hash不能把所有數據放入內存,那么至少可以放一部分。再加上利用磁盤的存儲,把hash值在一定范圍內的詞存放到第 i 個文件中。這樣每個文件直接互不相交,而且每個文件都能在內存中處理,一個一個文件的進行統計,那么統計詞頻的問題自然可以解決了。

這個想法看似可行,其實不然。

對於抽象問題所述,100w/1000 = 1000,所以至少需要1000個文件存放互不相交的詞。但是這里有兩個問題,1 是如何尋找這個hash函數,保證100w個詞在hash后能平均分配到1000個文件中。 2 是需要保證這個hash函數不會產生沖突,不然的話會導致不同的詞的統計到一起。

因此這個hash函數是比較難找的。(如果大家知道分享下啊)

2.1.3  仍然是歸並(可行的解決辦法

上述2中的歸並,是需要人為的把詞分到不同的桶里。但是hash函數太難設計了。

不過受《數據結構》書中對數值外排序的啟發,我想到了類似歸並排序的解決辦法。

即對於抽象問題所述,首先按內存大小,把100w數據順序分詞1000份,這1000份是均勻大小的桶,上述2.1.2中難以實現的均分桶在這里可以很簡單的實現。對於每個桶中的數據,因為每個詞在內存中對應了一個2進制數值,可以通過移位比較兩兩詞之間的數值大小。這樣,詞直接可以進行比較了,因此就可以進行排序了。再利用外排序的方法,就可以構成一個歸並的解,因此可以將所有詞匯進行詞頻統計。

時間復雜度為  O(M/N * log(N) + M *log(M/N) ) 。(M是所有數據量,N是每個桶中的數據量)

具體到抽象問題,即  O( 100w/100 * log(1000) (排序) + 100w * log(100w/1000) (歸並) ) 。

 

2.2 返回topK

topK的方法有兩種,一是 堆排序。 二是 K查找。

二者對比:

          堆排序      K查找

   時間復雜度      O(logN)             O(N)

      空間復雜度       O(K)                 O(N)   (K就是topK的K)

      topK結果     有序        無序

 

2.2.1   堆排序(適合本題

對於提到的抽象數據,使用堆排序更合適。首先拿出第一個文件中的1000條數據構造一個 top100的堆樹。然后剩下的999個文件,每次從每個文件取500條數據放入內存,和top100的堆樹重新構造堆。那么需要一遍文件讀取就能得到top100的詞。所以對於文件讀取為主要時間消耗的外排來說,堆排序無疑更合適。

本題時間復雜度  100*log100 + 100w * log100 + 1000 read file time

2.2.2  K查找(不可行)

似乎關於K查找相關文章不多,這里提一下。

K查找類似快排,因為快排的每一趟處理,都會得到樞紐值得位置,如果這個位置正好為所求的K,便可以返回topK的結果。

算法大概思想:一個頭指針,一個尾指針。選擇某值作為平衡點,調整平衡點兩邊數值位置(到這里和快排一致)。然后根據平衡點和K值的關系,選擇頭一半或尾一半進行上述遞歸的查找(快排是兩邊都查找)。直到平衡點和K相等退出。可見K值只是划分了兩個邊界,每一個邊界內的數據並不是有序的。

 

因此K查找的時間復雜度:   N + N/2 + N/4 ... 1 ~ 2N ,為 O(N)

K查找代碼如下:

 

 1 int Kfind(int array[], int lowPos, int highPos, int K){
 2     int first = lowPos;
 3     int last = highPos;
 4     int key = array[first];
 5     while(first < last){
 6         while(first<last && array[last]>=key)
 7             last--;
 8         array[first] = array[last];
 9         while(first<last && array[first]<=key)
10             first++;
11         array[last] = array[first];
12     }
13     array[first] = key;
14     if(first == K-1)
15         return first+1;
16     if(lowPos < first && first > K-1)
17         return Kfind(array, lowPos, first-1, K);
18     if(first < highPos && first < K-1)
19         return Kfind(array, first+1, highPos, K);
20 }

 

測試樣例及結果:

 

可見,topK內的數據並不一定有序

 

但是對於本題,如果強行在文件中直接進行K查找,那么時間消耗會非常大。因此不適合。

 

 3、擴展

 

如果本題不限制完成條件,使用hadoop的MapReduce框架很適合解決本問題,詞之間沒有聯系,分治是很好的選擇。

當然,即使對於單機環境來說,詞頻統計也已經不算大數據量了,但是作為一個問題思考還是有意義的。

 

轉載請注明出處,謝謝~  http://www.cnblogs.com/xiaoboCSer/p/4202394.html  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM