讀 【99%的海量數據處理面試題】 個人理解與歸納

處理海量數據六大方法【原文】:

  1. 分而治之/hash映射 + hash統計 + 堆/快速/歸併排序;
  2. 雙層桶劃分
  3. Bloom filter/Bitmap;
  4. Trie樹/數據庫/倒排索引;
  5. 外排序;
  6. 分佈式處理之Hadoop/Mapreduce

【0】從set/map談起得基本數據結構類型、分類與特點

STL(Standard Template Library):

  1. 序列容器:vector, list, deque, string.
  2. 關聯容器:set, multiset, map, mulmap, hash_set, hash_map, hash_multiset, hash_multimap
  3. 其他的雜項:stack, queue, valarray, bitset

  • set/map/multiset/multimap都是基於RB-tree之上,所以有自動排序功能;
  • hash_set/hash_map/hash_multiset/hash_multimap都是基於hashtable之上,所以不含有自動排序功能;
  • 加個前綴multi_無非就是允許鍵值重複而已;
  • set:內部元素唯一,用一棵平衡樹結構來存儲,因此遍歷的時候就排序了,查找也比較快的哦。
  • map:一對一的映射的結合,key不能重複
  • 適配器其實就是一個接口轉換裝置,包括容器適配器、迭代器適配器和函數適配器
      eg:我們已有的容器(比如vector、list、deque)就是設備,這個設備支持的操作很多,比如插入,刪除,迭代器訪問等等。
          而我們希望這個容器表現出來的是棧的樣子:先進後出,入棧出棧等等,
          此時,我們沒有必要重新動手寫一個新的數據結構,而是把原來的容器重新封裝一下,改變它的接口,就能把它當做棧使用了。
     
    •     "age" : 23 } 
  • 在MongoDB內,文檔(document)是最基本的數據組織形式,每個文檔也是以Key-Value(鍵-值對)的方式組織起來。
    一個文檔可以有多個Key-Value組合,每個Value可以是不同的類型,比如String、Integer、List等等。 
    { "name" : "July",     "sex" : "male",      "age" : 23 }
     

標準容器類 說明
順序性容器
vector 從後面快速的插入與刪除,直接訪問任何元素
deque 從前面或後面快速的插入與刪除,直接訪問任何元素
list 雙鏈表,從任何地方快速插入與刪除
關聯容器
set 快速查找,不允許重複值
multiset 快速查找,允許重複值
map 一對多映射,基於關鍵字快速查找,不允許重複值
multimap 一對多映射,基於關鍵字快速查找,允許重複值
容器適配器
stack 後進先出
queue 先進先出
priority_queue 最高優先級元素總是第一個出列

【1】密匙一、分而治之/Hash映射 + Hash_map統計 + 堆/快速/歸併排序

【eg-1】:把整個大文件映射爲1000個小文件,再找出每個小文中出現頻率最大的IP及相應的頻率。然後再在這1000個最大的IP中,找出那個頻率最大的IP,即爲所求;
【eg-2】: 300萬個查詢字符串中統計最熱門的10個查詢
假設有一千萬個Query,但是由於重複度比較高,因此事實上只有300萬的Query,每個Query255Byte,因此我們可以考慮把他們都放進內存中去!300萬個字符串假設沒有重複,都是最大長度,那麼最多佔用內存3M*1K/4=0.75G。所以可以將所有字符串都存放在內存中進行處理在這裏,   HashTable絕對是我們優先的選擇。所以我們放棄分而治之/hash映射的步驟,直接上hash統計,然後排序。So,針對此類典型的TOP K問      題,採取的對策往往是:hashmap + 堆。如下所示:
1.hash_map統計:先對這批海量數據預處理。

具體方法是:維護一個Key爲Query字串,Value爲該Query出現次數的HashTable,即hash_map(Query,Value),

2.堆排序:第二步、藉助堆這個數據結構,找出Top K,時間複雜度爲N‘logK。

即藉助堆結構,我們可以在log量級的時間內查找和調整/移動

【2】密匙二、多層劃分

其實本質上還是分而治之的思想,重在“分”的技巧上!
【eg-1】:2.5億個整數中找出不重複的整數的個數,內存空間不足以容納這2.5億個整數。
   有點像鴿巢原理,
整數個數爲2^32,所以我們可以將這2^32個數劃分爲2^8個區域(比如用單個文件代表一個區域),
然後將數據分離到不同的區域;
然後不同的區域在利用bitmap就可以直接解決了;
也就是說只要有足夠的磁盤(不是內存)空間,就可以很方便的解

