关于某日访问次数最多的IP的topK问题的三种解法
題目描述
在july大神的博客中,看到這樣兩道題:
1. 海量日志數據,提取出某日訪問百度次數最多的那個IP。
2. 假設目前有一千萬個記錄(這些查詢串的重復度比較高,雖然總數是1千萬,但如果除去重復后,不超過3百萬個。一個查詢串的重復度越高,說明查詢它的用戶越多,也就是越熱門。),請你統計最熱門的10個查詢串,要求使用的內存不能超過1G。
現在我將兩題結合一下:
假如有1千萬+的ip,如何知道訪問次數最多的topk ip呢?假設k為10
來源:https://blog.csdn.net/v_JULY_v/article/details/6256463
生成1千萬的IP
我們來生成1千萬+的ip
# coding:utf-8
# /bin/pythondef generateRandom(rangeFrom, rangeTo):import randomreturn random.randint(rangeFrom,rangeTo)def generageMassiveIPAddr(fileLocation,numberOfLines):IP = []file_handler = open(fileLocation, 'a+')for i in range(numberOfLines):IP.append('10.197.' + str(generateRandom(0,255))+'.'+ str(generateRandom(0,255)) + '\n')file_handler.writelines(IP)file_handler.close()if __name__ == '__main__':from time import ctimeprint ctime()for i in range(10):print ' ' + str(i) + ": " + ctime()generageMassiveIPAddr('imassiveIP.txt', 1000000)print ctime()
通過這段代碼我們可以在當前的目錄生成一個10010000行的imassiveIP.txt文件
$ wc -l imassiveIP.txt
10010000 imassiveIP.txt
我將這個文件作為我們的數據來進行實驗
解法1 通過linux shell來計算,實現最快
$ time cat imassiveIP.txt | sort | uniq -c | sort -nrk1 | head -n 10204 10.197.30.171202 10.197.92.207202 10.197.42.72201 10.197.195.77201 10.197.190.108200 10.197.49.128200 10.197.236.54200 10.197.223.59200 10.197.145.27199 10.197.43.98real 1m18.731s
user 1m17.010s
sys 0m1.090s
可以看到我們花費時間為1m18.731s
綜合分析一下,第一步排序的時間復雜度是O(NlgN),而uniq遍歷的時間復雜度是O(N),假設去重后的數為M個,則第二步排序的時間復雜度為O(MlgM)因此該算法的總體時間復雜度就是O(N+NlgN+MlgM)=O(NlgN)
解法2 Hash Table法+部分排序
- 第一步:維護一個Key為IP字串,Value為該IP出現次數的HashTable,每次讀取一個IP,如果該字串不在Table中,那么加入該字串,并且將Value值設為1;如果該字串在Table中,那么將該字串的計數加一即可。最終我們在O(N)的時間復雜度內完成了對該海量數據的處理。
- 第二步 題目要求是求出Top 10,因此我們沒有必要對所有的IP都進行排序,我們只需要維護一個10個大小的數組,初始化放入10個IP,按照每個IP的統計次數由大到小排序,排序的時間復雜度為
O(klg(k)), 假設去重后的數為M個, 然后遍歷這M條記錄,每讀一條記錄就和數組最后一個IP對比,如果小于這個IP,那么繼續遍歷,否則,將數組中最后一條數據淘汰,加入當前的IP。最后當所有的數據都遍歷完畢之后,那么這個數組中的10個IP便是我們要找的Top10了。
- 第二步 題目要求是求出Top 10,因此我們沒有必要對所有的IP都進行排序,我們只需要維護一個10個大小的數組,初始化放入10個IP,按照每個IP的統計次數由大到小排序,排序的時間復雜度為
python 代碼實現
#coding:utf-8
#/bin/pythonimport sys, os
from collections import defaultdictdef main():if len(sys.argv) != 2:print "Usage: python **.py topK"sys.exit(1)else:#print sys.argvk = int(sys.argv[1])res_dict = defaultdict(int)topk_dict = defaultdict(int)for line in sys.stdin:res_dict[line.strip()] += 1topk_dict_sorted = []for key, value in res_dict.items():if len(topk_dict) <= k:topk_dict[key] = valueif len(topk_dict) == k:topk_dict_sorted = sorted(topk_dict.items(), key=lambda x:x[1], reverse=True)else:if topk_dict_sorted[-1][1] < value:topk_dict_sorted.pop()topk_dict_sorted.append((key, value))topk_dict_sorted = sorted(topk_dict_sorted, key=lambda x:x[1], reverse=True)if len(topk_dict_sorted) < k:topk_dict_sorted = sorted(topk_dict.items(), key=lambda x:x[1], reverse=True)for key, value in topk_dict_sorted:print "%s\t%d"%(key, value)if __name__ == '__main__':main()
結果如下
$ time cat imassiveIP.txt| python get_top10.py 10
10.197.30.171 204
10.197.92.207 202
10.197.42.72 202
10.197.190.108 201
10.197.195.77 201
10.197.236.54 200
10.197.145.27 200
10.197.49.128 200
10.197.223.59 200
10.197.241.183 199real 0m5.046s
user 0m4.730s
sys 0m0.290s
我們發現我們只需要用5.046s就可以找到這個top10的ip
綜合分析一下,第一步統計頻數時間復雜度是O(N),假設去重后的數為M個,則第二步計算的時間復雜度為O(M*k*lgk), 其中O(k*lgk)為對k個數排序的時間復雜度,因此該算法的總體時間復雜度就是O(N+M*k*lgk),這個速度比直接shell已經快了10幾倍了。
方法3 Hash Table法+堆
我們主要做的優化是如何在去重后的M個IP中找到topK個最大的,在方法二中,我們采取的是先排序然后插入到最后位置排序, 其實也可以通過采用二分的方法查找,應該插入的合適位置,這樣就可以避免再排序,這樣操作的復雜度就降到了logK,可是,隨之而來的問題就是數據移動,因為移動數據次數增多了。有沒有既能快速查找,又能快速移動元素的數據結構呢?回答是肯定的,那就是堆。
借助堆結構,我們可以在log量級的時間內查找和調整/移動。因此到這里,我們的算法可以改進為這樣,維護一個K(該題目中是10)大小的小根堆,然后遍歷M個IP,分別將出現次數和根元素進行對比。
具體過程是:
堆頂存放的是整個堆中最小的數,現在遍歷N個數,把最先遍歷到的k個數存放到最小堆中,并假設它們就是我們要找的最大的k個數,X1>X2...XminX1>X2...Xmin(堆頂),而后遍歷后續的N-K個數,一一與堆頂元素進行比較,如果遍歷到的XiXi大于堆頂元素XminXmin,則把Xi放入堆中,而后更新整個堆,更新的時間復雜度為O(lgK),如果Xi<XminXi<Xmin則不更新堆,整個過程的復雜度為O(K)+O((M-K)*logK)=O(M*logK)
這個有個關于堆排序很好3d動畫鏈接
python 實現1
一個速度還可以但是空間復雜度很高的程序,在python中有個heapq可以直接實現堆.關于heap的詳細用法可以看這里
我們把所有的數據都都放在堆中,然后直接返回最大的k個數據就可以了,時間復雜度是Time complexity: O(M + klogM),總的時間復雜度為O(N+M+klgM)
#coding:utf-8
#/bin/pythonimport sys, os
from collections import defaultdict
import heapqdef main():if len(sys.argv) != 2:print "Usage: python **.py topK"sys.exit(1)else:#print sys.argvk = int(sys.argv[1])res_dict = defaultdict(int)heap_res = []for line in sys.stdin:res_dict[line.strip()] += 1for key, value in res_dict.items():heap_res.append((value, key))heap_res = heapq.nlargest(k, heap_res)for key, value in heap_res:print "%s\t%d"%(value, key)if __name__ == '__main__':main()
結果如下:
$ time cat imassiveIP.txt | python get_top10_heap.py 10
10.197.30.171 204
10.197.92.207 202
10.197.42.72 202
10.197.195.77 201
10.197.190.108 201
10.197.49.128 200
10.197.236.54 200
10.197.223.59 200
10.197.145.27 200
10.197.43.98 199real 0m5.085s
user 0m4.760s
sys 0m0.300s
python實現2
如果不排序輸出 Time Complexity: O(k + (M-k)Logk),如果排序輸出Time Complexity:O(k + (M-k)Logk + kLogk)=O(k + MLogk),所以總的Time Complexity:O(N + k + MLogk)
#coding:utf-8
#/bin/pythonimport sys, os
from collections import defaultdict
import heapqdef main():if len(sys.argv) != 2:print "Usage: python **.py topK"sys.exit(1)else:k = int(sys.argv[1])res_dict = defaultdict(int)heap_res = []for line in sys.stdin:res_dict[line.strip()] += 1for key, value in res_dict.items():if len(heap_res) < k:heap_res.append((value, key))if len(heap_res) == k:heapq.heapify(heap_res)else:if value > heap_res[0][0]:heapq.heappushpop(heap_res, (value, key))heap_res = heapq.nlargest(k, heap_res)for key, value in heap_res:print "%s\t%d"%(value, key)if __name__ == '__main__':main()
結果如下:
$ time cat imassiveIP.txt | python get_top10_heap.py 10
10.197.30.171 204
10.197.92.207 202
10.197.42.72 202
10.197.195.77 201
10.197.190.108 201
10.197.49.128 200
10.197.236.54 200
10.197.223.59 200
10.197.145.27 200
10.197.43.98 199real 0m4.917s
user 0m4.670s
sys 0m0.220s
總結
如果我們處理大批量的數據時候,還是要學會優化代碼,但是掌握shell的命令行使用方法,可以極大的提高我們的效率
參考鏈接
十一、從頭到尾解析Hash表算法
【原創】Python處理海量數據的實戰研究
https://www.geeksforgeeks.org/k-largestor-smallest-elements-in-an-array/
總結
以上是生活随笔為你收集整理的关于某日访问次数最多的IP的topK问题的三种解法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TF-IDF 原理及sklearn中的t
- 下一篇: 【置顶】利用 NLP 技术做简单数据可视