編程之美有一道考察多種排序的題目,題目如下:
有一個長度爲N的無序數組,假定其中的每一個元素都各不相等,求其中最大的K個數。
作者對於此題目結合各種排序算法給出了五種解法思路。
解法一:
使用快速排序或堆排序對它們元素進行排序,整個排序的時間複雜度爲O(N*
解法二:
利用快速排序思想對數據進行分組,讓其一組數據的任何一個數都比另一組中的任意數大,整個算法的時間複雜度是O(N*
假設N個數存儲在數組S中,我們從數組S中隨機的找到一個元素X,把數組分成兩部分Sa和Sb。Sa中的元素大於等於X,Sb中的元素小於X。
(1)Sa中的元素個數小於K,Sa中所有的數和Sb中最大的K-|Sa|個元素就是數組S中最大的K個數。
(2)Sa中的元素的個數大於等於K,則需要返回Sa中最大的K個元素。
代碼實現:
#include <iostream>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
//返回基準索引
int partition(int a[],int low,int height)
{
//存儲基準值對應數組的索引
int index;
//利用隨機選擇比較基準值,以避免特殊數據下的算法退化
swap(a[low],a[low+rand()%(height-low+1)]);
int p=a[low];//比較的基準值
int i=low,j=height;
do
{
//從右端開始尋找大於基準值的第一個值
while(i<j&&a[j]<p)j--;
//比基準值大的數往左邊移
if(i<j)
a[i++]=a[j];
//從左端端開始尋找小於基準值的第一個值
while(i<j&&a[i]>=p)i++;
//比基準值小的數往右邊移
if(i<j)
a[j--]=a[i];
}while(i<j);
a[i]=p;
index=i;
return index;
}
int Kbig(int a[],int low,int height,int k)
{
if(low>=height)
return a[low];
int index=partition(a,low,height);
//獲得數組連續最大的數目
int count=index-low+1;
//獲得數組連續最大的數目等於K則輸出
// 若小於K則是第一種情況
//若大於K則是第二種情況
if(count==k)
return a[index];
if(count<k)
return Kbig(a,index+1,height,k-count);//後面部分尋找最大的K個數
else
return Kbig(a,low,index-1,k);//前面部分尋找最大的K個數
}
//交換數據
void swap(int *a,int *b)
{
int temp=*a;
*a=*b;
*b=temp;
}
int main()
{
int a[]={100,2000,45,68,5,700,9,50,45,89,87};
int len=sizeof(a)/sizeof(int);
int k=5;
cout<<Kbig(a,0,len-1,k)<<endl;
for(int i=0;i<k;i++)
cout<<a[i]<<"\t";
return 0;
}
解法三思路:
尋找N個數中最大的K個數,本質上就是尋找最大的K個數中最小的那個,也就是第K大的數,因此可以使用二分搜索的策略,對一個給定的數p,可以在O(N)的時間複雜度內找出所有不小於P的數,假如N個常數最大的數爲Vmax,最小的數爲Vmin,那麼這N個數中的第K大數一定在區間[Vmin,Vmax]之間,整個算法的時間複雜度也爲O(N*
代碼實現:
#include <iostream>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
int find(int*a,int x,int len)
{
int number=0;
for(int i=0;i<len;i++)
if(a[i]>=x)
number++;
return number;
}
int findKMax(int *a,int max,int min,int k,int len,int delta)
{
while(max-min>delta)
{
int mid=min+(max-min)/2;
//若後面部分大於mid的個數大於K,那麼查找後半部分
if(find(a,mid,len)>=k)
min=mid;
//若後面部分大於mid的個數小於K,那麼查找前半部分
else
max=mid;
}
return min;
}
int main()
{
int a[]={100,2000,45,68,5,700,9,50,45,89,87};
int len=sizeof(a)/sizeof(int);
int k=5;
//該取值要比所有N個數中的任意兩個不相等的元素差值之最小值都要小
int delta=1;
cout<<findKMax(a,2000,5,5,len,delta)<<endl;
return 0;
}
解法四思路
如果數據很大,100億多個,這個時候數據不能全部裝入內存,所以要求儘可能少地遍歷所有數據,如果能通過逐漸的加入元素來判斷,能就可以解決這問題了,因此可以用最小堆的結構來求出前面已加入元素的前K個數,因此可以構造K個元素的小頂堆來存儲最大的K個數,最小堆的堆頂(Y)就是最大的K個數中的最小的一個,對於每一個新加入的數X,如果X < Y,則不需要更新堆,如果X>Y,那麼用X替換掉堆頂的元素Y,然後調整小頂堆結構,整個過程遍歷一次數組元素就行了,因此整個算法的時間複雜度O(N *
代碼實現:
#include <iostream>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
//調整堆
void adjustMinHeap(int *a,int m,int n)
{
int i=m;
int j=2*i+1;//左結點
int temp;//臨時存儲
while(j<n)
{
//比較左右結點得到較小的那個結點
if(j+1<n&&a[j]>a[j+1])
j++;
//若父結點大於較小的那個子結點,則交換,否則就跳出循環
if(a[i]>a[j])
{
temp=a[j];
a[j]=a[i];
a[i]=temp;
i=j;
j=2*i+1;
}
else
break;
}
}
//創建k個元素最小堆
void createMinHead(int *a,int k)
{
//自下而上的建立堆,從最後一個非葉子結點開始初始化最小堆
for(int i=k/2-1;i>=0;i--)
adjustMinHeap(a,i,k);
}
void FindKMax(int *a,int n,int *kMax,int k)
{
for(int i=0;i<k;i++)
kMax[i]=a[i];
//初始K個元素最小堆
createMinHead(kMax,k);
for(int i=k;i<n;i++)
{
//如果該元素大於她的最小堆則替換
if(a[i]>kMax[0])
{
kMax[0]=a[i];
adjustMinHeap(kMax,0,k);
}
}
}
int main()
{
int a[]={100,2000,45,68,5,700,9,50,45,89,87};
int len=sizeof(a)/sizeof(int);
int k=5;
int * kMax=(int*)malloc(k*sizeof(int));
FindKMax(a,len,kMax,k);
for(int i=0;i<k;i++)
cout<<kMax[i]<<"\t";
return 0;
}
第五中解法思路:
利用計數排序思想,如果所有N個數都是正整數,且它們的取值範圍不太大,可以考慮申請空間,記錄每個整數出現的次數,然後再從大到小取最大的K個,比如,所有整數都在(0,MAXN)區間中的話,利用一個數組count[MAXN]來記錄每個整數出現的個數(count[i]表示整數i在所有整數中出現的個數),我們只須掃描一遍就可以得到count數組,然後尋找第K大的元素,整個算法時間複雜度爲O(n).
代碼實現:
#include <iostream>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
#define MAX 100000
int FindKMax(int *a,int k)
{
int sumCount=0;
int i;
//從大到小開始計算其元素個數,直到爲K
for(i=MAX-1;i>=0;i--)
{
sumCount+=a[i];
if(sumCount>=k)
break;
}
return i;
}
int main()
{
int a[]={100,2000,45,68,5,700,9,50,45,89,87};
int len=sizeof(a)/sizeof(int);
int count[MAX]={0};
int k=5;
//統計a元素對應出現個數
for(int i=0;i<len;i++)
{
count[a[i]]++;
}
cout<<FindKMax(count,k)<<endl;
return 0;
}