大資料量獲取TopK的幾種方案
一:介紹
生活中經常會遇到求TopK的問題,在小資料量的情況下可以先將所有資料排序,最後進行遍歷。但是在大資料量情況下,這種的時間複雜度最低的也就是O(NlogN)此處的N可能為10億這麼大的數字,時間複雜度過高,那麼什麼方法可以減少時間複雜度呢,以下幾種方式,與大家分享。
二:區域性淘汰法 -- 藉助“氣泡排序”獲取TopK
-
思路:
-
可以避免對所有資料進行排序,只排序部分
-
氣泡排序是每一輪排序都會獲得一個最大值,則K輪排序即可獲得TopK
-
-
時間複雜度空間複雜度
-
時間複雜度:排序一輪是O(N),則K次排序總時間複雜度為:O(KN)
-
空間複雜度:O(K),用來存放獲得的topK,也可以O(1)遍歷原陣列的最後K個元素即可。ps:氣泡排序請參考: https://blog.csdn.net/CSDN___LYY/article/details/81478583
-
-
程式碼比較簡單就不貼了,只要會寫冒泡就ok了
三:區域性淘汰法 -- 藉助資料結構"堆"獲取TopK
-
思路:
-
堆:分為大頂堆(堆頂元素大於其他所有元素)和小頂堆(堆頂其他元素小於所有其他元素)
-
我們使用小頂堆來實現,為什麼不適用大頂堆下面會介紹~
-
取出K個元素放在另外的陣列中,對這K個元素進行建堆 ps:堆排序請參考:https://blog.csdn.net/CSDN___LYY/article/details/81454613
-
然後迴圈從K下標位置遍歷資料,只要元素大於堆頂,我們就將堆頂賦值為該元素,然後重新調整為小頂堆
-
迴圈完畢後,K個元素的堆陣列就是我們所需要的TopK
-
-
為什麼使用小頂堆呢?
-
我們在比較的過程中使用堆頂是最小值的小頂堆,元素大於堆頂我們對堆頂進行重新賦值,那麼堆頂永遠是這K個值中最小的值,當我們下一個元素和堆頂比較時,如果不大於堆頂的話,那麼一定不屬於topK範圍的
-
-
時間複雜度與空間複雜度
-
時間複雜度:每次對K個元素進行建堆,時間複雜度為:O(KlogK),加上N-K次的迴圈,則總時間複雜度為O((K+(N-K))logK),即O(NlogK),其中K為想要獲取的TopK的數量N為總資料量
-
空間複雜度:O(K),只需要新建一個K大小的陣列用來儲存topK即可
-
-
適用環境
-
適用於單核單機環境,不會發揮多核的優勢
-
也可用於分治法中獲取每一份元素的Top,下面會介紹
-
-
程式碼實現
-
使用的java程式碼實現的,程式碼內每一步都有註釋便於理解
-
import java.util.Arrays;
/**
* 通過堆這種資料結構
* 獲得大資料量中的TopK
*/
public class TopKStack {
public static void main(String[] args) {
//定義一個陣列,找出該陣列中的topK,大資料量不好搞到,先用這個陣列測試
int [] datas = {2,3,42,1,34,5,6,67,3,243,8,246,123,6,32,3451,23,5,6,31,5,6,2346,36};
int [] re = getTopK(datas,10);
System.out.println(Arrays.toString(re));
}
/**
* 獲取前topk的方法
* @param datas 原陣列
* @param num 前topNum
* @return 最後的topNum的堆陣列
*/
static int[] getTopK(int[] datas,int num){
//定義儲存前num個元素的陣列,用於建堆
int[] res = new int[num];
//初始化陣列
for (int i = 0; i < num; i++) {
res[i] = datas[i];
}
//建造初始化堆
for (int i = (num - 1)/2; i >= 0 ; i--) {
shift(res,i);
}
//遍歷查詢num個最大值
for (int i = num; i < datas.length; i++) {
if (datas[i] > res[0]){
res[0] = datas[i];
shift(res,0);
}
}
return res;
}
/**
* 調整元素滿足堆結構
* @param datas
* @param index
* @return
*/
static int[] shift(int[] datas ,int index){
while(true){
int left = (index<<1) + 1; //左孩子
int right = (index<<1) + 2; //右孩子
int min_num = index; //標識自身節點和孩子節點中最小值的位置
//判斷是否存在左右孩子,並且得到左右孩子和自身的最小值
if (left <= datas.length-1&&datas[left] < datas[index]){
min_num = left;
}
if (right <= datas.length-1&&datas[right] < datas[min_num]){
min_num = right;
}
//如果最小值不等於自身,則將最小值與自身交換
if (min_num != index){
int temp = datas[index];
datas[index] = datas[min_num];
datas[min_num] = temp;
}else{
//此處break是因為我們是從樹的最下面進行調整的,如果上層節點符合堆,則下層節點一定符合!
break;
}
//執行到此處,說明可能需要調整下面的節點,則將初始節點賦值為最小值所在的節點位置,
// 因為最大值點的位置進行了交換,可能下層節點就不滿足堆性質
index = min_num;
}
return datas;
}
}
四:分治法 -- 藉助”快速排序“方法獲取TopK
-
思路:
-
比如有10億的資料,找處Top1000,我們先將10億的資料分成1000份,每份100萬條資料
-
在每一份中找出對應的Top 1000,整合到一個陣列中,得到100萬條資料,這樣過濾掉了999%%的資料
-
使用快速排序對這100萬條資料進行”一輪“排序,一輪排序之後指標的位置指向的數字假設為S,會將陣列分為兩部分,一部分大於S記作Si,一部分小於S記作Sj。 ps:快速排序請參考:https://blog.csdn.net/CSDN___LYY/article/details/81478583
-
如果Si元素個數大於1000,我們對Si陣列再進行一輪排序,再次將Si分成了Si和Sj。如果Si的元素小於1000,則我們需要在Sj中獲取1000-count(Si)個元素的,也就是對Sj進行排序
-
如此遞迴下去即可獲得TopK
-
-
和第一種方法有什麼不同呢?相對來說的優點是什麼?
-
第二種方法中我們可以採用多核的優勢,建立多個執行緒,分別去操作不同的資料。
-
當然我們在分治的第二步可以使用第一種方法去獲取每一份的Top。
-
-
適用環境
-
多核多機的情況,分治法會將多核的作用發揮到最大,節省大量時間
-
-
時間複雜度與空間複雜度
-
時間複雜度:一份獲取前TopK的時間複雜度:O((N/n)logK)。則所有份數為:O(NlogK),但是分治法我們會使用多核多機的資源,比如我們有S個執行緒同時處理。則時間複雜度為:O((N/S)logK)。之後進行快排序,一次的時間複雜度為:O(N),假設排序了M次之後得到結果,則時間複雜度為:O(MN)。所以 ,總時間複雜度大約為O(MN+(N/S)logK) 。
-
空間複雜度:需要每一份一個陣列,則空間複雜度為O(N)
-
五:其他情況
-
通常我們要根據資料的情況去判斷我們使用什麼方法,在獲取TopK前我們可以做什麼操作減少資料量。
-
比如:資料集中有許多重複的資料並且我們需要的是前TopK個不同的數,我們可以先進行去重之後再獲取前TopK。如何進行大資料量的去重操作呢,簡單的說一下:
-
採用bitmap來進行去重。
-
一個char型別的資料為一個位元組也就是8個字元,而每個字元都是用0\1標識,我們初始化所有字元為0。
-
我們申請N/8+1容量的char陣列,總共有N+8個字元。
-
對資料進行遍歷,對每個元素S進行S/8操作獲得char陣列中的下標位置,S%8操作獲得該char的第幾個字元置1。
-
在遍歷過程中,如果發現對應的字元位置上已經為1,則代表該值為重複值,可以去除。
-
-
主要還是根據記憶體、核數、最大建立執行緒數來動態判斷如何獲取前TopK。
相關文章
- javascript獲取url地址的幾種方式JavaScript
- EXCEL大資料量匯出的解決方案Excel大資料
- AngularJS中獲取資料來源的幾種方式AngularJS
- 大資料量處理實踐方案整理大資料
- PG獲取檔案大小的幾種方式
- java獲取當前路徑的幾種方法Java
- Spring - 獲取ApplicationContext的幾種方式SpringAPPContext
- 獲取或操作DOM元素特性的幾種方式
- android獲取控制元件的幾種方法Android控制元件
- 關於java獲取本地ip的幾種方法Java
- Oracle 獲取執行計劃的幾種方法Oracle
- VB6 獲取CPUID的幾種方法UI
- 異源資料同步 → 如何獲取 DataX 已同步資料量?
- 通過網址獲取ES最近10分鐘的資料量
- springmvc請求引數獲取的幾種方法SpringMVC
- js獲取頁面dom元素的幾種常用方式JS
- 在SpringMVC中獲取request物件的幾種方式SpringMVC物件
- C#中獲取當前路徑的幾種方法C#
- 基於HTTP協議的幾種實時資料獲取技術HTTP協議
- 大資料分析的幾種方法大資料
- python讀取大檔案的幾種方法Python
- 獲取Java執行緒返回值的幾種方式Java執行緒
- Spring在程式碼中獲取bean的幾種方式SpringBean
- 獲取WebLogic版本號有以下幾種方式Web
- 在專案中獲取Spring的Bean的幾種方式SpringBean
- 淺談資料備份的幾種方案
- java獲取B站彈幕檔案的兩種方案Java
- js獲取數字陣列最大值的幾種方式JS陣列
- 介紹幾種獲取SQL執行計劃的方法(上)SQL
- PHP獲取POST資料的3種方法PHP
- iOS開發——資料持久化的幾種方案iOS持久化
- oracle 大資料量資料插入Oracle大資料
- 大資料量刪除的思考(二)大資料
- 大資料量刪除的思考(三)大資料
- 大資料量刪除的思考(四)大資料
- React如何渲染大資料量的列表?React大資料
- 大資料量刪除的思考(一)大資料
- 關於大資料量的處理大資料