问题分析
寻找第n小元素可以由多种方式实现,但是利用分治算法可以高效求解。
算法解析
每次递归锁定首元素到末尾元素之间的中位数,然后进行顺序调整,使得中位数左边元素都小于中位数;中位数右边的元素都大于中位数。然后根据n的值去判断后续应该搜索左半部分元素还是右半部分元素,不断递归调用,最终最后锁定的中位数即为第n小元素。
①初始化
h,t分别为用来遍历的头下标和尾下标,midIndex为中位数的下标,mid储存中位数的值。
int h=head;
int t=tail;
int midIndex = (h+t)/2;//每次选择头到尾之间元素的中位数mid
int mid = dataArray[midIndex];
②调整中位数左右两边元素
1)从左向右遍历左边的元素,直到找到比中位数大或者相等的元素。
while (dataArray[h]<mid){
h++;//找到mid左边比其大的数
}
2)从右向左遍历右边的元素,直到找到比中位数小或者相等的元素。
while (dataArray[t]>mid){
t--;//找到mid右边比其小的数
}
3)将两侧找到的元素交换顺序,同时如果出现中位数被换顺序,要将中位数的下标也进行调整。
if(h<=t){
//交换找到的数
int tmp=0;
tmp = dataArray[h];
dataArray[h] =dataArray[t];
dataArray[t] =tmp;
//当左右下标移动到中间值mid上时,在交换数字同时,也要把下标换到正确位置。
if(midIndex==h){
t = midIndex;
}
else if(midIndex == t){
h = midIndex;
}
//这里是为了能够跳出最外层循环,避免形成死循环,也影响到了后面的递归判断。
h++;
t--;
}
③判断第n小元素方位,进行递归调用
如果第n小元素在左半部分就遍历左半部分元素;如果在右半部分就遍历右半部分元素
//h与t将整个数组分为两块,左边为小于等于目标值的元素,右边为大于等于目标值的元素。
//并且h与t分别对应了两部分相邻两元素的下标,t为左部分的末尾元素下标,h为右部分的首元素下标。
if(n<=t){
//如果n在左侧就递归左部分继续寻找
return findNMin(head,t,n);
}
else if(n>=h){
//如果n在右侧就递归右部分继续寻找
return findNMin(h,tail,n);
}
else{
//如果第一轮的中位数就是第n小元素直接结束
return dataArray[n];
}
④结果展示
完整代码
package com.algorithm.day1;
import java.util.Arrays;
import java.util.Scanner;
import java.util.Vector;
public class KeyWord{
public int [] dataArray =new int[50];
public int key = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
KeyWord keyWord =new KeyWord();
System.out.println("输入数组元素个数");
int numberCount = scanner.nextInt();
System.out.println("请输入n");
int n= scanner.nextInt();
for(int i=1;i<=numberCount;i++){
keyWord.dataArray[i] = scanner.nextInt();
}
System.out.println("第"+n+"小的数字是"+ keyWord.findNMin(1,numberCount,n));
}
public findNMin(int head, int tail, int n) {
int h=head;
int t=tail;
int midIndex = (h+t)/2;//每次选择头到尾之间元素的中位数mid
int mid = dataArray[midIndex];
while(h<=t){//h大于t时表示完成了,使mid左边为比它大的数,右边为比它小的数。
while (dataArray[h]<mid){
h++;//找到mid左边比其大的数
}
while (dataArray[t]>mid){
t--;//找到mid右边比其小的数
}
if(h<=t){
//交换找到的数
int tmp=0;
tmp = dataArray[h];
dataArray[h] =dataArray[t];
dataArray[t] =tmp;
//当左右下标移动到中间值mid上时,在交换数字同时,也要把下标换到正确位置。
if(midIndex==h){
t = midIndex;
}
else if(midIndex == t){
h = midIndex;
}
h++;
t--;
}
}
//h与t将整个数组分为两块,左边为小于等于目标值的元素,右边为大于等于目标值的元素。
//并且h与t分别对应了两部分相邻两元素的下标,t为左部分的末尾元素下标,h为右部分的首元素下标。
if(n<=t){
//如果n在左侧就递归左部分继续寻找
return findNMin(head,t,n);
}
else if(n>=h){
//如果n在右侧就递归右部分继续寻找
return findNMin(h,tail,n);
}
else{
//如果锁定中位数直接返回值
return dataArray[n];
}
}
}