参考视频bilibil fjnuzs
给出一个n个元素的序列,求其中的第k小元素(即序列按升序排序后的第k个元素)
如果用排序时间复杂度nlogn,随机快速排序时间复杂度n。
但是这里采用一种分治的方法使时间复杂度为n。若分治算法中,每次分解子问题后,只取其中一个子问题解之,丢弃其余的子问题。每次递归调用,问题的规模以常数因子被减小。设分解问题只需线性时间,则算法的时间复杂性满足 Θ n \Theta n Θn。
大致思路是选一个“主元”,比他大的放一个数组,比他小的放另一个数组,等于的再放一个数组。然后每个数组都有长度,子问题就变为k所在的那个数组,其余两数组便可以舍去。直到只有44个元素时就去排序找第K。(为啥是44哩,是在计算时间复杂度时确定的)
还有一个难度是找到“主元”就是一个中间的数,避免子问题太大,被舍弃的内容太少。找主元的方式是五个一组,不够5个舍弃掉,将每五个的中间数放入mid数组,再从mid数组里找一个中间数作为主元(mm)。
//求A[low.high]中的第k小元素并返回。
select ( A, low, high, k )
p=high-low+1 //p为当前处理的元素个数。
if p<44 then // 当元素个数<44时,直接求解。
将A[low..high]排序
return(A[low+k-1])
end if
//以下分解子问题
q= p/5下取整
将A[low.low+5q-1]分为q组,每组5个元素
将q组中的每一组单独排序并找出中项,所有的中项存于数组Mid[1..q]中
mm=select(Mid , 1, q, q/2上取整) //求中项序列M的中项mm
将A[low..high]分成三组
A1={a|amm}
//以下选择一个子问题递归或直接解。
case
|A1|>=k: return select (A1,1, |A1|, k)
|A1|+|A2|>=k: return mm
|A1|+|A2|
但是有一个不足,就是我测试的数据没有找到大于44的(lan)所以只能算跟着伪代码码了一遍。
public class TestMink {
public static void main(String[] args) {
int[] arr = {
56, 34, 22, 7, 16, 95, 46, 37, 81, 12, 73, 26, 19, 31, 68, 42, 3, 72, 51};
System.out.println(select(arr, 0, arr.length-1, 8));
}
public static int select(int[] arr, int begin, int end, int k) {
//传的是下标
int p = end-begin+1;
if (p < 44) {
Arrays.sort(arr);
return arr[begin+k-1];
}
int q = (int)Math.floor(p/5);
int mm = medians(arr, begin, q);//起始下标和一共多少组
int[] a1 = new int[end-begin+1];
int[] a2 = new int[end-begin+1];
int[] a3 = new int[end-begin+1];
int count1 = 0;
int count2 = 0;
int count3 = 0;
for(int i=begin; i<=end; i++) {
if(arr[i] < mm) {
a1[count1++] = arr[i];
}else if(arr[i] > mm) {
a3[count3++] = arr[i];
}else {
a2[count2++] = arr[i];
}
}
if(count1 >= k ) {
return select(a1, 0, count1, k);
}else if(count1+count2 >= k) {
return mm;
}else {
return select(a3, 0, count3, k-count1-count2);
}
}
//找主元
public static int medians(int[] arr, int low, int q) {
int[] mArr = new int[q];
int j = 0;
int count = 0;
int[] temp = new int[5];
for (int i = low; i < low+5*q-1; i++) {
if(count == 5) {
count = 0;
Arrays.sort(temp);
mArr[j++] = temp[2];
}else {
temp[count++] = arr[i];
}
}
return select(mArr, 0, mArr.length - 1, mArr.length / 2);
}
}
select函数两个地方被调用,在找主元时递归的规模是n/5,还有就是舍弃两个数组递归去解决子问题时。a1和a3的规模采用估算。
T ( n ) ⩽ { c , n < 44 T ( ⌊ n / 5 ⌋ ) + T ( ⌊ 3 n / 4 ⌋ ) + c n , n ≥ 44 T(n) \leqslant \begin{cases} c &,n < 44\\ T(\lfloor n/5 \rfloor) + T(\lfloor 3n/4 \rfloor)+cn &,n\ge44 \end{cases} T(n)⩽{ cT(⌊n/5⌋)+T(⌊3n/4⌋)+cn,n<44,n≥44
解的 T ( n ) ⩽ 20 c n T(n)\leqslant20cn T(n)⩽20cn