问题描述
给定一个无序数组,找出最大的K个数。
以下方法可以解决此类问题。
1、基于快排Partition
快速排序中,其中最重要的一步是如何找到某个基准值的最终在数组的存放位置index,找到这个位置index后,则index左边的值全小于等于基准值,index右边的值全大于基准值。在本题中要找TOP K大的数,那么过程必须反着来,也即index左边大于基准值,右边小于基准值。
那么我们可以通过比较这个index和K值进行比较:
(1)若Sa组的个数大于或等于K,则继续在Sa分组中找取最大的K个数字 。
(2)若Sa组中的数字小于K ,其个数为T,则继续在Sb中找取 K-T个数字 。
首先需要找到基准值的位置,通过Partition函数实现:
int Partition(int a[], int left, int right)
{
int i,j,tmp;
i=left;j=right;tmp=a[i]; //保存当前基准值
while(i<j) //本次划分终止条件
{
while(i<j && a[j]<tmp) //从后往前找,如果比基准值大,则j--
j--;
if(i<j)
{
a[i]=a[j]; //将小于等于基准值的a[j],填充到前一个位置a[i]
i++;
}
while(i<j && a[i]>tmp) //从前往后找,如果比基准值小,则i++</span>
i++;
if(i<j)
{
a[j]=a[i]; //将大于等于基准值的a[i],填充到前一个位置a[j]
j--;
}
}
a[i]=tmp; //找到本次基准值的最终位置
return i;
}
int FindTopKNums(int a[], int left, int right, int K)
{
int index=-1;
if(left<right)
{
int idx=Partition(a,left,right);
int len=idx-left+1; //该子数组中,到该次划分的位置,总共有len个元素
if(len==K) <span style="white-space:pre"> //找到TOP K的元素下标
index=idx;
else if(len<K) //如果元素个数不足K个,则在划分的右数组进行查找,此时只需要找TOP K-len
index=FindTopKNums(a,idx+1,right,K-len);
else<span style="white-space:pre"> //如果划分的左数组元素个数大于K个,此时只需要在这个数组里面找TOP K
index=FindTopKNums(a,left,idx-1,K);
}
return index;
}
求得TOP K元素
int main()
{
int a[]={1,2,4,5,3};
int K=3;
int index=FindTopKNums(a,0,4,K);
for(int i=0;i<K;i++)
printf("%d ",a[i]);
return 0;
}
值得一提的是:考虑到某些数组元素的特殊性,可以采用Random Partition,也就是进行划分的时候,并不是从该数组的首元素开始,而是随机一个下标位置开始
//随机划分函数
int RandomPartition(int a[],int left,int right)
{
srand((int)time(0));
int i = left+rand()%(right-left+1);//产生随机数
int temp = a[i];
a[i] = a[left];
a[left] = temp;
return Partition(a,left,right);//调用划分函数
}
2、基于堆排序
上面基于快排Partition的方法,有一个缺点,就是需要比较很多次,而堆排序正是可以减少比较的次数。
该方法的思想:用容量为K的最小堆来存储最大的K个数,首先对这K个元素的数组建最小堆,那么堆顶元素a[0]是这K个元素里面最小的,接着遍历原始数组a的剩余元素,如果比堆顶的元素大,则将其替换堆顶元素,并重新调整此K个元素构成的最小堆。
堆调整:
//该函数调整下标k对应的元素a[k]在小顶堆的位置,下标从0开始
void MinHeapSift(int a[], int k, int n)
{
int i,j,temp;
i=k;
j=2*i+1;
temp=a[k];
while(j<n)
{
if(a[j+1]<a[j]) //找出左右孩子中较小者
j++;
if(temp<=a[j]) //不需要调整
break;
a[i]=a[j];
i=j;
j=2*i+1;
}
a[i]=temp;
}
建堆:
void BuildHeap(int a[], int n)
{
for(int i=n/2-1;i>=0;i--)
MinHeapSift(a, i, n);
}
void FindTopKNums(int a[], int topK[], int n, int K)
{
for(int i=0;i<K;i++)
topK[i]=a[i];
BuildHeap(topK, K); //建堆
for(int i=K;i<n;i++)
{
if(a[i]>topK[0])
{
topK[0]=a[i];
MinHeapSift(topK,0,K); //调整
}
}
}
int main()
{
int a[]={1,2,4,5,3};
int topK[4];
int K=4;
FindTopKNums(a,topK,5,K);
for(int i=0;i<K;i++)
printf("%d ",a[i]);
return 0;
}