寻找第K小的数

立个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));
  
 }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值