K:找寻数组中第n大的数组元素的三个算法

相关介绍:

 给定一个数组,找出该数组中第n大的元素的值。其中,1<=n<=length。例如,给定一个数组A={2,3,6,5,7,9,8,1,4},当n=1时,返回9。解决该问题的算法有三种。依据其时间复杂度的高低,分别对其进行讲解

第一种:时间复杂度为O(NlogN)

 解决该问题,容易想到的一个办法是,先对数组按元素值从大到小的方式进行排序,之后选取出其符合要求的元素并返回其值。由基于比较的排序算法的时间复咋读,其下界为NlogN,为此,解决该问题的时间复杂度为O(NlogN)。

示例代码:

/**
* 思路:排序数组,并寻找一个原本乱序的数组中的第th大的元素
* 平均时间复杂度视具体的排序算法而定,一般是O(nlogn)
* @param th 第th大的元素,即要找的那个元素在数组中的真实的下标的元素索引
* @param low 数组的元素下标,表示需要进行排序的数组元素的开始的下标
* @param high 数组元素的下标,表示需要进行排序的数组元素的结束的下标
* @return 返回第th大的那个数组元素
 */
public int sort(int th)
{
    Arrays.sort(array);
    return array[th];
}

第二种:平均情况下时间复杂度为O(N)

 该算法运用了快速排序的思想,由于在快速排序的思想中,每次确定一个数组中元素的正确位置,为此,可以通过比较每次查找出来的元素在数组中的正确位置和想要进行查找的位置的元素之间的关系,就可排除掉一部分元素,从而逼近答案。在最优的情况下,每次能够排除掉N/2个数组元素,为此,可以根据主定理法计算得出,其平均情况下,时间复杂度为O(N)

示例代码:

/**
* 思路:使用快速排序的思想,去寻找一个乱序的数组中的第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);
}

第三种:时间复杂度为O(N)

 该算法根据以上快速排序的思想进行改进,由于每次选择的元素找到其在数组中的位置时,其不一定能够恰好排除掉N/2个元素,在最坏的情况下,每次只能排除掉一个数组元素。为此,第二种解决办法在最坏的情况下可能达到O(N^2)。而该算法,就是基于解决不能够排除掉N/2个数组元素的情况下提出的。在数组元素中找到一个划分基准,使其按照这个基准所划分出的两个子数组的长度都至少为原数组长度的v倍(0< v< 1),则在最坏情况下,其也能用O(N)的时间完成任务。该算法的基本思路如下:对数组中的元素进行分组(每组元素的个数为5个),之后找到各组中的中位数,之后再找到中位数的中位数,将中位数的中位数那个元素根据快速排序的思想,找到其在数组中的正确的位置,使得其每次都能够排除掉一定数量的数组元素,从而逼近答案。

示例代码如下:

/**
* 采用线性时间选择的算法找出第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;
}

代码汇总如下:

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;
    }
}

回到目录|·(工)·)

posted @ 2018-01-08 17:23  林学徒  阅读(4866)  评论(0编辑  收藏  举报