分治策略 - 选择第k大数 - Java实现
题目要求:
编写程序,对任意输入的若干个不相同的整数,输出其第k大的数
解析:
我们采用分治法解决这道题。
把这些数放在一个数组中,用分治法的话,我们可以想到,怎样分治?
把一个数组分成若干个大小相等的子数组,然后在这些子数组中取中位数,再取这些中位数的中位数,用这个数就可以把数组划分为一个比它大的和比它小的数组。通过用这个数和我们要求的数进行比较,若等于它,则直接返回这个数,若小于,则递归的对这个数前面的数组的这些数进行分治策略,若大于,则递归的对这个数后面的数组的这些数进行分治策略。
\(main()\)里只有一个\(SelectK()\)方法,中包含有两个主要的方法,分别为取中位数\(Median()\)和划分\(Divide()\)方法。
\(Median()\):
首先,我们把数组分为5个一组(听说经过证明,5个一组划分最科学),然后取每组的中位数,这时用的是\(Median()\)方法。具体的对每5个连续的数进行排序,然后把每组中的中位数即第3个数交换到数组的最左边,如果这些转移的数的个数多于5个,就再次调用\(Median()\)方法,直到最左边的数是这个数组的中位数的中位数。
\(Divide()\):
介绍划分方法,当求得中位数的中位数后,我们就以这个数为基准,把当前进行排序的数组的段落进行划分,小于基准数的交换到基准数的左边,大于的交换至右边,然后比较要求的那个数和基准数是否相等,如果相等,则返回;若小于基准数,则递归左边的子数组;若大于基准数,则递归右边的子数组。
到这里,就是简单介绍一下这道题,这道题的主要算法在网上叫BFPRT算法,有兴趣的同学可以去搜一下,如果不想搜的话,可以直接看我之前写的具体讲这道题分治算法背后的算法思想的文章分治策略-选择问题。
Java代码:
static void swap(int[]arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
static void sortArr(int[] arr,int a,int b){
for(int i=a;i<b;i++){
for(int j=i+1;j<=b;j++){
if(arr[i]<arr[j])
swap(arr,i,j);
}
}
}
//找中位数的中位数
static int Median(int[] arr,int a,int b){
if(a==b)
return a;
int i=0;
int n=0;
for(i=a;i<b-5;i+=5){
sortArr(arr,i,i+4);
n = i - a;
swap(arr,a+n/5,i+2);
}
//下面是剩余的数
int last = b-i+1;
if(last>0){
sortArr(arr,i,i+last-1);
n = i - a;
swap(arr,a+n/5,i+last/2);
}
n/=5;
if(n==a)
return a;
return Median(arr,a,a+n);
}
//划分
static int Divide(int[]arr,int a,int b,int m){
swap(arr,m,a);
int i = a;
int j = b;
int flag = arr[a];
while (i<j){
while (arr[j]<=flag && i<j)
j--;
arr[i] = arr[j];
while (arr[i]>=flag && i<j)
i++;
arr[j] = arr[i];
}
arr[i] = flag;
return i;
}
static int SelectK(int[] arr,int a,int b,int k){
int m = Median(arr,a,b);
int i = Divide(arr,a,b,m);
int x = i-a+1;
if(x==k)
return arr[i];
if(x>k)
return SelectK(arr,a,i-1,k);
return SelectK(arr,i+1,b,k-x);
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("请输入要求的第k大的k:");
int k = in.nextInt();
int[] arr = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,100,99,98,97,96,95,94,93,92,91,
90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,
53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26};
// System.out.println(arr.length);
System.out.println("第"+k+"大为:"+SelectK(arr,0,arr.length-1,k));
}