尋找N個數中最大的K個數,本質上就是尋找最大的K個數中最小的那個,也就是第K大的數。
可以使用二分搜索的策略來尋找N個數中的第K大的數。對於一個給定的數p,可以在O(N)的時間復雜度內找出所有不小於p的數。
尋找第k大的元素:
#include <iostream> using namespace std; //快速排序的划分函數 int partition(int a[],int l,int r) { int i,j,x,temp; i = l; j = r+1; x = a[l]; //將>=x的元素換到左邊區域 //將<=x的元素換到右邊區域 while (1) { while(a[++i] > x); while(a[--j] < x); if(i >= j) break; temp = a[i]; a[i] = a[j]; a[j] = temp; } a[l] = a[j]; a[j] = x; return j; } //隨機划分函數 int random_partition(int a[],int l,int r) { int i = l+rand()%(r-l+1);//生產隨機數 int temp = a[i]; a[i] = a[l]; a[l] = temp; return partition(a,l,r);//調用划分函數 } //線性尋找第k大的數 int random_select(int a[],int l,int r,int k) { int i,j; if (l == r) //遞歸結束 { return a[l]; } i = random_partition(a,l,r);//划分 j = i-l+1; if(k == j) //遞歸結束,找到第K大的數 return a[i]; if(k < j) { return random_select(a,l,i-1,k);//遞歸調用,在前面部分查找第K大的數 } else return random_select(a,i+1,r,k-j);//遞歸調用,在后面部分查找第K大的數
} int main() { int a[]={1,2,3,4,6,6,7,8,10,10}; cout<<random_select(a,0,9,1)<<endl; cout<<random_select(a,0,9,5)<<endl; return 0; }
如果所有N個數都是正整數,且它們的取值范圍不太大,可以考慮申請空間,記錄每個整數出現的次數,然后再從大到小取最大的K個。比如,所有整數都在(0, MAXN)區間中的話,利用一個數組count[MAXN]來記錄每個整數出現的個數(count[i]表示整數i在所有整數中出現的個數)。只需要掃描一遍就可以得到count數組。然后,尋找第K大的元素:
for(sumCount = 0, v = MAXN-1; v >= 0; v--) { sumCount += count[v]; if(sumCount >= K) break; } return v;
極端情況下,如果N個整數各不相同,我們甚至只需要一個bit來存儲這個整數是否存在(bit位為1或為0),這樣使用的空間可以大大壓縮。
當然也可以使用像計數排序、桶排序等這些以O(N)的時間排序算法也可以尋找第K大的數,但這也是以空間換時間為代價的。
實際情況下,並不一定保證所有元素都是正整數,且取值范圍不太大。上面的方法仍然可以推廣使用。如果N個數中最大的數Vmax,最小的Vmin,我們可以把這個區間[Vmax,Vmin]分成M塊,每個小區間的跨度為d=(Vmax-Vmin)/M,即[Vmin,Vmin+d],[Vmin+d,Vmin+2d]......然后,掃描一遍所有元素,統計各個小區間中的元素個數,就可以知道第K大的元素在哪一個小區間。然后,再在那個小區間中找第K大的數(此時這個小區間中,第K大的數可能就是第T大的數了,這個T和每個小區間的個數有關)。我們需要找一個盡量大的M,但M的取值受到內存的限制。
