大資料處理演算法三:分而治之/hash對映 + hash統計 + 堆/快速/歸併排序
百度面試題1、海量日誌資料,提取出某日訪問百度次數最多的那個IP。
IP 是32位的,最多有個2^32個IP。同樣可以採用對映的方法,比如模1000,把整個大檔案對映為1000個小檔案,再找出每個小文中出現頻率最大的 IP(可以採用hash_map進行頻率統計,然後再找出頻率最大的幾個)及相應的頻率。然後再在這1000個最大的IP中,找出那個頻率最大的IP,即 為所求。
百度面試題2、搜尋引擎會通過日誌檔案把使用者每次檢索使用的所有檢索串都記錄下來,每個查詢串的長度為1-255位元組。
假設目前有一千萬個記錄(這些查詢串的重複度比較高,雖然總數是1千萬,但如果除去重複後,不超過3百萬個。一個查詢串的重複度越高,說明查詢它的使用者越多,也就是越熱門。),請你統計最熱門的
第 一步借用hash統計進行預處理: 先對這批海量資料預處理(維護一個Key為Query字串,Value為該Query出現次數,即Hashmap(Query,Value),每次讀取一 個Query,如果該字串不在Table中,那麼加入該字串,並且將Value值設為1;如果該字串在Table中,那麼將該字串的計數加一即可。最終我 們在O(N)(N為1千萬,因為要遍歷整個陣列一遍才能統計處每個query出現的次數)的時間複雜度內用Hash表完成了統計;
第二步借用堆排序找出最熱門的10個查詢串:時間複雜度為N'*logK。維護一個K(
最終的時間複雜度是:O(N) + N'*O(logK),(N為1000萬,N’為300萬)
或者:採用trie樹,關鍵字域存該查詢串出現的次數,沒有出現為0。最後用10個元素的最小推來對出現頻率進行排序。
我們先看HashMap 實現
1. HashMap的資料結構
資料結構中有陣列和連結串列來實現對資料的儲存,但這兩者基本上是兩個極端。
陣列
陣列儲存區間是連續的,佔用記憶體嚴重,故空間複雜的很大。但陣列的二分查詢時間複雜度小,為O(1)
連結串列
連結串列儲存區間離散,佔用記憶體比較寬鬆,故空間複雜度很小,但時間複雜度很大,達O(N)。連結串列的特點是:定址困難,插入和刪除容易。
雜湊表
那麼我們能不能綜合兩者的特性,做出一種定址容易,插入刪除也容易的資料結構?答案是肯定的,這就是我們要提起的雜湊表。雜湊表((Hash table)既滿足了資料的查詢方便,同時不佔用太多的內容空間,使用也十分方便。
雜湊表有多種不同的實現方法,我接下來解釋的是最常用的一種方法——拉鍊法,我們可以理解為“連結串列的陣列”
我用java 自己實現了一個HashMap,當然這比較簡點,不過能說明大概原理,改有的功能基本上有了
index=hashCode(key)=key%16
雜湊演算法很多,下面我用了java自帶的,當然你也可以用別的
/**
* 自定義 HashMap
* @author JYC506
*
* @param <K>
* @param <V>
*/
public class HashMap<K, V> {
private static final int CAPACTITY = 16;
transient Entry<K, V>[] table = null;
@SuppressWarnings("unchecked")
public HashMap() {
super();
table = new Entry[CAPACTITY];
}
/* 雜湊演算法 */
private final int toHashCode(Object obj) {
int h = 0;
if (obj instanceof String) {
return StringHash.toHashCode((String) obj);
}
h ^= obj.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
/*放入hashMap*/
public void put(K key, V value) {
int hashCode = this.toHashCode(key);
int index = hashCode % CAPACTITY;
if (table[index] == null) {
table[index] = new Entry<K, V>(key, value, hashCode);
} else {
for (Entry<K, V> entry = table[index]; entry != null; entry = entry.nextEntry) {
if (entry.hashCode == hashCode && (entry.key == key || key.equals(entry.key))) {
entry.value = value;
return;
}
}
Entry<K, V> entry2 = table[index];
Entry<K, V> entry3 = new Entry<K, V>(key, value, hashCode);
entry3.nextEntry = entry2;
table[index] = entry3;
}
}
/*獲取值*/
public V get(K key) {
int hashCode = this.toHashCode(key);
int index = hashCode % CAPACTITY;
if (table[index] == null) {
return null;
} else {
for (Entry<K, V> entry = table[index]; entry != null; entry = entry.nextEntry) {
if (entry.hashCode == hashCode && (entry.key == key || key.equals(entry.key))) {
return entry.value;
}
}
return null;
}
}
/*刪除*/
public void remove(K key){
int hashCode = this.toHashCode(key);
int index = hashCode % CAPACTITY;
if (table[index] == null) {
return ;
} else {
Entry<K, V> parent=null;
int i=0;
for (Entry<K, V> entry = table[index]; entry != null; entry = entry.nextEntry) {
if (entry.hashCode == hashCode && (entry.key == key || key.equals(entry.key))) {
if(i==0){
table[index]=null;
}
if(parent!=null){
parent.nextEntry=entry.nextEntry;
}
entry=null;
return ;
}
i++;
parent=entry;
}
}
}
public static void main(String[] args) {
HashMap<String,String> map=new HashMap<String,String>();
for(int i=0;i<10000;i++){
map.put(Integer.toString(i), Integer.toString(i));
}
map.put("1", "2");
map.put("3", "哈哈哈");
System.out.println(map.get("1"));
System.out.println(map.get("3"));
map.remove("1");
System.out.println(map.get("1"));
for(int i=0;i<10000;i++){
String s= map.get(Integer.toString(i));
if(s==null){
System.out.println(i);
}
}
}
}
class Entry<K, V> {
K key;
V value;
int hashCode;
Entry<K, V> nextEntry;
public Entry(K key, V value, int hashCode) {
super();
this.key = key;
this.value = value;
this.hashCode = hashCode;
}
}
/* 字串hash演算法 */
class StringHash {
public static final int toHashCode(String str) {
/* 我用java自帶的 */
return str.hashCode();
}
}
執行結果