1亿个数据取前1万大的整数

来源:互联网 发布:nib文件怎么打开 mac 编辑:程序博客网 时间:2024/05/25 08:12


数据规模分析

 

不考虑操作系统的区别,通常将C++中的一个整型变量认为4bytes。那么1亿整型需要400M左右的内存空间。当然,就现代PC机而言,连续开辟400M的内存空间还是可行的。因此,下面的讨论只考虑在内存中的情况。为了讨论方便,假设M=1亿,N=1万。

 

 

用大拇指想想

略微考虑一下,使用选择排序。循环1万次,每次选择最大的元素。源代码如下:

//解决方案1,简单选择排序//BigArr[]存放1亿的总数据、ResArr[]存放1万的总数据void solution_1(int BigArr[], int ResArr[] ){       for( int i = 0; i < RES_ARR_SIZE; ++i ){              int idx = i;              //选择最大的元素              for( int j = i+1; j < BIG_ARR_SIZE; ++j ){                     if( BigArr[j] > BigArr[idx] )                            idx = j;              }              //将最大元素交换到开始位置              ResArr[i] = BigArr[idx];              std::swap( BigArr[idx], BigArr[i] );       }}


性能分析: 哇靠!时间复杂度为O(M*N)。 有人做过实验《从一道笔试题谈算法优化(上)》,需要40分钟以上的运行时间。太悲剧了......

 

当然,用先进的排序方法(比如快排),时间复杂度为O(M*logM)。虽然有很大的改进了,据说使用C++的STL中的快排方法只需要32秒左右。确实已经达到指数级的优化了,但是否还能够优化呢?

 

 

 

 

稍微动下脑子

我们只需要1万个最大的数,并不需要所有的数都有序,也就是说只要保证的9999万个数比这1万个数都小就OK了 。我们可以通过下面的方法来该进:

 

(1) 先找出M数据中的前N个数。确定这N个数中的最小的数MinElement。

(2) 将  (N+1) —— M个数循环与MinElement比较,如果比MinElement还小,则不处理。如果比MinElement大,则与MinElement交换,然后重新找出N个数中的MinElement。

//解决方案2void solution_2( T BigArr[], T ResArr[] ){       //取最前面的一万个       memcpy( ResArr, BigArr, sizeof(T) * RES_ARR_SIZE );       //标记是否发生过交换       bool bExchanged = true;       //遍历后续的元素       for( int i = RES_ARR_SIZE; i < BIG_ARR_SIZE; ++i ){              int idx;              //如果上一轮发生过交换              if( bExchanged ){                     //找出ResArr中最小的元素                     int j;                     for( idx = 0, j = 1; j < RES_ARR_SIZE; ++j ){                            if( ResArr[idx] > ResArr[j] )                                   idx = j;                     }              }              //这个后续元素比ResArr中最小的元素大,则替换。              if( BigArr[i] > ResArr[idx] ){                     bExchanged = true;                     ResArr[idx] = BigArr[i];              }else                     bExchanged = false;       }}

性能分析: 最坏的时间复杂度为O((M-N)*N)。咋一看好像比快排的时间复杂度还高。但是注意是最坏的,实际上,并不是每次都需要付出一个最小值O(N)的代价的。因为,如果当前的BigArr[i]<ResArr[idx]的话,就不需要任何操作,则1——N的最小值也就没有变化了。下一次也就不需要付出O(N)的代价去寻找最小值了。当然,如果M基本正序的话,则每次都要交换最小值,每次都要付出一个O(N)代价。最坏的情况比快排还要差。

 

就平均性能而言,改进的算法还是比快排要好的,其运行时间大约在2.0秒左右。

 

 

使劲动下脑子

上面的解决方案2还有一个地方不太好。当BigArr[i]>ResArr[idx]时,则必须交换这两个数,进而每次都需要重新计算一轮N个数的最小值。只改变了一个数就需要全部循环一次N实在是不划算。能不能下一次的最小值查找可以借助上一次的比较结果呢?

 

基于这样一个想法,我们考虑到了堆排序的优势(每一次调整堆都只需要比较logN的结点数量)。因此我们再做一次改进:

 

(1) 首先我们把前N个数建立成小顶堆,则根结点rootIdx。

(2) 当BigArr[i]>ResArr[rootIdx]时,则交换这两个数,并重新调整堆,使得根结点最小。

 

性能分析:显然,除了第一次建堆需要O(N)时间的复杂度外,每一次调整堆都只需要O(logN)的时间复杂度。因此最坏情况下的时间复杂度为O((M-N)*logN),这样即使在最坏情况下也比快排的O(M*logM)要好的多了。

 

另外:实际上也可以使用二分查找的思想,第一次找N中的最小值的时候将N排序。以后每次替换最小值,都使用二分查找在logN代价下找到当前N的最小值即可。与使用堆的过程如出一辙

0 0
原创粉丝点击