package demo;
public class P39 {
//找出乱序数组中第k小的数字
//思路:利用快排中的分区方法,下标为a-1的主元就是第a小的数字,比较a和k,再到k在的那一边用分区找
//这样比快排更优,因为分区过程中排除了没用的部分
public static void main(String[] args) {
int[] arr= {3,9,7,6,1,2};
System.out.println(selectK(arr, 0, arr.length-1, 1));
System.out.println(selectK(arr, 0, arr.length-1, 2));
System.out.println(selectK(arr, 0, arr.length-1, 3));
System.out.println(selectK(arr, 0, arr.length-1, 4));
System.out.println(selectK(arr, 0, arr.length-1, 5));
System.out.println(selectK(arr, 0, arr.length-1, 6));
}
static int selectK(int[] arr, int s,int e,int k) {
int a=partition2(arr, s, e); //主元下标
int aK=a-s+1; //主元是第几个元素
if(aK==k)
return arr[a];
else if(aK>k)
return selectK(arr, s, a-1, k); //k在左区
else
return selectK(arr, a+1, e, k-aK); //k在右区,因为丢掉了aK个比它小的元素,k在子问题中是第(k-aK)小
}
static int partition2(int[] arr,int p,int r) { //双向扫描
//优化,三点中值法,取头、中、尾三点的中值作为主元
int mid=p+((r-p)>>1);
int midValue; //中值的下标
int temp;
if( (arr[mid]-arr[p])*(arr[mid]-arr[r])<0 ) {
midValue=mid;
}
else if( (arr[p]-arr[mid])*(arr[p]-arr[r])<0 ) {
midValue=p;
}
else {
midValue=r;
}
temp=arr[p]; //把中值交换到主元位置
arr[p]=arr[midValue];
arr[midValue]=temp;
int value=arr[p]; //把p作为主元
int left=p+1;
int right=r;
while(left<=right) { //左右交错为结束条件,此时右指针指向左区末尾,左指针指向右区开头
while(left<=right && arr[left]<=value) left++; //让左指针指向第一个大于主元的元素
while (left <= right && arr[right] > value)
right--; // 让右指针指向 从右边数第一个 小于等于主元的元素
if (left < right) { //交换值
temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
}
arr[p]=arr[right];
arr[right]=value;
return right;
}
}