【3】密匙三、Bloom filter/Bitmap

Bloom Filter是一種空間效率很高的隨機數據結構,它利用位數組很簡潔地表示一個集合,並能判斷一個元素是否屬於這個集合,【參考】它的原理是:
當一個元素被加入集合時,通過K個Hash函數將這個元素映射成一個位陣列(Bit array)中的K個點,把它們置爲1。
檢索時,我們只要看看這些點是不是都是1就(大約)知道集合中有沒有它了:
如果這些點有任何一個0,則被檢索元素一定不在;
如果都是1,則被檢索元素很可能在。這就是布隆過濾器的基本思想。
不適合那些“零錯誤”的應用場合。
而在能容忍低錯誤率的應用場合下,Bloom Filter通過極少的錯誤換取了存儲空間的極大節省。
【eg-1】:假設要你寫一個網絡蜘蛛(web crawler)。由於網絡間的鏈接錯綜複雜,蜘蛛在網絡間爬行很可能會形成“環”。爲了避免形成“環”,就需要知道蜘蛛已經訪問過那些URL。給一個URL,怎樣知道蜘蛛是否已經訪問過呢?稍微想想,有如下幾種方案:
   1. 將訪問過的URL保存到數據庫。
   2. 用HashSet將訪問過的URL保存起來。那隻需接近O(1)的代價就可以查到一個URL是否被訪問過了。
   3. URL經過MD5或SHA-1等單向哈希後再保存到HashSet或數據庫。
   4. Bit-Map方法。建立一個BitSet,將每個URL經過一個哈希函數映射到某一位。

   方法1~3都是將訪問過的URL完整保存,方法4則只標記URL的一個映射位。

     以上方法在數據量較小的情況下都能完美解決問題,但是當數據量變得非常龐大時問題就來了。

方法1的缺點:數據量變得非常龐大後關係型數據庫查詢的效率會變得很低。而且每來一個URL就啓動一次數據庫查詢是不是太小題大做了?
方法2的缺點:太消耗內存。隨着URL的增多,佔用的內存會越來越多。就算只有1億個URL,每個URL只算50個字符,就需要5GB內存。
方法3的缺點:由於字符串經過MD5處理後的信息摘要長度只有128Bit,SHA-1處理後也只有160Bit,因此方法3比方法2節省了好幾倍的內存。
方法4的缺點:內存消耗相對較少的,但缺點是單一哈希函數發生衝突的概率太高。還記得數據結構課上學過的Hash表衝突的各種解決方法麼?若要降低衝突發生的概率到1%,就要將BitSet的長度設置爲URL個數的100倍。
實質上上面的算法都忽略了一個重要的隱含條件:允許小概率的出錯,不一定要100%準確!也就是說少量url實際上沒有沒網絡蜘蛛訪問,而將它們錯判爲已訪問的代價是很小的——大不了少抓幾個網頁唄。
 方法四的致命缺點是衝突概率高,爲了降低衝突的概念,Bloom Filter使用了多個哈希函數,而不是一個。
Bloom Filter算法如下:
創建一個m位BitSet,先將所有位初始化爲0,然後選擇k個不同的哈希函數。
第i個哈希函數對字符串str哈希的結果記爲h(i,str),且h(i,str)的範圍是0到m-1 。
這樣就將字符串str映射到BitSet中的k個二進制位了。
Counting bloom filter(CBF)將位數組中的每一位擴展爲一個counter,從而支持了元素的刪除操作。
Spectral Bloom Filter(SBF)將其與集合元素的出現次數關聯。SBF採用counter中的最小值來近似表示元素的出現頻率。
Bit-map:
就是用一個bit位來標記某個元素對應的Value, 而Key即是該元素。
由於採用了Bit爲單位來存儲數據,因此在存儲空間方面,可以大大節省。
Bloom filter可以看做是對bit-map的擴展

【4】密匙四、Trie樹/數據庫/倒排索引

