主题:第73次编程比赛题目
boxertony [专家分:23030] 发布于 2008-09-28 17:57:00
题目:求最大的K个数
随机给定10000000个数,请找出其中最大的K(1<=K<N)个数。测试时统计K=1,10,100,1000,10000,100000,1000000,5000000等8种情况的时间总和(每次测试重新生成数据),花费总时间最少的为冠军。
呵呵,这个题目比较简单,方法也比较多,估计很多人都做过。但要效率足够高还是稍有点难度的。希望总时间耗费能在1秒以内(当然以我的电脑为准啦,嘿嘿)。
函数原型如下:
// a[] -- 存放10000000个整数
// n = 10000000
// k -- 待求取的最大数的个数
// result[] -- 存放获取的最大的k个数(不要求排序)
void MaxK(int a[], int n, int k, int result[]);
比赛截止时间:2008年10月7日22:00
备注:stl里面有nth_element这个函数可以直接实现,希望大家不要用,其他函数随意。
提问请到[url=http://www.programfan.com/club/post-285957.html]http://www.programfan.com/club/post-285957.html[/url]
最后更新于:2008-10-06 21:19:00
回复列表 (共20个回复)
沙发
yihui.L [专家分:0] 发布于 2008-10-01 22:58:00
代码分两个版本:
普通版:快速选择算法(使用递归实现),三数中值选择枢纽元素(函数实现)
优化版:快速选择算法(使用迭代实现),三/五数中值选择枢纽元素(宏实现)
板凳
yihui.L [专家分:0] 发布于 2008-10-01 23:18:00
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define N 10000000
int array[N];
int result[N];
/* 交换两整数的值 */
void Swap( int *a, int *b )
{
int t = *a;
*a = *b;
*b = t;
}
/* *a <= *b <= *c */
void Median( int *a, int *b, int *c )
{
if( *a > *b ) Swap( a, b );
if( *a > *c ) Swap( a, c );
if( *b > *c ) Swap( b, c );
}
/* 快速选择排序算法:三数中值法选择枢纽元素 */
int QuickSelect( int a[], int l, int r, int k )
{
int low = l;
int high = r - 1;
int mid = low + ( high - low ) / 2;
int cnt;
int pivot;
Median( &a[mid], &a[low], &a[high] );
pivot = a[low];
while( low < high )
{
while( pivot >= a[high] && low < high ) --high;
a[low] = a[high];
while( a[low] >= pivot && low < high ) ++low;
a[high] = a[low];
}
a[low] = pivot;
cnt = low - l + 1;
if( cnt < k )
return QuickSelect( a, low + 1, r, k - cnt );
else
if( cnt == k ) return pivot;
else
return QuickSelect( a, l, low, k );
}
/* 生成随机数组 */
void RandA( int a[], int n )
{
int i;
srand( time( 0 ) );
for( i = 0; i < n; i++ )
a[i] = rand();
}
/*
* a[] -- 存放10000000个整数
* n = 10000000
* k -- 待求取的最大数的个数
* result[] -- 存放获取的最大的k个数(不要求排序)
*/
void MaxK( int a[], int n, int k, int result[] )
{
int i;
QuickSelect( a, 0, n, k );
for( i = 0; i < k; i++ )
result[i] = a[i];
}
int main()
{
clock_t start, stop;
int i, K[8] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 5000000 };
RandA( array, N );
start = clock();
for( i = 0; i < 8; i++ )
MaxK( array, N, K[i], result );
stop = clock();
printf( " %ld / %ld = %lf seconds\n",
stop - start, CLOCKS_PER_SEC,
( double )( stop - start ) / CLOCKS_PER_SEC );
return 0;
}
3 楼
yihui.L [专家分:0] 发布于 2008-10-01 23:21:00
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#define N 10000000
int array[N];
int result[N];
/* 交换两个数的值 */
#define Swap( a, b, t ) ( (t) = (a), (a) = (b), (b) = (t) )
/* a <= b <= c */
#define Median3( a, b, c, t ) \
{ \
if( (a) > (b) ) Swap( a, b, t ); \
if( (a) > (c) ) Swap( a, c, t ); \
if( (b) > (c) ) Swap( b, c, t ); \
}
/* c为5数中值 */
#define Median5( a, b, c, d, e, t ) \
{ \
if( (a) > (b) ) Swap( a, b, t ); \
if( (a) > (c) ) Swap( a, c, t ); \
if( (b) > (c) ) Swap( b, c, t ); \
if( (c) > (e) ) Swap( c, e, t ); \
if( (c) > (d) ) Swap( c, d, t ); \
if( (b) > (c) ) Swap( b, c, t ); \
if( (a) > (c) ) Swap( a, c, t ); \
}
/* 快速选择排序算法:三/五数中值法选择枢纽元素 ( 迭代实现 ) */
int QuickSelect( int a[], int l, int r, int k )
{
int left = l;
int right = r;
int low, mid, high;
int cnt, tmp, part, pivot;
while( 1 )
{
low = left;
high = right - 1;
mid = ( high + low ) >> 1;
if ( 45 < high - low )
{
part = ( right - left ) >> 2;
Median5( a[mid-part], a[mid], a[low],
a[high], a[mid+part], tmp );
}
else
Median3( a[mid], a[low], a[high], tmp );
pivot = a[low];
while( low < high )
{
while( pivot >= a[high] && low < high ) --high;
a[low] = a[high];
while( a[low] >= pivot && low < high ) ++low;
a[high] = a[low];
}
a[low] = pivot;
cnt = low - left + 1;
if( cnt < k )
{
left = low + 1;
k -= cnt;
}
else if( cnt == k )
return pivot;
else
right = low;
}
}
/* 选择数组最大值并返回 */
int SelectMax( int a[], int n )
{
int i, t, max = 0;
t = n - 8;
for( i = 1; i < t; i += 8 ) /* 循环展开 */
{
if( a[max] < a[i] ) max = i;
if( a[max] < a[i+1] ) max = i + 1;
if( a[max] < a[i+2] ) max = i + 2;
if( a[max] < a[i+3] ) max = i + 3;
if( a[max] < a[i+4] ) max = i + 4;
if( a[max] < a[i+5] ) max = i + 5;
if( a[max] < a[i+6] ) max = i + 6;
if( a[max] < a[i+7] ) max = i + 7;
}
for( ; i < n; i++ )
if( a[max] < a[i] ) max = i;
return a[max];
}
/* 生成顺序数组 */
void SequenceA( int a[], int n )
{
int i;
for( i = 0; i < n; i++ )
a[i] = i + 1;
}
/* 生成逆序数组 */
void ReverseA( int a[], int n )
{
int i;
for( i = 0; i < n; i++ )
a[i] = n - i;
}
/* 生成随机数组 */
void RandA( int a[], int n )
{
int i;
srand( time( 0 ) );
for( i = 0; i < n; i++ )
a[i] = rand();
}
/*
* a[] -- 存放10000000个整数
* n = 10000000
* k -- 待求取的最大数的个数
* result[] -- 存放获取的最大的k个数(不要求排序)
*/
void MaxK( int a[], int n, int k, int result[] )
{
/* 参数必须合法 */
assert( a != NULL && k > 0 && n >= k && result != NULL );
if( k == 1 )
result[0] = SelectMax( a, n );
else
{
QuickSelect( a, 0, n, k );
memcpy( result, a, k * sizeof( int ) );
}
}
int main()
{
clock_t start, stop;
int i, K[8] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 5000000 };
// SequenceA( array, N ); /* 测试顺序数组 */
// ReverseA( array, N ); /* 测试逆序数组 */
RandA( array, N ); /* 测试随机数组 */
start = clock();
for( i = 0; i < 8; i++ )
MaxK( array, N, K[i], result );
stop = clock();
printf( " %ld / %ld = %lf seconds\n",
stop - start, CLOCKS_PER_SEC,
( double )( stop - start ) / CLOCKS_PER_SEC );
return 0;
}
4 楼
mo_0820 [专家分:50] 发布于 2008-10-02 15:49:00
/*这个是没有考虑相同数值的程序,例如给定的数字中有两个最大数100,输出中会有两个100.我用的电脑只能算到n=1000000,不知楼主的如何呢,下面的程序仍按要求编写n=100000000*/
#include <stdlib.h>
#include <stdio.h>
#define N 100000000
main()
{
int a[N],result[N],i,k[8]={1,10,100,1000,10000,100000,1000000,5000000};
void shiyan(int a[],int n,int result[],int k);
for(i=0;i<N;i++)
a[i]=rand();
for(i=0;i<8;i++)
{
printf("最大的%d个数:\n",k[i]);
shiyan(a,N,result,k[i]);
printf("\n");
}
}
void shiyan(int a[],int n,int result[], int k)
{
int i,j,l,b,c;
for(i=0;i<k;i++)
result[i]=a[i];
for(j=k;j<n;j++)
{
for(i=0;i<k;i++)
if(result[i]<a[j])
{
b=result[i];
result[i]=a[j];
l=i;
break;
}
for(;l<k;l++)
if(result[l]<b)
{
c=result[l];
result[l]=b;
b=c;
}
}
for(i=0;i<k;i++)
printf("%d ",result[i]);
}
5 楼
mo_0820 [专家分:50] 发布于 2008-10-02 15:53:00
/*这个是考虑了相同数字的程序,例如给定的一组数中有两个最大数100,输出中只有一个100.我用的电脑只能算到n=1000000,不知楼主的如何呢,下面的程序仍按要求编写n=10000000*/
#include <stdlib.h>
#include <stdio.h>
#define N 100000000
main()
{
int a[N],result[N],i,k[8]={1,10,100,1000,10000,100000,1000000,5000000};
void shiyan(int a[],int n,int result[],int k);
for(i=0;i<N;i++)
a[i]=rand();
for(i=0;i<8;i++)
{
printf("最大的%d个数:\n",k[i]);
shiyan(a,N,result,k[i]);
printf("\n");
}
}
void shiyan(int a[],int n,int result[], int k)
{
int i,j=0,l,c,b,panduan;
result[0]=a[0];
for(i=1;;i++)
{
panduan=0;
if(j==k-1)
break;
for(l=0;l<=j;l++)
if(result[l]==a[i])
{
panduan=1;
break;
}
if(!panduan)
{
j=j+1;
result[j]=a[i];
}
if(i==n-1)
{
k=j+1;
printf("重复的数太多,仅%d个不相同的数:\n",k);
break;
}
}
for(j=i;j<n;j++)
{
panduan=0;
for(i=0;i<k;i++)
if(result[i]==a[j])
{
panduan=1;
break;
}
if(!panduan)
{
for(i=0;i<k;i++)
if(result[i]<a[j])
{
c=result[i];
result[i]=a[j];
l=i;
break;
}
for(;l<k;l++)
if(result[l]<c)
{
b=result[l];
result[l]=c;
c=b;
}
}
}
for(i=0;i<k;i++)
{
printf("%-7d",result[i]);
if(!((i+1)%10))
printf("\n");
}
}
6 楼
strugglemyself [专家分:100] 发布于 2008-10-03 10:08:00
/***本算法我自知是不怎么好的,用的时间比较长啊******
****真不好有意思拿出来晒,呵呵,准备学习高手们的了**/
#include<iostream>
#include<time.h>
#include<stdio.h>
using namespace std;
#define M 100000 //本计算机限度只能到M=100000,否则不能运行了
int Order(int Max,int Kdata[]);
void OrderKdata(long int *p, int K);
void PrintResult(int K);
int Result[M]={0};
void main()
{
int max,totallTime;
int Kdata[8]={1,10,100,1000,10000,100000,1000000,5000000};
cout<<"输入随机数的最大值范围Max"<<endl;
cin>>max;
totallTime=Order(max,Kdata);
cout<<"耗费总时间="<<totallTime<<endl;
}
int Order(int Max,int Kdata[])
{
long int a[M];
int K,begineTime,endTime,totallTime=0;
for(int j=0;j<8;j++)
{
srand(time(0));
for(int i=0;i<M;i++)
a[i]=rand()%Max; //生成随机数组从(0~Max)
K=Kdata[j];
begineTime=clock(); //开始计时
OrderKdata(a,K);
endTime=clock(); //结束计时
cout<<"K="<<K<<"需要的时间=" //输出本次执行需要的时间
<<(endTime-begineTime)<<"毫秒"<<endl;
totallTime+=(endTime-begineTime); //计算总时间
PrintResult(K);
}
return totallTime;
}
void OrderKdata(long int *p,int K)
{
int m;
for(int k=0;k<M;k++) //冒泡排序法
{
for(int i=0;i<M-k-1;i++)
if(p[i]<p[i+1])
{
m=p[i];
p[i]=p[i+1];
p[i+1]=m;
}
}
for(int j=0;j<K;j++)
Result[j]=p[j];
}
void PrintResult(int K) //输出本次最大的K个数
{
cout<<"输出"<<K<<"个最大数:"<<endl;
for(int i=0;i<K;i++)
cout<<Result[i]<<" ";
cout<<endl<<"**********************************************************************"<<endl;
}
7 楼
GaussCheng [专家分:330] 发布于 2008-10-03 14:12:00
//很一般的实现,期待楼主妙解
[code=c]
#include<iostream>
using namespace std;
const int ITEMNUM = 10000000;
int Partition(int a[],int p,int r);
int RandomizedPartition(int a[],int p,int r);
int RandomizedSelect(int a[],int p,int r,int i);
void swap(int &a,int &b);
void TopKNum(int a[],int n,int k,int result[]);
int main()
{
/*我不知道你是怎么测试的,所以留空了,调用TopkNum就会得出最大的k个数了*
** TopKNum(输入数组,输入数组大小,待求取的最大数的个数,返回结果数组)
*/
return 0;
}
/***********************
求给定数组中最大的k个数
思想是找出第n - k + 1 小的元素,用该元素初始化result,然后扫描输入数组,找出k个大于该元素的数
***********************/
void TopKNum(int a[],int n,int k,int result[])
{
int kthNum = RandomizedSelect(a,0,n - 1,n - k + 1);
for(int i = 0; i != k;++i)
{
result[i] = kthNum;
}
int flag = 0; //记录找到的大于第n - k + 1元素的个数
for(int i = 0,j = 0; i != n && flag != k; ++i)
{
if(a[i] > kthNum)
{
result[j] = a[i];
++j;
++flag;
}
}
}
//随机版本的线性选择算法,返回第i个最小元素的值
int RandomizedSelect(int a[],int p,int r,int i)
{
if (p == r)
{
return a[p];
}
int q = RandomizedPartition(a,p,r);
int k = q - p + 1;
if(i == k)
{
return a[q];
}
else if(i < k)
{
return RandomizedSelect(a,p,q - 1,i);
}
else
{
return RandomizedSelect(a,q + 1,r,i - k);
}
}
int RandomizedPartition(int a[],int beg,int end)
{
int i = ::rand() % (end - beg ) + beg;
swap(a[end],a[i]);
return Partition(a,beg,end);
}
int Partition(int a[],int beg,int end)
{
int pivot = a[end];
int boundary = beg;
for(int i = beg;i != end;++i)
{
if(a[i] <= pivot)
{
swap(a[i],a[boundary]);
++boundary;
}
}
swap(a[end],a[boundary]);
return boundary;
}
void swap(int &a,int &b)
{
int tmp = a;
a = b;
b = tmp;
}
[/code]
8 楼
fenix124 [专家分:70] 发布于 2008-10-05 11:42:00
//本来还可以优化下,如果选较大的数较多的时候,可以直接选小的
//如果直接使用result来做堆,可以省一次复制
//本来*2和/2想用移位的,但是移位计算结果居然错误...
int heap[5000001];//不够自己调大
int heapsize;
void push(int i)
{
int current,parent,temp;
heap[heapsize] = i;
current = heapsize;
while(current != 0)
{
parent = current/2;
if(heap[parent] > heap[current])
{
temp = heap[current];
heap[current] = heap[parent];
heap[parent] = temp;
current = parent;
}
else break;
}
heapsize++;
}
void adjust(int pos)
{
int current,lc,rc,temp,min;
current = pos;
while(1)
{
min = current;
lc = current*2+1;
rc = current*2+2;
if(lc < heapsize && heap[min] > heap[lc])
{
min = lc;
}
if(rc < heapsize && heap[min] > heap[rc])
{
min = rc;
}
if(min != current)
{
temp = heap[current];
heap[current] = heap[min];
heap[min] = temp;
current = min;
}
else break;
}
}
void MaxK(int a[], int n, int k, int result[])
{
int i,l;
heapsize = 0;
for(i = 0;i < k;i++)
{
push(a[i]);
}
for(;i < n;i++)
{
if(a[i] > heap[0])
{
heap[0] = a[i];
adjust(0);
}
}
for(i = 0;i < k;i++)
result[i] = heap[i];
}
9 楼
Chipset [专家分:16190] 发布于 2008-10-06 10:18:00
这个题目没有意思。
看本站这个帖子:http://bbs.pfan.cn/post-283971.html
假如仅仅找出最大的数而不用对他们进行排序的话,速度还会更快一些,C++STL中有个nth_element可以胜任,需要排序就再配合sort,如果选择的数据比例比较小就用C++ STL的partial_sort
10 楼
JackieRasy [专家分:3050] 发布于 2008-10-06 20:11:00
//开始想的是堆排序,但是后来发现怎么都是O(nlogn)——初学算法,说错了请不要笑话,呵呵——
//后来就想了下面的办法
//主要思想是快速排序中来的,优化的时候用到了分治思想
int find(int a[],int n,int k)//算法书一开始就是讲递归,我的思维目前还局限在这个上面
{
int curvalue=a[0];
int temp[n-1];
int i, position, t=n-2;
for(i=1;i<n;i++)
{
if(a[i]<curvalue) temp[position++]=a[i];
else temp[t--]=a[1];
}
if(s==k-1) return curvalue;
if(s<k-1) return find(b+s,n-s-1,k-s-1);
else return find(b,s,k);
}
void MaxK(int a[], int n, int k, int result[])
{
int kthnumber=find(a,n,k);
int i,j=0;
for(i=0,i<n<i++) // 进一步的优化肯定在这里了,因为上一个函数实际已经完成了这个功能
//由于楼主给出的是void的返回值,所以这个递归不成立,时间有限,明天就要
//交卷了,也开始上课了,先写到这里了,
{
if(a[i]>= kthnumber) result[j++]=a[i];
continue;
}
}
我来回复