一、從set/map談到hashtable/hash_map/hash_set算法
稍後本文第二部分中將屢次提到hash_map/hash_set,下面稍稍介紹下這些容器,以做爲基礎準備。通常來講,STL容器分兩種:數據庫
序列式容器(vector/list/deque/stack/queue/heap),數據結構
關聯式容器。關聯式容器又分爲set(集合)和map(映射表)兩大類,以及這兩大類的衍生體multiset(多鍵集合)和multimap(多鍵映射表),這些容器均以RB-tree完成。此外,還有第3類關聯式容器,如hashtable(散列表),以及以hashtable爲底層機制完成的hash_set(散列集合)/hash_map(散列映射表)/hash_multiset(散列多鍵集合)/hash_multimap(散列多鍵映射表)。也就是說,set/map/multiset/multimap都內含一個RB-tree,而hash_set/hash_map/hash_multiset/hash_multimap都內含一個hashtable。架構
所謂關聯式容器,相似關聯式數據庫,每筆數據或每一個元素都有一個鍵值(key)和一個實值(value),即所謂的Key-Value(鍵-值對)。當元素被插入到關聯式容器中時,容器內部結構(RB-tree/hashtable)便依照其鍵值大小,以某種特定規則將這個元素放置於適當位置。分佈式
包括在非關聯式數據庫中,好比,在MongoDB內,文檔(document)是最基本的數據組織形式,每一個文檔也是以Key-Value(鍵-值對)的方式組織起來。一個文檔能夠有多個Key-Value組合,每一個Value能夠是不一樣的類型,好比String、Integer、List等等。 { "name" : "July", "sex" : "male", "age" : 23 } 函數
set/map/multiset/multimap:搜索引擎
set,同map同樣,全部元素都會根據元素的鍵值自動被排序,由於set/map二者的全部各類操做,都只是轉而調用RB-tree的操做行爲,不過,值得注意的是,二者都不容許兩個元素有相同的鍵值。
不一樣的是:set的元素不像map那樣能夠同時擁有實值(value)和鍵值(key),set元素的鍵值就是實值,實值就是鍵值,而map的全部元素都是pair,同時擁有實值(value)和鍵值(key),pair的第一個元素被視爲鍵值,第二個元素被視爲實值。
至於multiset/multimap,他們的特性及用法和set/map徹底相同,惟一的差異就在於它們容許鍵值重複,即全部的插入操做基於RB-tree的insert_equal()而非insert_unique()。url
hash_set/hash_map/hash_multiset/hash_multimap:spa
hash_set/hash_map,二者的一切操做都是基於hashtable之上。不一樣的是,hash_set同set同樣,同時擁有實值和鍵值,且實質就是鍵值,鍵值就是實值,而hash_map同map同樣,每個元素同時擁有一個實值(value)和一個鍵值(key),因此其使用方式,和上面的map基本相同。但因爲hash_set/hash_map都是基於hashtable之上,因此不具有自動排序功能。爲何?由於hashtable沒有自動排序功能。
至於hash_multiset/hash_multimap的特性與上面的multiset/multimap徹底相同,惟一的差異就是它們hash_multiset/hash_multimap的底層實現機制是hashtable(而multiset/multimap,上面說了,底層實現機制是RB-tree),因此它們的元素都不會被自動排序,不過也都容許鍵值重複。日誌
因此,綜上,說白了,什麼樣的結構決定其什麼樣的性質,由於set/map/multiset/multimap都是基於RB-tree之上,因此有自動排序功能,而hash_set/hash_map/hash_multiset/hash_multimap都是基於hashtable之上,因此不含有自動排序功能,至於加個前綴multi_無非就是容許鍵值重複而已。
二、尋找熱門查詢:搜索引擎會經過日誌文件把用戶每次檢索使用的全部檢索串都記錄下來,每一個查詢串的長度爲1-255字節。
假設目前有一千萬個記錄(這些查詢串的重複度比較高,雖然總數是1千萬,但若是除去重複後,不超過3百萬個。一個查詢串的重複度越高,說明查詢它的用戶越多,也就是越熱門),請你統計最熱門的10個查詢串,要求使用的內存不能超過1G。
解決方案:雖然有一千萬個Query,可是因爲重複度比較高,所以事實上只有300萬的Query,每一個Query255Byte,(300w*255B<1G,能夠把數據所有讀入內存),所以咱們能夠考慮把他們都放進內存中去,而如今只是須要一個合適的數據結構,在這裏,Hash Table絕對是咱們優先的選擇。因此咱們放棄分而治之/hash映射的步驟,直接上hash統計,而後排序。So:
堆排序思路:「維護k個元素的最小堆,即用容量爲k的最小堆存儲最早遍歷到的k個數,並假設它們便是最大的k個數,建堆費時O(k),並調整堆(費時O(logk))後,有k1>k2>...kmin(kmin設爲小頂堆中最小元素)。繼續遍歷數列,每次遍歷一個元素x,與堆頂元素比較,若x>kmin,則更新堆(用時logk),不然不更新堆。這樣下來,總費時O(k*logk+(n-k)*logk)=O(n*logk)。此方法得益於在堆中,查找等各項操做時間複雜度均爲logk。
三、有一個1G大小的一個文件,裏面每一行是一個詞,詞的大小不超過16字節,內存限制大小是1M。返回頻數最高的100個詞。
解決方案:(1G=5000*200k,將文件分紅5000個小文件,每一個文件200k)
1)分而治之/hash映射:順序讀文件中,對於每一個詞x,取hash(x)%5000,而後按照該值存到5000個小文件(記爲x0,x1,...x4999)中。這樣每一個文件大概是200k左右,而且每一個文件存放的都是具備同樣hash值的詞。若是其中的有的文件超過了1M大小,還能夠按照相似的方法繼續往下分,直到分解獲得的小文件的大小都不超過1M。
2)hash統計:對每一個小文件,採用trie樹/hash_map等統計每一個文件中出現的詞以及相應的頻率。
3)堆/歸併排序:取出出現頻率最大的100個詞(能夠用含100個結點的最小堆),並把100個詞及相應的頻率存入文件,這樣又獲得了5000個文件。最後就是把這5000個文件進行歸併(相似於歸併排序)的過程了。
1)堆排序:在每臺電腦上求出TOP10,能夠採用包含10個元素的堆完成(TOP10小,用最大堆,TOP10大,用最小堆)。好比求TOP10大,咱們首先取前10個元素調整成最小堆,若是發現,而後掃描後面的數據,並與堆頂元素比較,若是比堆頂元素大,那麼用該元素替換堆頂,而後再調整爲最小堆。最後堆中的元素就是TOP10大。
2)求出每臺電腦上的TOP10後,而後把這100臺電腦上的TOP10組合起來,共1000個數據,再利用上面相似的方法求出TOP10就能夠了。
上述第4題的此解法,經讀者反應有問題,如舉個例子如求2個文件中的top2,照上述算法,若是第一個文件裏有:a 49次b 50次c 2次d 1次第二個文件裏有:a 9次b 1次c 11次d 10次雖然第一個文件裏出來top2是b(50次),a(49次),第二個文件裏出來top2是c(11次),d(10次),而後2個top2:b(50次)a(49次)與c(11次)d(10次)歸併,則算出全部的文件的top2是b(50 次),a(49 次),但其實是a(58 次),b(51 次)。是否真是如此呢?若真如此,那做何解決呢?首先,先把全部的數據遍歷一遍作一次hash(保證相同的數據條目劃分到同一臺電腦上進行運算),而後根據hash結果從新分佈到100臺電腦中,接下來的算法按照以前的便可。最後因爲a可能出如今不一樣的電腦,各有必定的次數,再對每一個相同條目進行求和(因爲上一步驟中hash以後,也方便每臺電腦只須要對本身分到的條目內進行求和,不涉及到別的電腦,規模縮小)。
1)hash映射:順序讀取10個文件,按照hash(query)%10的結果將query寫入到另外10個文件(記爲)中。這樣新生成的文件每一個的大小大約也1G(假設hash函數是隨機的)。
2)hash統計:找一臺內存在2G左右的機器,依次對用hash_map(query, query_count)來統計每一個query出現的次數。注:hash_map(query,query_count)是用來統計每一個query的出現次數,不是存儲他們的值,出現一次,則count+1。
3)堆/快速/歸併排序:利用快速/堆/歸併排序按照出現次數進行排序,將排序好的query和對應的query_cout輸出到文件中,這樣獲得了10個排好序的文件(記爲)。最後,對這10個文件進行歸併排序(內排序與外排序相結合)。
方案2:通常query的總量是有限的,只是重複的次數比較多而已,可能對於全部的query,一次性就能夠加入到內存了。這樣,咱們就能夠採用trie樹/hash_map等直接來統計每一個query出現的次數,而後按出現次數作快速/堆/歸併排序就能夠了。
方案3:與方案1相似,但在作完hash,分紅多個文件後,能夠交給多個文件來處理,採用分佈式的架構來處理(好比MapReduce),最後再進行合併。
六、 給定a、b兩個文件,各存放50億個url,每一個url各佔64字節,內存限制是4G,讓你找出a、b文件共同的url?
能夠估計每一個文件安的大小爲5G×64=320G,遠遠大於內存限制的4G。因此不可能將其徹底加載到內存中處理。考慮採起分而治之的方法。
1)分而治之/hash映射:遍歷文件a,對每一個url求取,而後根據所取得的值將url分別存儲到1000個小文件(記爲
)中。這樣每一個小文件的大約爲300M。遍歷文件b,採起和a相同的方式將url分別存儲到1000小文件中(記爲
)。這樣處理後,全部可能相同的url都在對應的小文件(
)中,不對應的小文件不可能有相同的url。而後咱們只要求出1000對小文件中相同的url便可。
2)hash統計:求每對小文件中相同的url時,能夠把其中一個小文件的url存儲到hash_set中。而後遍歷另外一個小文件的每一個url,看其是否在剛纔構建的hash_set中,若是是,那麼就是共同的url,存到文件裏面就能夠了。
七、怎麼在海量數據中找出重複次數最多的一個?
1)先作hash映射,求模將大文件中的內容映射到小文件中
2)而後hash統計,求出每一個小文件中重複次數最多的一個,並記錄重複次數。
3)最後快速排序/堆排序/歸併排序,找出上一步求出的數據中重複次數最多的一個就是所求
八、上千萬或上億數據(有重複),統計其中出現次數最多的錢N個數據。
1)數據若是能夠直接所有放進內存,就不用hash映射成多個小文件。
2)採用hash_map/搜索二叉樹/紅黑樹等來進行統計次數。
3)而後就是取出前N個出現次數最多的數據了,能夠用第2題提到的堆機制完成。