BFPRT演算法(求前k個小的數)-Java實現
最近學習了左神BFPRT演算法,給大家先講個段子。
左神說他每次去美國面試,他都會拿BFPRT演算法吹一吹。美國5個大佬在一個美麗的地方研究出來這個演算法,他說自己熱愛演算法,他會BFPRT,每次去美國都會懷著朝聖的姿態去在那個地方轉轉。面試官一聽:哇! 這麼厲害, 過!!!!!!!
好了,下來說說這個演算法。
BFPRT演算法是在進行大量資料排序求topk(前k個最大或最小的數)時最優演算法。
為什麼叫BFPRT演算法,因為是美國5個大佬搞出來的,所以分別取他們的名字命名演算法。
演算法步驟大概如下:
1 將陣列劃分區域,5個數為一組,把陣列分為若干組。(為什麼是5呢?因為5能更好的收斂時間複雜度。)
2 將分開的組進行組內排序(這裡我用的是插入排序),找出每組數的中位數。
3 找出的若干個中位數,單獨放一組進行排序。
4 在找出的中位陣列中,遞迴呼叫步驟2的過程,找出中位陣列的中位數,把這個中位數叫做劃分數。
5 用劃分數進行partition過程(類似於快速排序的每趟的劃分過程):小於劃分數放左邊,等於劃分數放中間,大於劃分數放右邊。
6 將劃分數與k進行比較:
k<劃分數,在步驟5的左邊區域遞迴上述過程,找出左邊區域的中位數與k比較,直到與k相等,最後的劃分數左邊就是前k小的數;
k=劃分數,那麼左邊區域直接為前k小的數;
k>劃分數,在步驟5的右邊區域遞迴上述過程,找出右邊區域的中位數與k比較,直到與k相等,最後的劃分數左邊就是前k小的數;
為什麼要花費這麼多步驟去尋找劃分數呢?假設陣列長度為n,那麼整個陣列一共有n/5箇中位數,在中位陣列找出中位陣列的中位數(劃分數),那麼就會有3*(n/10)個數比劃分數小-如圖紅色區域所示:
我們一次就可以至少刷掉3*(n/10)、最多7*(n/10)的數量級的資料進行遞迴排序,節省了很多時間!
程式碼實現如下所示:
package com.zuoshen;
/**
* BFPRT演算法求topk,時間複雜度bO(n)
*
* @author wanglongfei
* E-mail: [email protected]
* @version 2017年8月5日
*
*/
public class BFPRT {
// 得到前k個最小的數
public static int[] getMinKNumsByBFPRT(int[] arr, int k) {
if (k < 1 || k > arr.length) {
return arr;
}
int minKth = getMinKthByBFPRT(arr, k);
int[] res = new int[k];//res前k個結果集
int index = 0;
for (int i = 0; i != arr.length; i++) {
if (arr[i] < minKth) {
res[index++] = arr[i];
}
}
for (; index != res.length; index++) {
res[index] = minKth;
}
return res;
}
//找出比k小的前k個數
public static int getMinKthByBFPRT(int[] arr, int K) {
int[] copyArr = copyArray(arr);
return select(copyArr, 0, copyArr.length - 1, K - 1);
}
//複製陣列
public static int[] copyArray(int[] arr) {
int[] res = new int[arr.length];
for (int i = 0; i != res.length; i++) {
res[i] = arr[i];
}
return res;
}
//用劃分值與k相比,依次遞迴排序
public static int select(int[] arr, int begin, int end, int i) {
if (begin == end) { //begin陣列的開始 end陣列的結尾 i表示要求的第k個數
return arr[begin];
}
int pivot = medianOfMedians(arr, begin, end);//找出劃分值(中位陣列中的中位數)
int[] pivotRange = partition(arr, begin, end, pivot);
if (i >= pivotRange[0] && i <= pivotRange[1]) {//小於放左邊,=放中間,大於放右邊
return arr[i];
} else if (i < pivotRange[0]) {
return select(arr, begin, pivotRange[0] - 1, i);
} else {
return select(arr, pivotRange[1] + 1, end, i);
}
}
//找出中位陣列中的中位數
public static int medianOfMedians(int[] arr, int begin, int end) {
int num = end - begin + 1;
int offset = num % 5 == 0 ? 0 : 1; //分組:每組5個數,不滿5個單獨佔一組
int[] mArr = new int[num / 5 + offset]; //mArr:中位陣列成的陣列
for (int i = 0; i < mArr.length; i++) { //計算分開後各陣列的開始位置beginI 結束位置endI
int beginI = begin + i * 5;
int endI = beginI + 4;
mArr[i] = getMedian(arr, beginI, Math.min(end, endI));//對於最後一組(不滿5個數),結束位置要選擇end
}
return select(mArr, 0, mArr.length - 1, mArr.length / 2);
}
//劃分過程,類似於快排
public static int[] partition(int[] arr, int begin, int end, int pivotValue) {
int small = begin - 1;
int cur = begin;
int big = end + 1;
while (cur != big) {
if (arr[cur] < pivotValue) {
swap(arr, ++small, cur++);
} else if (arr[cur] > pivotValue) {
swap(arr, cur, --big);
} else {
cur++;
}
}
int[] range = new int[2];
range[0] = small + 1;//比劃分值小的範圍
range[1] = big - 1; //比劃分值大的範圍
return range;
}
//計算中位數
public static int getMedian(int[] arr, int begin, int end) {
insertionSort(arr, begin, end);//將陣列中的5個數排序
int sum = end + begin;
int mid = (sum / 2) + (sum % 2);
return arr[mid];
}
//陣列中5個數排序(插入排序)
public static void insertionSort(int[] arr, int begin, int end) {
for (int i = begin + 1; i != end + 1; i++) {
for (int j = i; j != begin; j--) {
if (arr[j - 1] > arr[j]) {
swap(arr, j - 1, j);
} else {
break;
}
}
}
}
//交換元素順序
public static void swap(int[] arr, int index1, int index2) {
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}
//列印結果
public static void printArray(int[] arr) {
for (int i = 0; i != arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = { 6, 9, 1, 3, 1, 2, 2, 5, 6, 1, 3, 5, 9, 7, 2, 5, 6, 1, 9 };
printArray(getMinKNumsByBFPRT(arr, 10));
}
}
相關推薦
BFPRT演算法(求前k個小的數)-Java實現
最近學習了左神BFPRT演算法,給大家先講個段子。 左神說他每次去美國面試,他都會拿BFPRT演算法吹一吹。美國5個大佬在一個美麗的地方研究出來這個演算法,他說自己熱愛演算法,他會BFPRT,每次去美國都會懷著朝聖的姿態去在那個地方轉轉。面試官一聽:哇! 這麼厲害, 過!!
數獨求解演算法(回溯法和唯一解法)java實現
數獨(すうどく,Sudoku)是一種運用紙、筆進行演算的邏輯遊戲。玩家需要根據9×9盤面上的已知數字,推理出所有剩餘空格的數字,並滿足每一行、每一列、每一個粗線宮內的數字均含1-9,不重複。 注:數獨的各種知識和解決思路請 參考http://www.llang
堆處理海量資料----求前k個最小的數--時間複雜度(n * log k)
通過閱讀July的書籍,發現裡面求前k個最小數有很多方法。但在面對處理海量資料處理的時候,不能 把全部資料都放在電腦記憶體中。這時用堆來處理,並把資料放在外存中,通過讀取檔案的方式來讀取。感覺該方法十分巧妙,時間複雜度(n*log k)。程式碼如下: #include&l
兩個有序陣列,A[k]和B[k]長度都為k。求前k個最小的(a[i]+b[j])
設A={A1,A2,A3,A4,A5,A6,.......} ,B={B1,B2,B3,B4,B5,B6,.......} 因為A和B都是有序的陣列,必須充分的利用這點,可能有同學,看到有同學覺得這個題目比較容易,直接將所有的組合都計算出來,然後取最小的K個,其實出題的人是
LeetCode-347:Top K Frequent Elements(取前k個頻率最高的元素)
題目: Given a non-empty array of integers, return the k most frequent elements. 例子: Example: Given [1,1,1,2,2,3] and k = 2, ret
LeetCode703 Kth Largest Element in a Stream(求第K大元素)
Design a class to find the kth largest element in a stream. Note that it is the kth largest element in the sorted order, not the kth dis
bzoj2301 [HAOI2011]Problem b(求gcd==k的個數)(莫比烏斯反演+容斥原理)
首先我們搞掉下界,怎麼搞呢,用容斥原理即可。(看做矩形區間),然後我們需要求∑x=1n∑y=1ngcd(x,y)==k。 ∑x=1⌊n/k⌋∑y=1⌊m/k⌋gcd(x,y)==1 ∑x=1⌊n/k
歸併排序演算法與求逆序對思路及Java實現
1.歸併排序演算法思路 首先我們要清楚,歸併排序演算法採用了分治法的思想,即將原問題分解為幾個規模較小但類似於原問題的子問題,遞迴地求解這些子問題,然後再合併這些子問題的解來建立原問題的解。歸併排序首先將排序分成兩部分,接著再將這兩部分分解成更小的兩部分,直到分解到只剩一個元素為止。
幾種缺頁中斷演算法(FIFO,LRU與LFU)的實現過程
最近在做筆試題,其中虛擬儲存管理中幾種缺頁中斷演算法經常考到,雖然這類題可說非常簡單,但概念上卻容易混淆而且如果不掌握正確的做法很容易出錯,因此覺得有必要把這三種演算法的實現過程理一遍,並從原始碼級別去思考它們的實現。 首先推薦一個部落格,對這兩個演算法
PAT乙級——1087(陣列操作,輔助空間)java實現
題目: 有多少不同的值 (20 分) 當自然數 n 依次取 1、2、3、……、N 時,算式 ⌊ n
LeetCode——中級演算法——排序和搜尋——前K個高頻元素(JavaScript)
給定一個非空的整數陣列,返回其中出現頻率前 k 高的元素。 示例 1: 輸入: nums = [1,1,1,2,2,3], k = 2 輸出: [1,2] 示例 2: 輸入: nums = [1], k = 1 輸出: [1] 說明: 你可以假設給定的 k 總是合
【LeetCode題解】347_前K個高頻元素(Top-K-Frequent-Elements)
目錄 描述 解法一:排序演算法(不滿足時間複雜度要求) Java 實現 Python 實現 複雜度分析 解法二:最小堆 思路 Java 實現 Python 實現 複雜度分析 解法三:桶排序(bucket s
【演算法】反轉佇列前K個元素
反轉佇列前K個元素 想到佇列的反轉那肯定首先想到的就是堆疊(同理堆疊反轉也可以利用佇列) 有一個辦法我們可以將K個元素入棧,然後可以另一個佇列將剩下的元素放入,隨後我們首先將棧元素迴歸原來的佇列,最後將
KNN演算法---求前K個數據。
簡介 K Nearest Neighbor演算法又叫KNN演算法,K最近鄰演算法。K表示距離自己最近的k個數據樣本。 個人覺得重點在距離如何表示,如何計算,是簡單的用距離公式,還是用複雜的加權計算。
模擬實現atoi和itoa以及100G 的IP地址求出現次數最多的前K個IP
1.模擬實現C庫的atoi和itoa。 2.給一個超過100G的log file, log中存著IP地址, 設計演算法找到出現次數最多的100個IP地址? 1.題考察面試者的思維方式:完整性和魯棒性 先想好測試用例,溝通好錯誤處理,才能滿意
Leetcode 347:前K個高頻元素(最詳細解決方案!!!)
給定一個非空的整數陣列,返回其中出現頻率前 k 高的元素。 例如, 給定陣列 [1,1,1,2,2,3] , 和 k = 2,返回 [1,2]。 注意: 你可以假設給定的 k 總是合理的,1 ≤ k ≤ 陣列中不相同的元素的個數。 你的演算法的時間複雜
計算第K個能表示(2^i * 3^j * 5^k)的正整數(i,j,k為整數)?其前7個滿足此條件的數分別是1,2,3,4,5,6,8
public class Main { public static void main(String[] args) { int[] a = new int[1501]; a[1] = 1; TreeMap<Integer, Integ
TOP K演算法(微軟筆試題 統計英文電子書中出現次數最多的k個單詞)
在v_JULY_v的文章中找到了這個問題的解法後用C++實現了一下,發現C++的程式碼非常的簡潔。 主要用到了標準庫中的hash_map,優先順序佇列priority_queue。
歐幾里德演算法(求兩個正整數的最大公約數)
getchar()會接受前一個scanf的回車符 */ #include<stdio.h> void main() { int temp; int a,b; s
遞迴演算法(求n的加法組合,將一個整數拆分成多個整數相加的形式, O(N)時間,O(N)空間)
網上的多種解法比較複雜,本文用遞迴方法,22行程式碼搞定。時間和空間複雜度已經降到最低! 第三版:加入創作思路。 這個函式的主要功能就是輸出所有組合。既然是輸出所有的組合,那就意味著內部有一個遍歷所有組合的過程。既然是遍歷,而且是O(N)時間,那就說明這個遍歷是按照某種輸出次序,從“第一個組合”遍歷到