相關介紹:
給定一個數組,找出該數組中第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;
}
}