package other;
import java.util.Arrays;
/**
* 该类用于演示在一个无序的数组中寻找第n大的数的三个算法
* @author 学徒
*
*/
public class FindTHNumber
{
int[] array=null;
public static void main(String[] args)
{
FindTHNumber n=new FindTHNumber();
n.array=new int[]{1,3,2,4,5,6};
int low=0;
int high=n.array.length-1;
int th=6;
System.out.println(n.QuicklySortedMind(th-1, low, high));
System.out.println(n.sort(th-1));
System.out.println(n.LinedSelectFind(th, low, high));
}
/**
* 思路:排序数组,并寻找一个原本乱序的数组中的第th大的元素
* 平均时间复杂度视具体的排序算法而定,一般是O(nlogn)
* @param th 第th大的元素,即要找的那个元素在数组中的真实的下标的元素索引
* @param low 数组的元素下标,表示需要进行排序的数组元素的开始的下标
* @param high 数组元素的下标,表示需要进行排序的数组元素的结束的下标
* @return 返回第th大的那个数组元素
*/
public int sort(int th)
{
Arrays.sort(array);
return array[th];
}
/**
* 思路:使用快速排序的思想,去寻找一个乱序的数组中的第th大的元素
* 由于快速排序的思想中,每次确定一个元素在数组中的正确位置,为此,通过比较每次
* 查找出来的元素在数组中的正确位置和想要进行查找的位置的元素之间的关系,可以排除
* 掉一部分元素,从而逼近答案。在最优的情况下,每次能够排除掉N/2个数组元素
* 平均时间复杂度为O(n)
* @param th 第th大的元素,即要找的那个元素在数组中的真实的下标的元素索引
* @param low 数组的元素下标,表示需要进行排序的数组元素的开始的下标
* @param high 数组元素的下标,表示需要进行排序的数组元素的结束的下标
* @return 返回第th大的那个数组元素
*/
public int QuicklySortedMind(int th,int low,int high)
{
int lows=low;
int highs=high;
//选择该元素为基准元素
int midNumber=array[low];
//以下为快速排序部分,用于将元素midNumber的值插入到数组的正确位置中
while(low<high)
{
while(array[high]>midNumber&&low<high)
high–;
if(low<high)
{
array[low]=array[high];
}
while(array[low]<midNumber&&low<high)
low++;
if(low<high)
{
array[high]=array[low];
}
}
array[low]=midNumber;
//用于递归的寻找第n大的元素
//当找到的那个中间的元素为要找的第th大的元素的时候,将其直接进行返回
//ps:关键点在于,low的值即使是递归查找出来的,其依然为数组的绝对索引值
if(th==low)
return array[low];
//当找到的元素的位置在想要找的元素的位置的左边时,从右边继续寻找
else if(th>low)
return QuicklySortedMind(th,low+1,highs);
//当找到的元素的位置在想要找的元素的位置的右边时,从左边继续寻找
else
return QuicklySortedMind(th,lows,low-1);
}
/**
* 采用线性时间选择的算法找出第th大的元素的值
* 线性时间选择算法的思想为:
* 线性时间选择算法是对上面那个基于快速选择排序算法查找第n大的数组元素的改进。具体思想如下:
* 对数组中的元素进行分组(每组元素的个数为5个),之后找到各组中的中位数,之后再找到中位数的中位数
* 将中位数的中位数那个元素根据快速排序的思想,找到其在数组中的正确的位置
* 使得其每次都能够排除掉一定的数组元素,从而逼近答案
* @param th 第th大的元素
* @param low 数组的元素下标,表示需要进行排序的数组元素的开始的下标
* @param high 数组元素的下标,表示需要进行排序的数组元素的结束的下标
* @return 返回第th大的那个数组元素
*
*/
public int LinedSelectFind(int th,int low,int high)
{
//当数组元素个数小于5个的时候,即只有一组元素的时候,将其进行排序,之后直接返回
if(high-low<5)
{
Arrays.sort(array,low,high);
return array[low+th-1];
}
//变量i用于控制数组元素分组的迭代
//每组5个元素,为此,总共的组别数目为(high-low-4)/5组(舍弃掉数组元素不足5个的组别,不考虑元素个数不足5个的分组)
//其等价与i<(high-low)/5
for(int i=0;i<=(high-low-4)/5;i++)
{
//变量s为每组元素的第一个元素的下标(即索引),t为每组元素的最后一个元素的下标(即索引)
int s=low+5*i,t=s+4;
/*for(int j=0;j<3;j++)
{*/
//用于排序s到t之间的数组中的每组元素的前三个数组元素
Arrays.sort(array,s,t-2);
//用于将每组元素中的中位数按照各组之间的区别,
//在原数组(low到high之间的数组)中从头开始排,即原数组(low到high之间的数组)中
// 前(high-low-4)/5个元素为各个分组的中位数
swap(array,low+i,s+2);
/*
}
*/
}
//选择出中位数的中位数,其中在low到low+(high-low-4)/5这个范围内,其中、中位数为第(high-low+6)/10大的元素
int x=LinedSelectFind((high-low+6)/10,low,low+(high-low-4)/5);
//变量i为中位数的中位数那个数在数组中所在的下标位置,变量j为相对low到high这个范围的数组,元素x为其第几大的元素
int i=partition(low,high,x),j=i-low+1;
//进行递归查找,直到数组元素小于5个的时候
//当th在j的左边或者相等的时候,在low到i之间继续寻找第th大的元素
if(th<=j)
return LinedSelectFind(th,low,i);
//当th在j的右边的时候,在i+1到high之间,继续寻找第th-j大的元素
else
return LinedSelectFind(th-j,i+1,high);
}
//将元素x放置到p到r之间的数组元素的正确位置,并返回元素x所在的下标
private int partition(int p,int r,int x)
{
int i=p,j=r+1;
while(true)
{
while(array[++i]<x&&i<r);
while(array[–j]>x);
if(i>=j)
break;
swap(array,i,j);
}
array[p]=array[j];
array[j]=x;
return j;
}
//交换数组array中的下标为index1和index2的两个数组元素
private void swap(int[] array,int index1,int index2)
{
int temp=array[index1];
array[index1]=array[index2];
array[index2]=temp;
}
}