立个flag,希望可以坚持至少跟着算法课的进度来整理和实现有关算法(虽然我已经感觉到了flag要倒的气息。。)手动微笑脸。
寻找第k小的数
这是一个很常见的问题,在乱序的n个数中寻找到第k小的数,很容易想到先排序再取第k个数的方法,但是我们要降低时间复杂度,并用上减治的思想。
方法一 快速选择算法
基本思路跟快排很像。每次快排我们会把数分为三组,大于基准,小于基准,基准本身。我们需要在每次快排后判断自己需要寻找的数在哪一组,然后在该组中递归上述步骤,以降低时间复杂度。
基准是否能选好对时间复杂度的影响很大,最好的情况当然是每次基准都正好是中间值,而最坏的情况则是基准每次都是最值。
这里采用每次都选最末尾的值作为基准的方法。
import java.util.Scanner;
public class FindKth {
static int Partition(int A[],int left,int right){
int pivot=A[right];//选取最末尾的值作为基准
int tail=left-1;//指向小于基准的数组的最后一个元素
for(int i=left;i<right;i++){
if(A[i]<=pivot){
swap(A,++tail,i);
}
}
swap(A,tail+1,right);
return tail+1;//返回基准的索引
}
static int QuickSelected(int k,int left,int right,int A[]){
if(A.length==0 || A==null){
return 0;
}
while(true){
int index=Partition(A,left,right);
if(index==k){
return A[index];
}else if(index > k){
right=index-1;
}else{
left=index+1;
}
}
}
static void swap(int A[],int a,int b){
int temp=A[a];
A[a]=A[b];
A[b]=temp;
}
public static void main(String[] args){
int k=0,result=0;
int[] A={6,2,0,41,65,40,14,33,7,20};
int len=A.length;
try{
Scanner sc=new Scanner(System.in);
k=sc.nextInt();
System.out.println(len);
result=QuickSelect(k-1,0,len-1,A);
System.out.println("the "+k+"th number is "+result);
}catch(Exception e){
e.printStackTrace();
}
}
}
方法二 BFPRT算法
其实感觉BFPRT算法就是快速选择算法的改进版,改进在哪里呢?在选基准的问题上。通过更合理的选基准,我们可以降低最坏情况下的时间复杂度。BFPRT算法选的基准是一组数的中位数,比上面的快速选择算法的“佛系”选择是不是精准很多。。具体如下:
基本思路:
(1)把n个数按五个一组分组。
(2)找出每组的中间值(每组数据很少,找出中间值很简单,这里用的是插入排序把每个小组排好序)。
(3)对得到的每组的中间值再调用上述步骤,得到“中间值们”的中间值,作为整个数组的中间值,对数组进行划分(即比其小的放左侧,比其大的放右侧)。
(4)判断要找的第k小的数在哪一侧或是否是该中间值本身,对于在一侧的情况,对该侧进行递归,同样找中间值——划分——找中间值——划分。。。
在这个过程中,我们要找的第k小的数其实“正在被调整到数组的第k个位置上去”。此算法在最坏的情况下时间复杂度为O(n),不枉基准选得如此复杂。
代码如下,请忽略弱爆了的我一堆用来调试的注释emmm
import java.util.Arrays;
import java.util.Scanner;
public class BFPRT {
//得到中位数下标
public static int GetPivotIndex(int A[],int left,int right){
if(right-left<5){
InsertionSort(A,left,right);
return (left+right)/2;
}
int sub_right=left-1;
for(int i=left;i+4<=right;i+=5){
InsertionSort(A,i,i+4);
int index=i+2;
swap(A,index,++sub_right);
}
// System.out.println("sub_right"+sub_right);
int mid=sub_right/2;
// System.out.println("mid"+mid);
int pivot_index=BFPRTs(mid,A,0,sub_right);
// System.out.println("pivot_index"+pivot_index);
return pivot_index;
}
//插入排序
public static void InsertionSort(int A[],int left,int right){
for(int i=left+1;i<right;i++){
int j=i-1;
// System.out.println(j);
int cur=A[i];
while(true){
if(A[j]>cur&&j>=0){
A[j+1]=A[j];
j--;
// System.out.println(j);
}
if(j<0||A[j]<cur)break;
}
A[j+1]=cur;
}
}
//根据中位数分区
public static int Partition(int A[],int left,int right,int pivot_index){
swap(A,pivot_index,right);
int pivot=A[right];
int tail=left-1;
for(int i=left;i<right;i++){
if(A[i]<=pivot){
swap(A,++tail,i);
}
}
swap(A,tail+1,right);
// System.out.println("tail+1 "+tail);
return tail+1;
}
public static void swap(int A[],int a,int b){
int temp=A[a];
A[a]=A[b];
A[b]=temp;
}
public static int BFPRTs(int k,int A[],int left,int right){
int pivot_index=GetPivotIndex(A,left,right);
// System.out.println("pivot_index"+pivot_index);
int divide_index=Partition(A,left,right,pivot_index);
// System.out.println("divide_index"+divide_index);
int num = divide_index - left;
// System.out.println("num"+num);
// System.out.println("k"+k);
if(num==k){
// System.out.println("num==k");
return num;
}
else if(num < k)
return BFPRTs(k-num,A,divide_index,right);
else
return BFPRTs(k,A,left,divide_index);
}
public static void main(String[] args){
int[] A={6,20,74,60,10,94,12,1,59,14,36};
Scanner sc=new Scanner(System.in);
int k=sc.nextInt();
int right=A.length-1;
int left=0;
// System.out.println(right-left);
int index=BFPRTs(k-1,A,left,right);
// System.out.println(index);
System.out.println("the "+k+"th smallest number is "+A[k-1]);
// System.out.print(Arrays.toString(A));
}
}