【4-0】樹的基礎知識補充
動態查找樹主要有:二叉查找樹(Binary Search Tree),平衡二叉查找樹(Balanced Binary Search Tree),
紅黑樹(Red-Black Tree ),B-tree/B+-tree/ B*-tree (B~Tree)。
前三者是典型的二叉查找樹結構,其查找的時間複雜度O(log2N)與樹的深度相關,那麼降低樹的深度自然會提高查找效率。
樹的深度過大而造成磁盤I/O讀寫過於頻繁,進而導致查詢效率低下;
如何減少樹的深度(當然是不能減少查詢的數據量)
一個基本的想法就是:採用多叉樹結構(由於樹節點元素數量是有限的,自然該節點的子樹數量也就是有限的)。
【4-0-1】B-Tree是爲了磁盤或其它存儲設備而設計的一種多叉(相對於二叉,B樹每個內結點有多個分支,即多叉)平衡查找樹。
與紅黑樹很相似,但在降低磁盤I/0操作方面要更好一些。
許多數據庫系統都一般使用B樹或者B樹的各種變形結構,如下文即將要介紹的B+樹,B*樹來存儲信息。
B樹與紅黑樹最大的不同在於,B樹的結點可以有許多子女,從幾個到幾千個。
B樹與紅黑樹又很相似:
因爲與紅黑樹一樣,一棵含n個結點的B樹的高度也爲O(lgn),但可能比一棵紅黑樹的高度小許多,所以,B樹可以在O(logn)時間內,實現各種如插入(insert),刪除(delete)等動態集合操作。

B樹是一棵平衡樹,它是把一維直線分爲若干段線段,當我們查找滿足某個要求的點的時候,只要去查找它所屬的線段即可。

這種思想其實就是先找一個大的空間,再逐步縮小所要查找的空間,最終在一個自己設定的最小不可分空間內找出滿足要求的解。一個典型的B樹查找如下:

 

要查找某一滿足條件的點,先去找到滿足條件的線段,然後遍歷所在線段上的點,即可找到答案。

【4-0-2】R樹是B樹在高維空間的擴展,是一棵平衡樹,R樹運用了空間分割的理念,它很好的解決了在高維空間搜索等問題。

用地圖的例子來解釋,就是所有的數據都是餐廳所對應的地點,先把相鄰的餐廳劃分到同一塊區域,劃分好所有餐廳之後,再把鄰近的區域劃分到更大的區域,劃分完畢後再次進行更高層次的劃分,直到劃分到只剩下兩個最大的區域爲止。要查找的時候就方便了

【4-1】Tire樹與後綴樹

由於基因(或者其他)數據庫一般是不變的, 通過預處理來把搜索簡化或許是個好主意. 一種預處理的方法是建立一棵Trie. 

首先, Trie是一種n叉樹, n爲字母表大小, 每個節點表示從根節點到此節點所經過的所有字符組成的字符串. 

而後綴Trie的 “後綴” 說明這棵Trie包含了所給字段的所有後綴 


Trie的核心思想是空間換時間。利用字符串的公共前綴來降低查詢時間的開銷以達到提高效率的目的。

Trie樹,即字典樹,又稱單詞查找樹或鍵樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用於統計和排序大量的字符串(但不僅限於字符串),所以經常被搜索引擎系統用於文本詞頻統計。它的優點是:最大限度地減少無謂的字符串比較,查詢效率比哈希表高。

設我要查詢的單詞是abcd,那麼在他前面的單詞中,以b,c,d,f之類開頭的我顯然不必考慮。而只要找以a開頭的中是否存在abcd就可以了。同樣的,在以a開頭中的單詞中,我們只要考慮以b作爲第二個字母的,一次次縮小範圍和提高針對性,這樣一個樹的模型就漸漸清晰了。

後綴樹,就是包含一則字符串所有後綴的壓縮Trie;

首先, Trie是一種n叉樹, n爲字母表大小, 每個節點表示從根節點到此節點所經過的所有字符組成的字符串. 而後綴Trie的 “後綴” 說明這棵Trie包含了所給字段的所有後綴 

【4-2】再歸類總結

涉及到字符串的問題,無外乎這樣一些算法和數據結構自動機,KMP算法,Extend-KMP,後綴樹,後綴數組,trie樹,trie圖等;

最常用和最熟悉的大概是kmp,他們之間的關係如下:


圖中可以看到這樣一些關係:extend-kmp 是kmp的擴展;ac自動機是kmp的多串形式;它是一個有限自動機;而trie圖實際上是一個確定性有限自動機;ac自動機,trie圖,後綴樹實際上都是一種trie;後綴數組和後綴樹都是與字符串的後綴集合有關的數據結構;trie圖中的後綴指針和後綴樹中的後綴鏈接這兩個概念及其一致;

【4-2-0】KMP算法Knuth-Morris-Pratt算法參考這個圖文解釋

【問題】有一個字符串"BBC ABCDAB ABCDABCDABDE",我想知道,裏面是否包含另一個字符串"ABCDABD"?

【傳統BF算法】BF(Brute Force)算法是普通的模式匹配算法


