转:6.1海量数据处理
本文轉(zhuǎn)自看云,原文地址請移步:https://www.kancloud.cn/kancloud/the-art-of-programming/41608
偶然閑游,偶遇某一站點,發(fā)現(xiàn)這里寫的關(guān)于海量數(shù)據(jù)處理相關(guān)的思路還挺不錯,所以在這里采摘收藏,如有侵權(quán)之處還請評論區(qū)或者站內(nèi)消息告知。
本文導讀:
所謂海量數(shù)據(jù)處理,是指基于海量數(shù)據(jù)的存儲、處理、和操作。正因為數(shù)據(jù)量太大,所以導致要么無法在較短時間內(nèi)迅速解決,要么無法一次性裝入內(nèi)存。
事實上,針對時間問題,可以采用巧妙的算法搭配合適的數(shù)據(jù)結(jié)構(gòu)(如布隆過濾器、哈希、位圖、堆、數(shù)據(jù)庫、倒排索引、Trie樹)來解決;而對于空間問題,可以采取分而治之(哈希映射)的方法,也就是說,把規(guī)模大的數(shù)據(jù)轉(zhuǎn)化為規(guī)模小的,從而各個擊破。
此外,針對常說的單機及集群問題,通俗來講,單機就是指處理裝載數(shù)據(jù)的機器有限(只要考慮CPU、內(nèi)存、和硬盤之間的數(shù)據(jù)交互),而集群的意思是指機器有多臺,適合分布式處理或并行計算,更多考慮節(jié)點與節(jié)點之間的數(shù)據(jù)交互。
一般說來,處理海量數(shù)據(jù)問題,有以下十種典型方法:
- 1.哈希分治;
- 2.simhash算法;
- 3.外排序;
- 4.MapReduce;
- 5.多層劃分;
- 6.位圖;
- 7.布隆過濾器;
- 8.Trie樹;
- 9.數(shù)據(jù)庫;
- 10.倒排索引。
受理論之限,本章將摒棄絕大部分的細節(jié),只談方法和模式論,注重用最通俗、最直白的語言闡述相關(guān)問題。最后,有一點必須強調(diào)的是,全章行文是基于面試題的分析基礎(chǔ)之上的,具體實踐過程中,還得視具體情況具體分析,且各個場景下需要考慮的細節(jié)也遠比本章所描述的任何一種解決方案復(fù)雜得多。
?6.1 關(guān)聯(lián)式容器
一般來說,STL容器分為:
- 序列式容器(vector/list/deque/stack/queue/heap),和關(guān)聯(lián)式容器。
- 其中,關(guān)聯(lián)式容器又分為set(集合)和map(映射表)兩大類,以及這兩大類的衍生體multiset(多鍵集合)和multimap(多鍵映射表),這些容器均以RB-tree(red-black tree, 紅黑樹)完成。
- 此外,還有第3類關(guān)聯(lián)式容器,如hashtable(散列表),以及以hashtable為底層機制完成的hash_set(散列集合)/hash_map(散列映射表)/hash_multiset(散列多鍵集合)/hash_multimap(散列多鍵映射表)。也就是說,set/map/multiset/multimap都內(nèi)含一個RB-tree,而hash_set/hash_map/hash_multiset/hash_multimap都內(nèi)含一個hashtable。
所謂關(guān)聯(lián)式容器,類似關(guān)聯(lián)式數(shù)據(jù)庫,每筆數(shù)據(jù)或每個元素都有一個鍵值(key)和一個實值(value),即所謂的Key-Value(鍵-值對)。當元素被插入到關(guān)聯(lián)式容器中時,容器內(nèi)部結(jié)構(gòu)(RB-tree/hashtable)便依照其鍵值大小,以某種特定規(guī)則將這個元素放置于適當位置。
包括在非關(guān)聯(lián)式數(shù)據(jù)庫中,比如,在MongoDB內(nèi),文檔(document)是最基本的數(shù)據(jù)組織形式,每個文檔也是以Key-Value(鍵-值對)的方式組織起來。一個文檔可以有多個Key-Value組合,每個Value可以是不同的類型,比如String、Integer、List等等。
{ "name" : "July", "sex" : "male", "age" : 23 }set/map/multiset/multimap
set,同map一樣,所有元素都會根據(jù)元素的鍵值自動被排序,因為set/map兩者的所有各種操作,都只是轉(zhuǎn)而調(diào)用RB-tree的操作行為,不過,值得注意的是,兩者都不允許兩個元素有相同的鍵值。
不同的是:set的元素不像map那樣可以同時擁有實值(value)和鍵值(key),set元素的鍵值就是實值,實值就是鍵值,而map的所有元素都是pair,同時擁有實值(value)和鍵值(key),pair的第一個元素被視為鍵值,第二個元素被視為實值。
至于multiset/multimap,他們的特性及用法和set/map完全相同,唯一的差別就在于它們允許鍵值重復(fù),即所有的插入操作基于RB-tree的insert_equal()而非insert_unique()。
hash_set/hash_map/hash_multiset/hash_multimap
hash_set/hash_map,兩者的一切操作都是基于hashtable之上。不同的是,hash_set同set一樣,同時擁有實值和鍵值,且實質(zhì)就是鍵值,鍵值就是實值,而hash_map同map一樣,每一個元素同時擁有一個實值(value)和一個鍵值(key),所以其使用方式,和上面的map基本相同。但由于hash_set/hash_map都是基于hashtable之上,所以不具備自動排序功能。為什么?因為hashtable沒有自動排序功能。
至于hash_multiset/hash_multimap的特性與上面的multiset/multimap完全相同,唯一的差別就是它們hash_multiset/hash_multimap的底層實現(xiàn)機制是hashtable(而multiset/multimap,上面說了,底層實現(xiàn)機制是RB-tree),所以它們的元素都不會被自動排序,不過也都允許鍵值重復(fù)。
所以,綜上,說白了,什么樣的結(jié)構(gòu)決定其什么樣的性質(zhì),因為set/map/multiset/multimap都是基于RB-tree之上,所以有自動排序功能,而hash_set/hash_map/hash_multiset/hash_multimap都是基于hashtable之上,所以不含有自動排序功能,至于加個前綴multi_無非就是允許鍵值重復(fù)而已。
?
?6.2 分而治之
方法介紹
對于海量數(shù)據(jù)而言,由于無法一次性裝進內(nèi)存處理,導致我們不得不把海量的數(shù)據(jù)通過hash映射分割成相應(yīng)的小塊數(shù)據(jù),然后再針對各個小塊數(shù)據(jù)通過hash_map進行統(tǒng)計或其它操作。
那什么是hash映射呢?簡單來說,就是為了便于計算機在有限的內(nèi)存中處理big數(shù)據(jù),我們通過一種映射散列的方式讓數(shù)據(jù)均勻分布在對應(yīng)的內(nèi)存位置(如大數(shù)據(jù)通過取余的方式映射成小數(shù)存放在內(nèi)存中,或大文件映射成多個小文件),而這個映射散列方式便是我們通常所說的hash函數(shù),設(shè)計的好的hash函數(shù)能讓數(shù)據(jù)均勻分布而減少沖突。
問題實例
1、海量日志數(shù)據(jù),提取出某日訪問百度次數(shù)最多的那個IP
分析:百度作為國內(nèi)第一大搜索引擎,每天訪問它的IP數(shù)量巨大,如果想一次性把所有IP數(shù)據(jù)裝進內(nèi)存處理,則內(nèi)存容量明顯不夠,故針對數(shù)據(jù)太大,內(nèi)存受限的情況,可以把大文件轉(zhuǎn)化成(取模映射)小文件,從而大而化小,逐個處理。
換言之,先映射,而后統(tǒng)計,最后排序。
解法:具體分為以下3個步驟
- 1.分而治之/hash映射
- 首先把這一天訪問百度日志的所有IP提取出來,然后逐個寫入到一個大文件中,接著采用映射的方法,比如%1000,把整個大文件映射為1000個小文件。
- 2.hash_map統(tǒng)計
- 當大文件轉(zhuǎn)化成了小文件,那么我們便可以采用hash_map(ip, value)來分別對1000個小文件中的IP進行頻率統(tǒng)計,再找出每個小文件中出現(xiàn)頻率最大的IP。
- 3.堆/快速排序
- 統(tǒng)計出1000個頻率最大的IP后,依據(jù)各自頻率的大小進行排序(可采取堆排序),找出那個頻率最大的IP,即為所求。
注:Hash取模是一種等價映射,不會存在同一個元素分散到不同小文件中去的情況,即這里采用的是%1000算法,那么同一個IP在hash后,只可能落在同一個文件中,不可能被分散的。
2、尋找熱門查詢,300萬個查詢字符串中統(tǒng)計最熱門的10個查詢
原題:搜索引擎會通過日志文件把用戶每次檢索使用的所有檢索串都記錄下來,每個查詢串的長度為1-255字節(jié)。假設(shè)目前有一千萬個記錄,請你統(tǒng)計最熱門的10個查詢串,要求使用的內(nèi)存不能超過1G。
分析:這些查詢串的重復(fù)度比較高,雖然總數(shù)是1千萬,但如果除去重復(fù)后,不超過3百萬個。一個查詢串的重復(fù)度越高,說明查詢它的用戶越多,也就是越熱門。
由上面第1題,我們知道,數(shù)據(jù)大則劃為小的,例如一億個ip求Top 10,可先%1000將ip分到1000個小文件中去,并保證一種ip只出現(xiàn)在一個文件中,再對每個小文件中的ip進行hash_map統(tǒng)計并按數(shù)量排序,最后歸并或者最小堆依次處理每個小文件的top10以得到最后的結(jié)果。
但對于本題,數(shù)據(jù)規(guī)模比較小,能一次性裝入內(nèi)存。因為根據(jù)題目描述,雖然有一千萬個Query,但是由于重復(fù)度比較高,故去除重復(fù)后,事實上只有300萬的Query,每個Query255Byte,因此我們可以考慮把他們都放進內(nèi)存中去(300萬個字符串假設(shè)沒有重復(fù),都是最大長度,那么最多占用內(nèi)存3M*1K/4=0.75G。所以可以將所有字符串都存放在內(nèi)存中進行處理)。
所以我們放棄分而治之/hash映射的步驟,直接上hash_map統(tǒng)計,然后排序。So,針對此類典型的TOP K問題,采取的對策往往是:hash_map + 堆。
解法:
- 1.hash_map統(tǒng)計
- 先對這批海量數(shù)據(jù)預(yù)處理。具體方法是:維護一個Key為Query字串,Value為該Query出現(xiàn)次數(shù)的hash_map,即hash_map(Query, Value),每次讀取一個Query,如果該字串不在Table中,那么加入該字串,并將Value值設(shè)為1;如果該字串在Table中,那么將該字串的計數(shù)加1 即可。最終我們在O(N)的時間復(fù)雜度內(nèi)用hash_map完成了統(tǒng)計;
- 2.堆排序
- 借助堆這個數(shù)據(jù)結(jié)構(gòu),找出Top K,時間復(fù)雜度為N‘logK。即借助堆結(jié)構(gòu),我們可以在log量級的時間內(nèi)查找和調(diào)整/移動。因此,維護一個K(該題目中是10)大小的小根堆,然后遍歷300萬的Query,分別和根元素進行對比。所以,我們最終的時間復(fù)雜度是:O(n) + N' * O(logk),其中,N為1000萬,N’為300萬。
關(guān)于第2步堆排序,可以維護k個元素的最小堆,即用容量為k的最小堆存儲最先遍歷到的k個數(shù),并假設(shè)它們即是最大的k個數(shù),建堆費時O(k),并調(diào)整堆(費時O(logk))后,有k1>k2>...kmin(kmin設(shè)為小頂堆中最小元素)。繼續(xù)遍歷數(shù)列,每次遍歷一個元素x,與堆頂元素比較,若x>kmin,則更新堆(x入堆,用時logk),否則不更新堆。這樣下來,總費時O(k_logk+(n-k)_logk)=O(n*logk)。此方法得益于在堆中,查找等各項操作時間復(fù)雜度均為logk。
當然,你也可以采用trie樹,關(guān)鍵字域存該查詢串出現(xiàn)的次數(shù),沒有出現(xiàn)為0。最后用10個元素的最小推來對出現(xiàn)頻率進行排序。
3、有一個1G大小的一個文件,里面每一行是一個詞,詞的大小不超過16字節(jié),內(nèi)存限制大小是1M。返回頻數(shù)最高的100個詞
解法:
- 1.分而治之/hash映射
- 順序讀取文件,對于每個詞x,取hash(x)%5000,然后把該值存到5000個小文件(記為x0,x1,...x4999)中。這樣每個文件大概是200k左右。當然,如果其中有的小文件超過了1M大小,還可以按照類似的方法繼續(xù)往下分,直到分解得到的小文件的大小都不超過1M。
- 2.hash_map統(tǒng)計
- 對每個小文件,采用trie樹/hash_map等統(tǒng)計每個文件中出現(xiàn)的詞以及相應(yīng)的頻率。
- 3.堆/歸并排序
- 取出出現(xiàn)頻率最大的100個詞(可以用含100個結(jié)點的最小堆)后,再把100個詞及相應(yīng)的頻率存入文件,這樣又得到了5000個文件。最后就是把這5000個文件進行歸并(類似于歸并排序)的過程了。
4、海量數(shù)據(jù)分布在100臺電腦中,想個辦法高效統(tǒng)計出這批數(shù)據(jù)的TOP10
解法一:
如果同一個數(shù)據(jù)元素只出現(xiàn)在某一臺機器中,那么可以采取以下步驟統(tǒng)計出現(xiàn)次數(shù)TOP10的數(shù)據(jù)元素:
- 1.堆排序
- 在每臺電腦上求出TOP 10,可以采用包含10個元素的堆完成(TOP 10小,用最大堆,TOP 10大,用最小堆,比如求TOP10大,我們首先取前10個元素調(diào)整成最小堆,如果發(fā)現(xiàn),然后掃描后面的數(shù)據(jù),并與堆頂元素比較,如果比堆頂元素大,那么用該元素替換堆頂,然后再調(diào)整為最小堆。最后堆中的元素就是TOP 10大)。
- 2.組合歸并
- 求出每臺電腦上的TOP 10后,然后把這100臺電腦上的TOP 10組合起來,共1000個數(shù)據(jù),再利用上面類似的方法求出TOP 10就可以了。
解法二:
但如果同一個元素重復(fù)出現(xiàn)在不同的電腦中呢,比如拿兩臺機器求top 2的情況來說:
- 第一臺的數(shù)據(jù)分布及各自出現(xiàn)頻率為:a(50),b(50),c(49),d(49) ,e(0),f(0)
- 其中,括號里的數(shù)字代表某個數(shù)據(jù)出現(xiàn)的頻率,如a(50)表示a出現(xiàn)了50次。
- 第二臺的數(shù)據(jù)分布及各自出現(xiàn)頻率為:a(0),b(0),c(49),d(49),e(50),f(50)
這個時候,你可以有兩種方法:
- 遍歷一遍所有數(shù)據(jù),重新hash取摸,如此使得同一個元素只出現(xiàn)在單獨的一臺電腦中,然后采用上面所說的方法,統(tǒng)計每臺電腦中各個元素的出現(xiàn)次數(shù)找出TOP 10,繼而組合100臺電腦上的TOP 10,找出最終的TOP 10。
- 或者,暴力求解:直接統(tǒng)計統(tǒng)計每臺電腦中各個元素的出現(xiàn)次數(shù),然后把同一個元素在不同機器中的出現(xiàn)次數(shù)相加,最終從所有數(shù)據(jù)中找出TOP 10。
5、有10個文件,每個文件1G,每個文件的每一行存放的都是用戶的query,每個文件的query都可能重復(fù)。要求你按照query的頻度排序
解法一:
- 1.hash映射
- 順序讀取10個文件,按照hash(query)%10的結(jié)果將query寫入到另外10個文件(記為a0,a1,..a9)中。這樣新生成的文件每個的大小大約也1G(假設(shè)hash函數(shù)是隨機的)。
- 2.hash_map統(tǒng)計
- 找一臺內(nèi)存在2G左右的機器,依次對用hash_map(query, query_count)來統(tǒng)計每個query出現(xiàn)的次數(shù)。注:hash_map(query, query_count)是用來統(tǒng)計每個query的出現(xiàn)次數(shù),不是存儲他們的值,出現(xiàn)一次,則count+1。
- 3.堆/快速/歸并排序
- 利用快速/堆/歸并排序按照出現(xiàn)次數(shù)進行排序,將排序好的query和對應(yīng)的query_cout輸出到文件中,這樣得到了10個排好序的文件(記為)。最后,對這10個文件進行歸并排序(內(nèi)排序與外排序相結(jié)合)。
解法二:
一般query的總量是有限的,只是重復(fù)的次數(shù)比較多而已,可能對于所有的query,一次性就可以加入到內(nèi)存了。這樣,我們就可以采用trie樹/hash_map等直接來統(tǒng)計每個query出現(xiàn)的次數(shù),然后按出現(xiàn)次數(shù)做快速/堆/歸并排序就可以了。
解法三:
與解法1類似,但在做完hash,分成多個文件后,可以交給多個文件來處理,采用分布式的架構(gòu)來處理(比如MapReduce),最后再進行合并。
6、給定a、b兩個文件,各存放50億個url,每個url各占64字節(jié),內(nèi)存限制是4G,讓你找出a、b文件共同的url?
解法:
可以估計每個文件安的大小為5G×64=320G,遠遠大于內(nèi)存限制的4G。所以不可能將其完全加載到內(nèi)存中處理??紤]采取分而治之的方法。
- 1.分而治之/hash映射
- 遍歷文件a,對每個url求取,然后根據(jù)所取得的值將url分別存儲到1000個小文件(記為,這里漏寫個了a1)中。這樣每個小文件的大約為300M。遍歷文件b,采取和a相同的方式將url分別存儲到1000小文件中(記為)。這樣處理后,所有可能相同的url都在對應(yīng)的小文件()中,不對應(yīng)的小文件不可能有相同的url。然后我們只要求出1000對小文件中相同的url即可。
- 2.hash_set統(tǒng)計
- 求每對小文件中相同的url時,可以把其中一個小文件的url存儲到hash_set中。然后遍歷另一個小文件的每個url,看其是否在剛才構(gòu)建的hash_set中,如果是,那么就是共同的url,存到文件里面就可以了。
7、100萬個數(shù)中找出最大的100個數(shù)
解法一:采用局部淘汰法。選取前100個元素,并排序,記為序列L。然后一次掃描剩余的元素x,與排好序的100個元素中最小的元素比,如果比這個最小的要大,那么把這個最小的元素刪除,并把x利用插入排序的思想,插入到序列L中。依次循環(huán),知道掃描了所有的元素。復(fù)雜度為O(100萬*100)。
解法二:采用快速排序的思想,每次分割之后只考慮比軸大的一部分,知道比軸大的一部分在比100多的時候,采用傳統(tǒng)排序算法排序,取前100個。復(fù)雜度為O(100萬*100)。
解法三:在前面的題中,我們已經(jīng)提到了,用一個含100個元素的最小堆完成。復(fù)雜度為O(100萬*lg100)。
舉一反三
1、怎么在海量數(shù)據(jù)中找出重復(fù)次數(shù)最多的一個?
提示:先做hash,然后求模映射為小文件,求出每個小文件中重復(fù)次數(shù)最多的一個,并記錄重復(fù)次數(shù)。然后找出上一步求出的數(shù)據(jù)中重復(fù)次數(shù)最多的一個就是所求(具體參考前面的題)。
2、上千萬或上億數(shù)據(jù)(有重復(fù)),統(tǒng)計其中出現(xiàn)次數(shù)最多的前N個數(shù)據(jù)。
提示:上千萬或上億的數(shù)據(jù),現(xiàn)在的機器的內(nèi)存應(yīng)該能存下。所以考慮采用hash_map/搜索二叉樹/紅黑樹等來進行統(tǒng)計次數(shù)。然后就是取出前N個出現(xiàn)次數(shù)最多的數(shù)據(jù)了,可以用第2題提到的堆機制完成。
3、一個文本文件,大約有一萬行,每行一個詞,要求統(tǒng)計出其中最頻繁出現(xiàn)的前10個詞,請給出思想,給出時間復(fù)雜度分析。
提示:這題是考慮時間效率。用trie樹統(tǒng)計每個詞出現(xiàn)的次數(shù),時間復(fù)雜度是O(nle)(le表示單詞的平準長度)。然后是找出出現(xiàn)最頻繁的前10個詞,可以用堆來實現(xiàn),前面的題中已經(jīng)講到了,時間復(fù)雜度是O(nlg10)。所以總的時間復(fù)雜度,是O(nle)與O(nlg10)中較大的哪一個。
4、1000萬字符串,其中有些是重復(fù)的,需要把重復(fù)的全部去掉,保留沒有重復(fù)的字符串。請怎么設(shè)計和實現(xiàn)?
提示:這題用trie樹比較合適,hash_map也行。當然,也可以先hash成小文件分開處理再綜合。
5、一個文本文件,找出前10個經(jīng)常出現(xiàn)的詞,但這次文件比較長,說是上億行或十億行,總之無法一次讀入內(nèi)存,問最優(yōu)解。
提示:首先根據(jù)用hash并求模,將文件分解為多個小文件,對于單個文件利用上題的方法求出每個文件件中10個最常出現(xiàn)的詞。然后再進行歸并處理,找出最終的10個最常出現(xiàn)的詞。
6.3 simhash算法
方法介紹
背景
如果某一天,面試官問你如何設(shè)計一個比較兩篇文章相似度的算法?可能你會回答幾個比較傳統(tǒng)點的思路:
- 一種方案是先將兩篇文章分別進行分詞,得到一系列特征向量,然后計算特征向量之間的距離(可以計算它們之間的歐氏距離、海明距離或者夾角余弦等等),從而通過距離的大小來判斷兩篇文章的相似度。
- 另外一種方案是傳統(tǒng)hash,我們考慮為每一個web文檔通過hash的方式生成一個指紋(finger print)。
下面,我們來分析下這兩種方法。
- 采取第一種方法,若是只比較兩篇文章的相似性還好,但如果是海量數(shù)據(jù)呢,有著數(shù)以百萬甚至億萬的網(wǎng)頁,要求你計算這些網(wǎng)頁的相似度。你還會去計算任意兩個網(wǎng)頁之間的距離或夾角余弦么?想必你不會了。
- 而第二種方案中所說的傳統(tǒng)加密方式md5,其設(shè)計的目的是為了讓整個分布盡可能地均勻,但如果輸入內(nèi)容一旦出現(xiàn)哪怕輕微的變化,hash值就會發(fā)生很大的變化。
舉個例子,我們假設(shè)有以下三段文本:
- the cat sat on the mat
- the cat sat on a mat
- we all scream for ice cream
使用傳統(tǒng)hash可能會得到如下的結(jié)果:
- irb(main):006:0> p1 = 'the cat sat on the mat'
- irb(main):007:0> p1.hash => 415542861
- irb(main):005:0> p2 = 'the cat sat on a mat'
- irb(main):007:0> p2.hash => 668720516
- irb(main):007:0> p3 = 'we all scream for ice cream'
- irb(main):007:0> p3.hash => 767429688 "
可理想當中的hash函數(shù),需要對幾乎相同的輸入內(nèi)容,產(chǎn)生相同或者相近的hash值,換言之,hash值的相似程度要能直接反映輸入內(nèi)容的相似程度,故md5等傳統(tǒng)hash方法也無法滿足我們的需求。
出世
車到山前必有路,來自于GoogleMoses Charikar發(fā)表的一篇論文“detecting near-duplicates for web crawling”中提出了simhash算法,專門用來解決億萬級別的網(wǎng)頁的去重任務(wù)。
simhash作為locality sensitive hash(局部敏感哈希)的一種:
- 其主要思想是降維,將高維的特征向量映射成低維的特征向量,通過兩個向量的Hamming Distance來確定文章是否重復(fù)或者高度近似。
- 其中,Hamming Distance,又稱漢明距離,在信息論中,兩個等長字符串之間的漢明距離是兩個字符串對應(yīng)位置的不同字符的個數(shù)。也就是說,它就是將一個字符串變換成另外一個字符串所需要替換的字符個數(shù)。例如:1011101 與 1001001 之間的漢明距離是 2。至于我們常說的字符串編輯距離則是一般形式的漢明距離。
如此,通過比較多個文檔的simHash值的海明距離,可以獲取它們的相似度。
流程
simhash算法分為5個步驟:分詞、hash、加權(quán)、合并、降維,具體過程如下所述:
- 分詞
- 給定一段語句,進行分詞,得到有效的特征向量,然后為每一個特征向量設(shè)置1-5等5個級別的權(quán)重(如果是給定一個文本,那么特征向量可以是文本中的詞,其權(quán)重可以是這個詞出現(xiàn)的次數(shù))。例如給定一段語句:“CSDN博客結(jié)構(gòu)之法算法之道的作者July”,分詞后為:“CSDN 博客 結(jié)構(gòu) 之 法 算法 之 道 的 作者 July”,然后為每個特征向量賦予權(quán)值:CSDN(4) 博客(5) 結(jié)構(gòu)(3) 之(1) 法(2) 算法(3) 之(1) 道(2) 的(1) 作者(5) July(5),其中括號里的數(shù)字代表這個單詞在整條語句中的重要程度,數(shù)字越大代表越重要。
- hash
- 通過hash函數(shù)計算各個特征向量的hash值,hash值為二進制數(shù)01組成的n-bit簽名。比如“CSDN”的hash值Hash(CSDN)為100101,“博客”的hash值Hash(博客)為“101011”。就這樣,字符串就變成了一系列數(shù)字。
- 加權(quán)
- 在hash值的基礎(chǔ)上,給所有特征向量進行加權(quán),即W = Hash * weight,且遇到1則hash值和權(quán)值正相乘,遇到0則hash值和權(quán)值負相乘。例如給“CSDN”的hash值“100101”加權(quán)得到:W(CSDN) = 100101_4 = 4 -4 -4 4 -4 4,給“博客”的hash值“101011”加權(quán)得到:W(博客)=101011_5 = 5 -5 5 -5 5 5,其余特征向量類似此般操作。
- 合并
- 將上述各個特征向量的加權(quán)結(jié)果累加,變成只有一個序列串。拿前兩個特征向量舉例,例如“CSDN”的“4 -4 -4 4 -4 4”和“博客”的“5 -5 5 -5 5 5”進行累加,得到“4+5 -4+-5 -4+5 4+-5 -4+5 4+5”,得到“9 -9 1 -1 1”。
- 降維
- 對于n-bit簽名的累加結(jié)果,如果大于0則置1,否則置0,從而得到該語句的simhash值,最后我們便可以根據(jù)不同語句simhash的海明距離來判斷它們的相似度。例如把上面計算出來的“9 -9 1 -1 1 9”降維(某位大于0記為1,小于0記為0),得到的01串為:“1 0 1 0 1 1”,從而形成它們的simhash簽名。
其流程如下圖所示:?
應(yīng)用
- 每篇文檔得到SimHash簽名值后,接著計算兩個簽名的海明距離即可。根據(jù)經(jīng)驗值,對64位的 SimHash值,海明距離在3以內(nèi)的可認為相似度比較高。
- 海明距離的求法:異或時,只有在兩個比較的位不同時其結(jié)果是1 ,否則結(jié)果為0,兩個二進制“異或”后得到1的個數(shù)即為海明距離的大小。
舉個例子,上面我們計算到的“CSDN博客”的simhash簽名值為“1 0 1 0 1 1”,假定我們計算出另外一個短語的簽名值為“1 0 1 0 0 0”,那么根據(jù)異或規(guī)則,我們可以計算出這兩個簽名的海明距離為2,從而判定這兩個短語的相似度是比較高的。
換言之,現(xiàn)在問題轉(zhuǎn)換為:對于64位的SimHash值,我們只要找到海明距離在3以內(nèi)的所有簽名,即可找出所有相似的短語。
但關(guān)鍵是,如何將其擴展到海量數(shù)據(jù)呢?譬如如何在海量的樣本庫中查詢與其海明距離在3以內(nèi)的記錄呢?
- 一種方案是查找待查詢文本的64位simhash code的所有3位以內(nèi)變化的組合
- 大約需要四萬多次的查詢。
- 另一種方案是預(yù)生成庫中所有樣本simhash code的3位變化以內(nèi)的組合
- 大約需要占據(jù)4萬多倍的原始空間。
這兩種方案,要么時間復(fù)雜度高,要么空間復(fù)雜度復(fù)雜,能否有一種方案可以達到時空復(fù)雜度的絕佳平衡呢?答案是肯定的:
- 我們可以把 64 位的二進制simhash簽名均分成4塊,每塊16位。根據(jù)鴿巢原理(也稱抽屜原理),如果兩個簽名的海明距離在 3 以內(nèi),它們必有一塊完全相同。如下圖所示:?
- 然后把分成的4 塊中的每一個塊分別作為前16位來進行查找,建倒排索引。
具體如下圖所示:
如此,如果樣本庫中存有2^34(差不多10億)的simhash簽名,則每個table返回2^(34-16)=262144個候選結(jié)果,大大減少了海明距離的計算成本。
- 假設(shè)數(shù)據(jù)是均勻分布,16位的數(shù)據(jù),產(chǎn)生的像限為2^16個,則平均每個像限分布的文檔數(shù)則為2^34/2^16 = 2^(34-16)) ,四個塊返回的總結(jié)果數(shù)為 4* 262144 (大概 100 萬)。
- 這樣,原本需要比較10億次,經(jīng)過索引后,大概只需要處理100萬次。
(部分內(nèi)容及圖片參考自:http://grunt1223.iteye.com/blog/964564?,后續(xù)圖片會全部重畫)
問題實例
待續(xù)。
@復(fù)旦李斌:simhash不是google發(fā)明的,是princeton的人早在stoc02上發(fā)表的。google在www07上的那篇論文只是在網(wǎng)頁查重上應(yīng)用了下。事實上www07中的算法是stoc02中隨機超平面的一個極其巧妙的實現(xiàn),bit差異的期望正好等于原姶向量的余弦。
6.4 外排序
方法介紹
所謂外排序,顧名思義,即是在內(nèi)存外面的排序,因為當要處理的數(shù)據(jù)量很大,而不能一次裝入內(nèi)存時,此時只能放在讀寫較慢的外存儲器(通常是硬盤)上。
外排序通常采用的是一種“排序-歸并”的策略。
- 在排序階段,先讀入能放在內(nèi)存中的數(shù)據(jù)量,將其排序輸出到一個臨時文件,依此進行,將待排序數(shù)據(jù)組織為多個有序的臨時文件;
- 爾后在歸并階段將這些臨時文件組合為一個大的有序文件,也即排序結(jié)果。
假定現(xiàn)在有20個數(shù)據(jù)的文件A:{5 11 0 18 4 14 9 7 6 8 12 17 16 13 19 10 2 1 3 15},但一次只能使用僅裝4個數(shù)據(jù)的內(nèi)容,所以,我們可以每趟對4個數(shù)據(jù)進行排序,即5路歸并,具體方法如下述步驟:
-
我們先把“大”文件A,分割為a1,a2,a3,a4,a5等5個小文件,每個小文件4個數(shù)據(jù)
- a1文件為:5 11 0 18
- a2文件為:4 14 9 7
- a3文件為:6 8 12 17
- a4文件為:16 13 19 10
- a5文件為:2 1 3 15
-
然后依次對5個小文件分別進行排序
- a1文件完成排序后:0 5 11 18
- a2文件完成排序后:4 7 9 14
- a3文件完成排序后:6 8 12 17
- a4文件完成排序后:10 13 16 19
- a5文件完成排序后:1 2 3 15
-
最終多路歸并,完成整個排序
- 整個大文件A文件完成排序后:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
問題實例
1、給10^7個數(shù)據(jù)量的磁盤文件排序
輸入:給定一個文件,里面最多含有n個不重復(fù)的正整數(shù)(也就是說可能含有少于n個不重復(fù)正整數(shù)),且其中每個數(shù)都小于等于n,n=10^7。 輸出:得到按從小到大升序排列的包含所有輸入的整數(shù)的列表。 條件:最多有大約1MB的內(nèi)存空間可用,但磁盤空間足夠。且要求運行時間在5分鐘以下,10秒為最佳結(jié)果。
解法一:位圖方案
你可能會想到把磁盤文件進行歸并排序,但題目要求你只有1MB的內(nèi)存空間可用,所以,歸并排序這個方法不行。
熟悉位圖的朋友可能會想到用位圖來表示這個文件集合。例如正如編程珠璣一書上所述,用一個20位長的字符串來表示一個所有元素都小于20的簡單的非負整數(shù)集合,邊框用如下字符串來表示集合{1,2,3,5,8,13}:
0 1 1 1 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0上述集合中各數(shù)對應(yīng)的位置則置1,沒有對應(yīng)的數(shù)的位置則置0。
參考編程珠璣一書上的位圖方案,針對我們的10^7個數(shù)據(jù)量的磁盤文件排序問題,我們可以這么考慮,由于每個7位十進制整數(shù)表示一個小于1000萬的整數(shù)。我們可以使用一個具有1000萬個位的字符串來表示這個文件,其中,當且僅當整數(shù)i在文件中存在時,第i位為1。采取這個位圖的方案是因為我們面對的這個問題的特殊性:
所以,此問題用位圖的方案分為以下三步進行解決:
- 第一步,將所有的位都置為0,從而將集合初始化為空。
- 第二步,通過讀入文件中的每個整數(shù)來建立集合,將每個對應(yīng)的位都置為1。
- 第三步,檢驗每一位,如果該位為1,就輸出對應(yīng)的整數(shù)。
經(jīng)過以上三步后,產(chǎn)生有序的輸出文件。令n為位圖向量中的位數(shù)(本例中為1000 0000),程序可以用偽代碼表示如下:
//磁盤文件排序位圖方案的偽代碼 //copyright@ Jon Bentley //July、updated,2011.05.29。 //第一步,將所有的位都初始化為0 for i ={0,....n} bit[i]=0; //第二步,通過讀入文件中的每個整數(shù)來建立集合,將每個對應(yīng)的位都置為1。 for each i in the input file bit[i]=1; //第三步,檢驗每一位,如果該位為1,就輸出對應(yīng)的整數(shù)。 for i={0...n} if bit[i]==1 write i on the output file上述的位圖方案,共需要掃描輸入數(shù)據(jù)兩次,具體執(zhí)行步驟如下:
第一次,只處理1—4999999之間的數(shù)據(jù),這些數(shù)都是小于5000000的,對這些數(shù)進行位圖排序,只需要約5000000/8=625000Byte,也就是0.625M,排序后輸出。 第二次,掃描輸入文件時,只處理4999999-10000000的數(shù)據(jù)項,也只需要0.625M(可以使用第一次處理申請的內(nèi)存)。 因此,總共也只需要0.625M 位圖的的方法有必要強調(diào)一下,就是位圖的適用范圍為針對不重復(fù)的數(shù)據(jù)進行排序,若數(shù)據(jù)有重復(fù),位圖方案就不適用了。
不過很快,我們就將意識到,用此位圖方法,嚴格說來還是不太行,空間消耗10^7/8還是大于1M(1M=1024*1024空間,小于10^7/8)。
既然如果用位圖方案的話,我們需要約1.25MB(若每條記錄是8位的正整數(shù)的話,則10000000/(1024_1024_8) ~= 1.2M)的空間,而現(xiàn)在只有1MB的可用存儲空間,那么究竟該作何處理呢?
解法二:多路歸并
誠然,在面對本題時,還可以通過計算分析出可以用如2的位圖法解決,但實際上,很多的時候,我們都面臨著這樣一個問題,文件太大,無法一次性放入內(nèi)存中計算處理,那這個時候咋辦呢?分而治之,大而化小,也就是把整個大文件分為若干大小的幾塊,然后分別對每一塊進行排序,最后完成整個過程的排序。k趟算法可以在kn的時間開銷內(nèi)和n/k的空間開銷內(nèi)完成對最多n個小于n的無重復(fù)正整數(shù)的排序。
比如可分為2塊(k=2,1趟反正占用的內(nèi)存只有1.25/2M),1~4999999,和5000000~9999999。先遍歷一趟,首先排序處理1~4999999之間的整數(shù)(用5000000/8=625000個字的存儲空間來排序0~4999999之間的整數(shù)),然后再第二趟,對5000001~1000000之間的整數(shù)進行排序處理。
解法總結(jié)
1、關(guān)于本章中位圖和多路歸并兩種方案的時間復(fù)雜度及空間復(fù)雜度的比較,如下:
時間復(fù)雜度 空間復(fù)雜度位圖 O(N) 0.625M多位歸并 O(Nlogn) 1M(多路歸并,時間復(fù)雜度為O(k_n/k_logn/k ),嚴格來說,還要加上讀寫磁盤的時間,而此算法絕大部分時間也是浪費在這上面)
2、bit-map
適用范圍:可進行數(shù)據(jù)的快速查找,判重,刪除,一般來說數(shù)據(jù)范圍是int的10倍以下
基本原理及要點:使用bit數(shù)組來表示某些元素是否存在,比如8位電話號碼
擴展:bloom filter可以看做是對bit-map的擴展
舉一反三
1、已知某個文件內(nèi)包含一些電話號碼,每個號碼為8位數(shù)字,統(tǒng)計不同號碼的個數(shù)。 8位最多99 999 999,大概需要99m個bit,大概10幾m字節(jié)的內(nèi)存即可。
6.5 MapReduce
方法介紹
MapReduce是一種計算模型,簡單的說就是將大批量的工作(數(shù)據(jù))分解(MAP)執(zhí)行,然后再將結(jié)果合并成最終結(jié)果(REDUCE)。這樣做的好處是可以在任務(wù)被分解后,可以通過大量機器進行并行計算,減少整個操作的時間。但如果你要我再通俗點介紹,那么,說白了,Mapreduce的原理就是一個歸并排序。
適用范圍:數(shù)據(jù)量大,但是數(shù)據(jù)種類小可以放入內(nèi)存
基本原理及要點:將數(shù)據(jù)交給不同的機器去處理,數(shù)據(jù)劃分,結(jié)果歸約。
基礎(chǔ)架構(gòu)
想讀懂此文,讀者必須先要明確以下幾點,以作為閱讀后續(xù)內(nèi)容的基礎(chǔ)知識儲備:
所以,你現(xiàn)在,知道了什么是MapReduce,什么是hadoop,以及這兩者之間最簡單的聯(lián)系,而本文的主旨即是,一句話概括:在hadoop的框架上采取MapReduce的模式處理海量數(shù)據(jù)。下面,咱們可以依次深入學習和了解MapReduce和hadoop這兩個東西了。
MapReduce模式
前面說了,MapReduce是一種模式,一種什么模式呢?一種云計算的核心計算模式,一種分布式運算技術(shù),也是簡化的分布式編程模式,它主要用于解決問題的程序開發(fā)模型,也是開發(fā)人員拆解問題的方法。
Ok,光說不上圖,沒用。如下圖所示,MapReduce模式的主要思想是將自動分割要執(zhí)行的問題(例如程序)拆解成Map(映射)和Reduce(化簡)的方式,流程圖如下圖1所示:
在數(shù)據(jù)被分割后通過Map函數(shù)的程序?qū)?shù)據(jù)映射成不同的區(qū)塊,分配給計算機機群處理達到分布式運算的效果,在通過Reduce 函數(shù)的程序?qū)⒔Y(jié)果匯整,從而輸出開發(fā)者需要的結(jié)果。
MapReduce借鑒了函數(shù)式程序設(shè)計語言的設(shè)計思想,其軟件實現(xiàn)是指定一個Map函數(shù),把鍵值對(key/value)映射成新的鍵值對(key/value),形成一系列中間結(jié)果形式的key/value 對,然后把它們傳給Reduce(規(guī)約)函數(shù),把具有相同中間形式key的value合并在一起。Map和Reduce函數(shù)具有一定的關(guān)聯(lián)性。函數(shù)描述如表1 所示:
MapReduce致力于解決大規(guī)模數(shù)據(jù)處理的問題,因此在設(shè)計之初就考慮了數(shù)據(jù)的局部性原理,利用局部性原理將整個問題分而治之。MapReduce集群由普通PC機構(gòu)成,為無共享式架構(gòu)。在處理之前,將數(shù)據(jù)集分布至各個節(jié)點。處理時,每個節(jié)點就近讀取本地存儲的數(shù)據(jù)處理(map),將處理后的數(shù)據(jù)進行合并(combine)、排序(shuffle and sort)后再分發(fā)(至reduce節(jié)點),避免了大量數(shù)據(jù)的傳輸,提高了處理效率。無共享式架構(gòu)的另一個好處是配合復(fù)制(replication)策略,集群可以具有良好的容錯性,一部分節(jié)點的down機對集群的正常工作不會造成影響。
ok,你可以再簡單看看下副圖,整幅圖是有關(guān)hadoop的作業(yè)調(diào)優(yōu)參數(shù)及原理,圖的左邊是MapTask運行示意圖,右邊是ReduceTask運行示意圖:
如上圖所示,其中map階段,當map task開始運算,并產(chǎn)生中間數(shù)據(jù)后并非直接而簡單的寫入磁盤,它首先利用內(nèi)存buffer來對已經(jīng)產(chǎn)生的buffer進行緩存,并在內(nèi)存buffer中進行一些預(yù)排序來優(yōu)化整個map的性能。而上圖右邊的reduce階段則經(jīng)歷了三個階段,分別Copy->Sort->reduce。我們能明顯的看出,其中的Sort是采用的歸并排序,即merge sort。
問題實例
6.6 多層劃分
方法介紹
多層劃分法,本質(zhì)上還是分而治之的思想,因為元素范圍很大,不能利用直接尋址表,所以通過多次劃分,逐步確定范圍,然后最后在一個可以接受的范圍內(nèi)進行。
問題實例
1、2.5億個整數(shù)中找出不重復(fù)的整數(shù)的個數(shù),內(nèi)存空間不足以容納這2.5億個整數(shù)
分析:有點像鴿巢原理,整數(shù)個數(shù)為2^32,也就是,我們可以將這2^32個數(shù),劃分為2^8個區(qū)域(比如用單個文件代表一個區(qū)域),然后將數(shù)據(jù)分離到不同的區(qū)域,然后不同的區(qū)域在利用bitmap就可以直接解決了。也就是說只要有足夠的磁盤空間,就可以很方便的解決。
2、5億個int找它們的中位數(shù)
分析:首先我們將int劃分為2^16個區(qū)域,然后讀取數(shù)據(jù)統(tǒng)計落到各個區(qū)域里的數(shù)的個數(shù),之后我們根據(jù)統(tǒng)計結(jié)果就可以判斷中位數(shù)落到那個區(qū)域,同時知道這個區(qū)域中的第幾大數(shù)剛好是中位數(shù)。然后第二次掃描我們只統(tǒng)計落在這個區(qū)域中的那些數(shù)就可以了。
實際上,如果不是int是int64,我們可以經(jīng)過3次這樣的劃分即可降低到可以接受的程度。即可以先將int64分成2^24個區(qū)域,然后確定區(qū)域的第幾大數(shù),在將該區(qū)域分成2^20個子區(qū)域,然后確定是子區(qū)域的第幾大數(shù),然后子區(qū)域里的數(shù)的個數(shù)只有2^20,就可以直接利用direct addr table進行統(tǒng)計了。
方法介紹
什么是Bit-map
所謂的Bit-map就是用一個bit位來標記某個元素對應(yīng)的Value, 而Key即是該元素。由于采用了Bit為單位來存儲數(shù)據(jù),因此在存儲空間方面,可以大大節(jié)省。
來看一個具體的例子,假設(shè)我們要對0-7內(nèi)的5個元素(4,7,2,5,3)排序(這里假設(shè)這些元素沒有重復(fù))。那么我們就可以采用Bit-map的方法來達到排序的目的。要表示8個數(shù),我們就只需要8個Bit(1Bytes),首先我們開辟1Byte的空間,將這些空間的所有Bit位都置為0(如下圖:)
然后遍歷這5個元素,首先第一個元素是4,那么就把4對應(yīng)的位置為1(可以這樣操作 p+(i/8)|(0×01
?
?
?
6.8 Bloom filter
方法介紹
一、什么是Bloom Filter
Bloom Filter,被譯作稱布隆過濾器,是一種空間效率很高的隨機數(shù)據(jù)結(jié)構(gòu),Bloom filter可以看做是對bit-map的擴展,它的原理是:
- 當一個元素被加入集合時,通過K個Hash函數(shù)將這個元素映射成一個位陣列(Bit array)中的K個點,把它們置為1**。檢索時,我們只要看看這些點是不是都是1就(大約)知道集合中有沒有它了:
- 如果這些點有任何一個0,則被檢索元素一定不在;
- 如果都是1,則被檢索元素很可能在。
其可以用來實現(xiàn)數(shù)據(jù)字典,進行數(shù)據(jù)的判重,或者集合求交集。
但Bloom Filter的這種高效是有一定代價的:在判斷一個元素是否屬于某個集合時,有可能會把不屬于這個集合的元素誤認為屬于這個集合(false positive)。因此,Bloom Filter不適合那些“零錯誤”的應(yīng)用場合。而在能容忍低錯誤率的應(yīng)用場合下,Bloom Filter通過極少的錯誤換取了存儲空間的極大節(jié)省。
1.1、集合表示和元素查詢
下面我們具體來看Bloom Filter是如何用位數(shù)組表示集合的。初始狀態(tài)時,Bloom Filter是一個包含m位的位數(shù)組,每一位都置為0。
為了表達S={x1, x2,…,xn}這樣一個n個元素的集合,Bloom Filter使用k個相互獨立的哈希函數(shù)(Hash Function),它們分別將集合中的每個元素映射到{1,…,m}的范圍中。對任意一個元素x,第i個哈希函數(shù)映射的位置hi(x)就會被置為1(1≤i≤k)。注意,如果一個位置多次被置為1,那么只有第一次會起作用,后面幾次將沒有任何效果。在下圖中,k=3,且有兩個哈希函數(shù)選中同一個位置(從左邊數(shù)第五位,即第二個“1“處)。
在判斷y是否屬于這個集合時,我們對y應(yīng)用k次哈希函數(shù),如果所有hi(y)的位置都是1(1≤i≤k),那么我們就認為y是集合中的元素,否則就認為y不是集合中的元素。下圖中y1就不是集合中的元素(因為y1有一處指向了“0”位)。y2或者屬于這個集合,或者剛好是一個false positive。
1.2、錯誤率估計
前面我們已經(jīng)提到了,Bloom Filter在判斷一個元素是否屬于它表示的集合時會有一定的錯誤率(false positive rate),下面我們就來估計錯誤率的大小。在估計之前為了簡化模型,我們假設(shè)kn1, x2,…,xn}的所有元素都被k個哈希函數(shù)映射到m位的位數(shù)組中時,這個位數(shù)組中某一位還是0的概率是:
其中1/m表示任意一個哈希函數(shù)選中這一位的概率(前提是哈希函數(shù)是完全隨機的),(1-1/m)表示哈希一次沒有選中這一位的概率。要把S完全映射到位數(shù)組中,需要做kn次哈希。某一位還是0意味著kn次哈希都沒有選中它,因此這個概率就是(1-1/m)的kn次方。令p = e-kn/m是為了簡化運算,這里用到了計算e時常用的近似:
令ρ為位數(shù)組中0的比例,則ρ的數(shù)學期望E(ρ)= p’。在ρ已知的情況下,要求的錯誤率(false positive rate)為:
(1-ρ)為位數(shù)組中1的比例,(1-ρ)k就表示k次哈希都剛好選中1的區(qū)域,即false positive rate。上式中第二步近似在前面已經(jīng)提到了,現(xiàn)在來看第一步近似。p’只是ρ的數(shù)學期望,在實際中ρ的值有可能偏離它的數(shù)學期望值。M. Mitzenmacher已經(jīng)證明[2]?,位數(shù)組中0的比例非常集中地分布在它的數(shù)學期望值的附近。因此,第一步的近似得以成立。分別將p和p’代入上式中,得:
相比p’和f’,使用p和f通常在分析中更為方便。
1.3、最優(yōu)的哈希函數(shù)個數(shù)
既然Bloom Filter要靠多個哈希函數(shù)將集合映射到位數(shù)組中,那么應(yīng)該選擇幾個哈希函數(shù)才能使元素查詢時的錯誤率降到最低呢?這里有兩個互斥的理由:如果哈希函數(shù)的個數(shù)多,那么在對一個不屬于集合的元素進行查詢時得到0的概率就大;但另一方面,如果哈希函數(shù)的個數(shù)少,那么位數(shù)組中的0就多。為了得到最優(yōu)的哈希函數(shù)個數(shù),我們需要根據(jù)上一小節(jié)中的錯誤率公式進行計算。
先用p和f進行計算。注意到f = exp(k ln(1 ? e?kn/m)),我們令g = k ln(1 ? e?kn/m),只要讓g取到最小,f自然也取到最小。由于p = e-kn/m,我們可以將g寫成
根據(jù)對稱性法則可以很容易看出當p = 1/2,也就是k = ln2· (m/n)時,g取得最小值。在這種情況下,最小錯誤率f等于(1/2)k≈ (0.6185)m/n。另外,注意到p是位數(shù)組中某一位仍是0的概率,所以p = 1/2對應(yīng)著位數(shù)組中0和1各一半。換句話說,要想保持錯誤率低,最好讓位數(shù)組有一半還空著。
需要強調(diào)的一點是,p = 1/2時錯誤率最小這個結(jié)果并不依賴于近似值p和f。同樣對于f’ = exp(k ln(1 ? (1 ? 1/m)kn)),g’ = k ln(1 ? (1 ? 1/m)kn),p’ = (1 ? 1/m)kn,我們可以將g’寫成
同樣根據(jù)對稱性法則可以得到當p’ = 1/2時,g’取得最小值。
1.4、位數(shù)組的大小
下面我們來看看,在不超過一定錯誤率的情況下,Bloom Filter至少需要多少位才能表示全集中任意n個元素的集合。假設(shè)全集中共有u個元素,允許的最大錯誤率為?,下面我們來求位數(shù)組的位數(shù)m。
假設(shè)X為全集中任取n個元素的集合,F(X)是表示X的位數(shù)組。那么對于集合X中任意一個元素x,在s = F(X)中查詢x都能得到肯定的結(jié)果,即s能夠接受x。顯然,由于Bloom Filter引入了錯誤,s能夠接受的不僅僅是X中的元素,它還能夠? (u - n)個false positive。因此,對于一個確定的位數(shù)組來說,它能夠接受總共n + ? (u - n)個元素。在n + ? (u - n)個元素中,s真正表示的只有其中n個,所以一個確定的位數(shù)組可以表示
個集合。m位的位數(shù)組共有2m個不同的組合,進而可以推出,m位的位數(shù)組可以表示
個集合。全集中n個元素的集合總共有
個,因此要讓m位的位數(shù)組能夠表示所有n個元素的集合,必須有
即:
上式中的近似前提是n和?u相比很小,這也是實際情況中常常發(fā)生的。根據(jù)上式,我們得出結(jié)論:在錯誤率不大于?的情況下,m至少要等于n log2(1/?)才能表示任意n個元素的集合。
上一小節(jié)中我們曾算出當k = ln2· (m/n)時錯誤率f最小,這時f = (1/2)k= (1/2)mln2 / n。現(xiàn)在令f≤?,可以推出
這個結(jié)果比前面我們算得的下界n log2(1/?)大了log2e≈ 1.44倍。這說明在哈希函數(shù)的個數(shù)取到最優(yōu)時,要讓錯誤率不超過?,m至少需要取到最小值的1.44倍。
問題實例
1、給你A,B兩個文件,各存放50億條URL,每條URL占用64字節(jié),內(nèi)存限制是4G,讓你找出A,B文件共同的URL。如果是三個乃至n個文件呢?
分析:如果允許有一定的錯誤率,可以使用Bloom filter,4G內(nèi)存大概可以表示340億bit。將其中一個文件中的url使用Bloom filter映射為這340億bit,然后挨個讀取另外一個文件的url,檢查是否與Bloom filter,如果是,那么該url應(yīng)該是共同的url(注意會有一定的錯誤率)。”
6.9 Trie樹
方法介紹
1.1、什么是Trie樹
Trie樹,即字典樹,又稱單詞查找樹或鍵樹,是一種樹形結(jié)構(gòu)。典型應(yīng)用是用于統(tǒng)計和排序大量的字符串(但不僅限于字符串),所以經(jīng)常被搜索引擎系統(tǒng)用于文本詞頻統(tǒng)計。它的優(yōu)點是最大限度地減少無謂的字符串比較,查詢效率比較高。
Trie的核心思想是空間換時間,利用字符串的公共前綴來降低查詢時間的開銷以達到提高效率的目的。
它有3個基本性質(zhì):
1.2、樹的構(gòu)建
咱們先來看一個問題:假如現(xiàn)在給你10萬個長度不超過10的單詞,對于每一個單詞,我們要判斷它出沒出現(xiàn)過,如果出現(xiàn)了,求第一次出現(xiàn)在第幾個位置。對于這個問題,我們該怎么解決呢?
如果我們用最傻的方法,對于每一個單詞,我們都要去查找它前面的單詞中是否有它。那么這個算法的復(fù)雜度就是O(n^2)。顯然對于10萬的范圍難以接受。
換個思路想:
- 假設(shè)我要查詢的單詞是abcd,那么在它前面的單詞中,以b,c,d,f之類開頭的顯然不必考慮,而只要找以a開頭的中是否存在abcd就可以了。
- 同樣的,在以a開頭中的單詞中,我們只要考慮以b作為第二個字母的,一次次縮小范圍和提高針對性,這樣一個樹的模型就漸漸清晰了。
即如果現(xiàn)在有b,abc,abd,bcd,abcd,efg,hii 這6個單詞,我們可以構(gòu)建一棵如下圖所示的樹:
如上圖所示,對于每一個節(jié)點,從根遍歷到他的過程就是一個單詞,如果這個節(jié)點被標記為紅色,就表示這個單詞存在,否則不存在。
那么,對于一個單詞,只要順著他從根走到對應(yīng)的節(jié)點,再看這個節(jié)點是否被標記為紅色就可以知道它是否出現(xiàn)過了。把這個節(jié)點標記為紅色,就相當于插入了這個單詞。
這樣一來我們查詢和插入可以一起完成,所用時間僅僅為單詞長度(在這個例子中,便是10)。這就是一棵trie樹。
我們可以看到,trie樹每一層的節(jié)點數(shù)是26^i級別的。所以為了節(jié)省空間,我們還可以用動態(tài)鏈表,或者用數(shù)組來模擬動態(tài)。而空間的花費,不會超過單詞數(shù)×單詞長度。
1.3、查詢
Trie樹是簡單但實用的數(shù)據(jù)結(jié)構(gòu),通常用于實現(xiàn)字典查詢。我們做即時響應(yīng)用戶輸入的AJAX搜索框時,就是Trie開始。本質(zhì)上,Trie是一顆存儲多個字符串的樹。相鄰節(jié)點間的邊代表一個字符,這樣樹的每條分支代表一則子串,而樹的葉節(jié)點則代表完整的字符串。和普通樹不同的地方是,相同的字符串前綴共享同一條分支。
下面,再舉一個例子。給出一組單詞,inn, int, at, age, adv, ant, 我們可以得到下面的Trie:
可以看出:
- 每條邊對應(yīng)一個字母。
- 每個節(jié)點對應(yīng)一項前綴。葉節(jié)點對應(yīng)最長前綴,即單詞本身。
- 單詞inn與單詞int有共同的前綴“in”, 因此他們共享左邊的一條分支,root->i->in。同理,ate, age, adv, 和ant共享前綴"a",所以他們共享從根節(jié)點到節(jié)點"a"的邊。
查詢操縱非常簡單。比如要查找int,順著路徑i -> in -> int就找到了。
搭建Trie的基本算法也很簡單,無非是逐一把每則單詞的每個字母插入Trie。插入前先看前綴是否存在。如果存在,就共享,否則創(chuàng)建對應(yīng)的節(jié)點和邊。比如要插入單詞add,就有下面幾步:
問題實例
1、一個文本文件,大約有一萬行,每行一個詞,要求統(tǒng)計出其中最頻繁出現(xiàn)的前10個詞,請給出思想,給出時間復(fù)雜度分析
提示:用trie樹統(tǒng)計每個詞出現(xiàn)的次數(shù),時間復(fù)雜度是O(nle)(le表示單詞的平均長度),然后是找出出現(xiàn)最頻繁的前10個詞。當然,也可以用堆來實現(xiàn),時間復(fù)雜度是O(nlg10)。所以總的時間復(fù)雜度,是O(nle)與O(nlg10)中較大的哪一個。
2、尋找熱門查詢
原題:搜索引擎會通過日志文件把用戶每次檢索使用的所有檢索串都記錄下來,每個查詢串的長度為1-255字節(jié)。假設(shè)目前有一千萬個記錄,這些查詢串的重復(fù)讀比較高,雖然總數(shù)是1千萬,但是如果去除重復(fù)和,不超過3百萬個。一個查詢串的重復(fù)度越高,說明查詢它的用戶越多,也就越熱門。請你統(tǒng)計最熱門的10個查詢串,要求使用的內(nèi)存不能超過1G。
提示:利用trie樹,關(guān)鍵字域存該查詢串出現(xiàn)的次數(shù),沒有出現(xiàn)為0。最后用10個元素的最小推來對出現(xiàn)頻率進行排序。
6.10 數(shù)據(jù)庫
方法介紹
當遇到大數(shù)據(jù)量的增刪改查時,一般把數(shù)據(jù)裝進數(shù)據(jù)庫中,從而利用數(shù)據(jù)的設(shè)計實現(xiàn)方法,對海量數(shù)據(jù)的增刪改查進行處理。
?
6.11 倒排索引
方法介紹
倒排索引是一種索引方法,被用來存儲在全文搜索下某個單詞在一個文檔或者一組文檔中的存儲位置的映射,常被應(yīng)用于搜索引擎和關(guān)鍵字查詢的問題中。
以英文為例,下面是要被索引的文本:
T0 = "it is what it is" T1 = "what is it" T2 = "it is a banana"我們就能得到下面的反向文件索引:
"a": {2} "banana": {2} "is": {0, 1, 2} "it": {0, 1, 2} "what": {0, 1}檢索的條件"what","is"和"it"將對應(yīng)集合的交集。
正向索引開發(fā)出來用來存儲每個文檔的單詞的列表。正向索引的查詢往往滿足每個文檔有序頻繁的全文查詢和每個單詞在校驗文檔中的驗證這樣的查詢。在正向索引中,文檔占據(jù)了中心的位置,每個文檔指向了一個它所包含的索引項的序列。也就是說文檔指向了它包含的那些單詞,而反向索引則是單詞指向了包含它的文檔,很容易看到這個反向的關(guān)系。
問題實例
1、文檔檢索系統(tǒng),查詢那些文件包含了某單詞,比如常見的學術(shù)論文的關(guān)鍵字搜索
提示:建倒排索引。
6.15 本章習題
本章海量數(shù)據(jù)的習題
1?有100W個關(guān)鍵字,長度小于等于50字節(jié)。用高效的算法找出top10的熱詞,并對內(nèi)存的占用不超過1MB。
提示:老題,與caopengcs討論后,得出具體思路為:
- 先把100W個關(guān)鍵字hash映射到小文件,根據(jù)題意,100W_50B = 50_10^6B = 50M,而內(nèi)存只有1M,故干脆搞一個hash函數(shù) % 50,分解成50個小文件;
- 針對對每個小文件依次運用hashmap(key,value)完成每個key的value次數(shù)統(tǒng)計,后用堆找出每個小文件中value次數(shù)最大的top 10; -最后依次對每兩小文件的top 10歸并,得到最終的top 10。
此外,很多細節(jié)需要注意下,舉個例子,如若hash映射后導致分布不均的話,有的小文件可能會超過1M,故為保險起見,你可能會說根據(jù)數(shù)據(jù)范圍分解成50~500或更多的小文件,但到底是多少呢?我覺得這不重要,勿糾結(jié)答案,雖準備在平時,但關(guān)鍵還是看臨場發(fā)揮,保持思路清晰關(guān)注細節(jié)即可。
2
單機5G內(nèi)存,磁盤200T的數(shù)據(jù),分別為字符串,然后給定一個字符串,判斷這200T數(shù)據(jù)里面有沒有這個字符串,怎么做? 如果查詢次數(shù)會非常的多, 怎么預(yù)處理?
提示:如果數(shù)據(jù)是200g且允許少許誤差的話,可以考慮用布隆過濾器Bloom Filter。但本題是200T,得另尋良策,具體解法請讀者繼續(xù)思考。
3
現(xiàn)在有一個大文件,文件里面的每一行都有一個group標識(group很多,但是每個group的數(shù)據(jù)量很小),現(xiàn)在要求把這個大文件分成十個小文件,要求:
- 1、同一個group的必須在一個文件里面;
- 2、切分之后,要求十個小文件的數(shù)據(jù)量盡可能均衡。
7
服務(wù)器內(nèi)存1G,有一個2G的文件,里面每行存著一個QQ號(5-10位數(shù)),怎么最快找出出現(xiàn)過最多次的QQ號。
8
盡量高效的統(tǒng)計一片英文文章(總單詞數(shù)目)里出現(xiàn)的所有英文單詞,按照在文章中首次出現(xiàn)的順序打印輸出該單詞和它的出現(xiàn)次數(shù)。
9
在人人好友里,A和B是好友,B和C是好友,如果A 和C不是好友,那么C是A的二度好友,在一個有10萬人的數(shù)據(jù)庫里,如何在時間0(n)里,找到某個人的十度好友。
12
海量記錄,記錄形式如下: TERMID URLNOCOUNT urlno1 urlno2 ..., urlnon,請問怎么考慮資源和時間這兩個因素,實現(xiàn)快速查詢?nèi)我鈨蓚€記錄的交集,并集等,設(shè)計相關(guān)的數(shù)據(jù)結(jié)構(gòu)和算法。
14
有一億個整數(shù),請找出最大的1000個,要求時間越短越好,空間占用越少越好。
18
10億個int型整數(shù),如何找出重復(fù)出現(xiàn)的數(shù)字。
19
有2G的一個文本文檔,文件每行存儲的是一個句子,每個單詞是用空格隔開的。問:輸入一個句子,如何找到和它最相似的前10個句子。
提示:可用倒排文檔。
20
某家視頻網(wǎng)站,每天有上億的視頻被觀看,現(xiàn)在公司要請研發(fā)人員找出最熱門的視頻。 該問題的輸入可以簡化為一個字符串文件,每一行都表示一個視頻id,然后要找出出現(xiàn)次數(shù)最多的前100個視頻id,將其輸出,同時輸出該視頻的出現(xiàn)次數(shù)。
- 1.假設(shè)每天的視頻播放次數(shù)為3億次,被觀看的視頻數(shù)量為一百萬個,每個視頻ID的長度為20字節(jié),限定使用的內(nèi)存為1G。請簡述做法,再寫代碼。
- 2.假設(shè)每個月的視頻播放次數(shù)為100億次,被觀看的視頻數(shù)量為1億,每個視頻ID的長度為20字節(jié),一臺機器被限定使用的內(nèi)存為1G。
提示:萬變不離其宗,分而治之/Hash映射 + Hash統(tǒng)計 + 堆/快速/歸并排序。
21
有一個log文件,里面記錄的格式為:
QQ號 時間 flag123456 14:00:00 0 123457 14:00:01 1其中flag=0表示登錄 flag=1表示退出
問:統(tǒng)計一天平均在線的QQ數(shù)。
22
一個文本,一萬行,每行一個詞,統(tǒng)計出現(xiàn)頻率最高的前10個詞(詞的平均長度為Len)。并分析時間復(fù)雜度。
23
在一個文件中有 10G 個整數(shù),亂序排列,要求找出中位數(shù)。內(nèi)存限制為 2G。只寫出思路即可。
24
一個url指向的頁面里面有另一個url,最終有一個url指向之前出現(xiàn)過的url或空,這兩種情形都定義為null。這樣構(gòu)成一個單鏈表。給兩條這樣單鏈表,判斷里面是否存在同樣的url。url以億級計,資源不足以hash。
25
一個1G大小的一個文件,里面每一行是一個詞,詞的大小不超過16字節(jié),內(nèi)存限制大小是1M。返回頻數(shù)最高的100個詞。
26
1000萬字符串,其中有些是重復(fù)的,需要把重復(fù)的全部去掉,保留沒有重復(fù)的字符串。請怎么設(shè)計和實現(xiàn)?
27?有10個文件,每個文件1G,每個文件的每一行都存放的是用戶的query,每個文件的query都可能重復(fù)。要你按照query的頻度排序。
28
現(xiàn)有一200M的文本文件,里面記錄著IP地址和對應(yīng)地域信息,如
202.100.83.56 北京 北京大學202.100.83.120 北京 人民大學 202.100.83.134 北京 中國青年政治學院 211.93.120.45 長春市 長春大學 211.93.120.129 吉林市 吉林大學 211.93.120.200 長春 長春KTV現(xiàn)有6億個IP地址,請編寫程序,讀取IP地址便轉(zhuǎn)換成IP地址相對應(yīng)的城市,要求有較好的時間復(fù)雜度和空間復(fù)雜度。
(本篇完~)
?
總結(jié)
以上是生活随笔為你收集整理的转:6.1海量数据处理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 逸管家:把握不同行业生命周期,选择正确商
- 下一篇: springboot系列(十)sprin