回 帖 发 新 帖 刷新版面

主题:第73次编程比赛题目

题目:求最大的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]

回复列表 (共20个回复)

沙发

代码分两个版本:
普通版:快速选择算法(使用递归实现),三数中值选择枢纽元素(函数实现)
优化版:快速选择算法(使用迭代实现),三/五数中值选择枢纽元素(宏实现)

板凳

#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 楼

#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 楼

/*这个是没有考虑相同数值的程序,例如给定的数字中有两个最大数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 楼

/*这个是考虑了相同数字的程序,例如给定的一组数中有两个最大数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 楼


/***本算法我自知是不怎么好的,用的时间比较长啊******
****真不好有意思拿出来晒,呵呵,准备学习高手们的了**/

#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 楼

//很一般的实现,期待楼主妙解

[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 楼

//本来还可以优化下,如果选较大的数较多的时候,可以直接选小的
//如果直接使用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 楼

这个题目没有意思。
看本站这个帖子:http://bbs.pfan.cn/post-283971.html
假如仅仅找出最大的数而不用对他们进行排序的话,速度还会更快一些,C++STL中有个nth_element可以胜任,需要排序就再配合sort,如果选择的数据比例比较小就用C++ STL的partial_sort

10 楼


//开始想的是堆排序,但是后来发现怎么都是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;
  }
}



我来回复

您尚未登录,请登录后再回复。点此登录或注册