问题描述:给定一个整型数组,返回其中第K小的数
普通解法:
这道题可以利用荷兰国旗改进的 partition 和随机快排的思想:随机选出一个数,将数组以该数作比较划分为 <,=,> 三个部分,则 = 部分的数是数组中第几小的数不难得知,接着对 < (如果第K小的数在 < 部分)或 > (如果第K小的数在 > 部分)部分的数递归该过程,直到 = 部分的数正好是整个数组中第K小的数。这种做法不难求得时间复杂度的数学期望为 O(NlogN) (以2为底)。
但这毕竟是数学期望,在实际工程中的表现可能会有偏差(最坏情况下的时间复杂度会达到O(N^2))
BFPRT算法可以说是这种算法的一种优化吧
public static int process(int[] arr, int L, int R, int index) { if(L == R) { return arr[L]; } //等概率随机选取一个值作划分 L+ [0,R-L] int pivot = arr[L+(int)(Math.random()*(R-L+1))]; //range[0] range[1] // L .... R pivot int[] range = partition(arr,L,R,pivot); if(index >= range[0] && index <= range[1]) { return arr[index]; } else if(index < range[0]) { return process(arr,L,range[0]-1,index); } else { return process(arr,range[1]+1,R,index); } } public static int[] partition(int[] arr,int L, int R, int pivot) { int less = L - 1; int more = R + 1; int cur = L; while(cur < more) { if(arr[cur] < pivot) { swap(arr,++less,cur++); } else if(arr[cur] > pivot) { swap(arr,cur,--more); } else { cur++; } } return new int[] {less + 1, more - 1}; } public static void swap(int[] arr,int i,int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
BFPRT算法
BFPRT算法,又称为中位数的中位数算法,有5位大牛(Blum、Floyd、Pratt、Rivest、Tarjan)提出,并以他们的名字命名。
BFPRT算法能够做到时间复杂度就是 O(N)
BFPRT算法,接收一个数组和一个K值,返回数组中的一个数
数组被划分为了 N/5 个小部分,每个部分的5个数排序需要 O(1) ,所有部分排完需要 O(N/5)=O(N)
取出每个小部分的中位数,一共有 N/5 个,递归调用BFPRT算法得到这些数中第 (N/5)/2 小的数(即这些数 的中位数),记为 pivot
以 pivot 作为比较,将整个数组划分为 <pivot , =pivot , >pivot 三个区域
判断第K小的数在哪个区域,如果在 = 区域则直接返回 pivot ,如果在 < 或 > 区域,则将这个区域的数递 归调用BFPRT算法
base case :在某次递归调用BFPRT算法时发现这个区域只有一个数,那么这个数就是我们要找的数
// arr[L..R] 如果排序的话,位于index位置的数,是什么,返回什么 public static int bfprt(int[] arr, int L, int R, int index) { if(L == R) return arr[L]; int pivot = medianOfMedians(arr,L,R); int[] range = partition(arr,L,R,pivot); if(index >= range[0] && index <= range[1]) { return arr[index]; } else if(index < range[0]) { return bfprt(arr,L,range[0]-1,index); } else { return bfprt(arr,range[1]+1,R,index); } } //arr[L..R] 五个数一组 //每个小组内部排序 //每个小组中位数拎出来,组成mArr //mArr中的中位数,返回 public static int medianOfMedians(int[] arr, int L, int R) { int size = R - L + 1; int offset = size % 5 == 0 ? 0 : 1; int[] mArr = new int[size / 5 + offset]; for(int team = 0; team < mArr.length; team++) { int teamFirst = L + team *5; mArr[team] = getMedian(arr,teamFirst,Math.min(R, teamFirst)); } return bfprt(mArr,0,mArr.length-1,mArr.length / 2); } //五个数排序,返回中位数 public static int getMedian(int[] arr,int L, int R) { insertionSort(arr,L,R); return arr[(L+R) / 2]; } public static void insertionSort(int[] arr,int L,int R) { for(int i = L +1; i <= R; i++) { for(int j = i - 1; j >= L && arr[j] > arr[j+1]; j--){ swap(arr,j,j+1); } } }
时间复杂度分析:
BFPRT算法在最坏情况下的时间复杂度是O(n),下面予以证明。令T(n)为所求的时间复杂度,则有:
其中:
设T(n)=t·n,其中t为未知,它可以是一个正常数,也可以是一个关于n的函数,代入上式︰
其中c为一个正常数,故t也是一个正常数,即T(n)≤10c ·n,因此T(n)=O(n),至此证明结束。