1.

  首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一個字符與搜索詞"ABCDABD"的第一個字符,進行比較。因爲B與A不匹配,所以搜索詞後移一位。

  2.

  因爲B與A不匹配,搜索詞再往後移。

  3.

  就這樣,直到字符串有一個字符,與搜索詞的第一個字符相同爲止。

  4.

  接着比較字符串和搜索詞的下一個字符,還是相同。

  5.

  直到字符串有一個字符,與搜索詞對應的字符不相同爲止。

  6.

  這時,最自然的反應是,將搜索詞整個後移一位,再從頭逐個比較。這樣做雖然可行,但是效率很差,因爲你要把"搜索位置"移到已經比較過的位置,重比一遍。

  7.


【改進的KMP算法】

一個基本事實是,當空格與D不匹配時,你其實知道前面六個字符是"ABCDAB"。KMP算法的想法是,設法利用這個已知信息,不要把"搜索位置"移回已經比較過的位置,繼續把它向後移,這樣就提高了效率。

一個基本事實是,當空格與D不匹配時,你其實知道前面六個字符是"ABCDAB"。KMP算法的想法是,設法利用這個已知信息,不要把"搜索位置"移回已經比較過的位置,繼續把它向後移,這樣就提高了效率。

  8.

  怎麼做到這一點呢?可以針對搜索詞,算出一張《部分匹配表》(Partial Match Table)。這張表是如何產生的,後面再介紹,這裏只要會用就可以了


9.

  已知空格與D不匹配時,前面六個字符"ABCDAB"是匹配的。查表可知,最後一個匹配字符B對應的"部分匹配值"爲2,因此按照下面的公式算出向後移動的位數:

  移動位數 = 已匹配的字符數 - 對應的部分匹配值

  因爲 6 - 2 等於4,所以將搜索詞向後移動4位。

  10.

  因爲空格與C不匹配,搜索詞還要繼續往後移。這時,已匹配的字符數爲2("AB"),對應的"部分匹配值"爲0。所以,移動位數 = 2 - 0,結果爲 2,於是將搜索詞向後移2位。

  11.

  因爲空格與A不匹配,繼續後移一位。

  12.

  逐位比較,直到發現C與D不匹配。於是,移動位數 = 6 - 2,繼續將搜索詞向後移動4位。

  13.

  逐位比較,直到搜索詞的最後一位,發現完全匹配,於是搜索完成。如果還要繼續搜索(即找出全部匹配),移動位數 = 7 - 0,再將搜索詞向後移動7位,這裏就不再重複了。


【4-3】倒排索引【參考這篇博客

 單詞-文檔矩陣是表達兩者之間所具有的一種包含關係的概念模型,

                           

搜索引擎的索引其實就是實現“單詞-文檔矩陣”的具體數據結構。可以有不同的方式來實現上述概念模型,如“倒排索引”、“簽名文件”、“後綴樹”等;

但是各項實驗數據表明,“倒排索引”是實現單詞到文檔映射關係的最佳實現方式,                           

有了這個索引系統,搜索引擎可以很方便地響應用戶的查詢,                 

倒排索引基本概念示意圖如下:

假設文檔集合包含五個文檔,每個文檔內

容如圖3-3所示,在圖中最左端一欄是每個文檔對應的文檔編號。我們的任務就是對這個文檔集合建立倒排索引。

                          

圖3-6所示索引系統除了記錄文檔編號和單詞頻率信息外,額外記載了兩類信息,即每個單詞對應的“文檔頻率信息”(對應圖3-6的第三欄)以及在倒排列表中記錄單詞在某個文檔出現的位置信息。

                        


【5】密匙五、外排序


外部排序指的是大文件的排序,即待排序的記錄存儲在外存儲器上,待排序的文件無法一次裝入內存,需要在內存和外部存儲器之間進行多次數據交換,以達到排序整個文件的目的。                           
外部排序最常用的算法是多路歸併排序,即將原文件分解成多個能夠一次性裝入內存的部分,分別把每一部分調入內存完成排序。然後,對已經排序的子文件進行多路歸併排序。
  1. 按可用內存的大小,把外存上含有n個記錄的文件分成若干個長度爲L的子文件,把這些子文件依次讀入內存,並利用有效的內部排序方法對它們進行排序,再將排序後得到的有序子文件重新寫入外存;
  2. 然後對這些有序子文件逐趟歸併,使其逐漸由小到大,直至得到整個有序文件爲止。



【6】密匙六、分佈式處理之Mapreduce

可以用Spark替代了!


           
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章