问题定义:
从一亿个数中找出最大的一万个数
不假思索:
拿到这道题,马上就会想到的方法是建立一个数组把1亿个数装起来,然后用for循环遍历这个数组,找出最大的1万个数来。原因很简单,如果要找最大的那个数,就是这样解决的;而找最大的一万个数,只是重复一万遍。这个解法类似于选择排序,一次将一个正确解放在合适的位置,重复一万次,所以时间复杂度为O(n *m),如果你愿意去实现一下,会发现不等个几十分钟是不会出结果的。
稍做思考:
上面的解决方案类似于一个选择排序,而我们知道,所有排序算法中选择排序是比较慢的,所以我们选择快速排序,将整个数组都排好续,然后取前一万个数就是我们想要的结果,solution1就是采用这种方法,将时间减少到只要几秒,可见快速排序真的很快。
深入思考:
上面的方法已经有一个不错的效率了,但是,这里我们做了很多无用功,题目只要求我们将前一万个最大的数找到,我们却排序了整个数组,稍微相一下就知道还有很大的优化空间。方法二是解设前一万个数是我们需要找的一万个数,但是假设不一定成立,那么,我们只需要讲后续元素与前一万个数中的最小元素比较,如果最小元素比后续元素小,则交换数据,这样只需要遍历一遍大数组就能得到正确解。时间复杂度为O(n).solution2就是采用这种方法
深思熟虑:
solution3的基本思路和solution2是一样的,不过做了两点优化。在solution2中,我们需要不断的找出前一万个数中最小的元素,是否可以进行优化呢?答案是肯定的,优化的方法就是维持前一万个数基本有序,则每次只需要查找一个很小的范围,就能找到前一万个数中最小的元素。还有点优化就是,前一万个数无序的时候,查找时间就变长了,就退化成solution2了,所以再交换600次数据以后,再对前一万个数进行排序,这样能提高查找最小元素的时间。
不断否定自己的方法:
solution4测试了使用mutilset来保存前一万个数的情况,我们知道mutilset采用的是红黑树,且容器中的元素是有序的,正好符合我们的需求,实际测试发现,先率没有solution3快。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <set>
#include <algorithm>
#include <iostream>
#include <sys/times.h>
#include <unistd.h>
#define DEBUG
using namespace std;
multiset<int> s;
const int BIG_ARR_SIZE = 100000000;
const int RESULT_ARR_SIZE = 10000;
bool compre( int a, int b)
{
return a > b;
}
void solution1( int *buff)
{
sort( buff, buff + BIG_ARR_SIZE, compre);
}
void swap( int *buff, int i, int j)
{
int temp = buff[i];
buff[i] = buff[j];
buff[j] = temp;
}
void solution2( int *buff)
{
bool bExchanged = true;
int i, idx, j;
for( i = RESULT_ARR_SIZE; i < BIG_ARR_SIZE; i++)
{
if( bExchanged )
{
//找出前一万个数中最小的
for( idx = 0, j = 1; j < RESULT_ARR_SIZE; j++)
{
if( buff[idx] > buff[j])
idx = j;
}
}
//在后续元素比前一万个元素最小元素大,则替换
if( buff[i] > buff[idx])
{
swap( buff, i, idx);
bExchanged = true;
}
else
{
bExchanged = false;
}
}
}
void solution3( int *buff)
{
sort(buff, buff+RESULT_ARR_SIZE, compre);
int minElemIdx = RESULT_ARR_SIZE -1;
int zoneBeginIdx = minElemIdx;
for( int i = RESULT_ARR_SIZE; i < BIG_ARR_SIZE; i++)
{
if( buff[i] > buff[minElemIdx] )
{
swap(buff, i, minElemIdx);
if( minElemIdx == zoneBeginIdx )
{
zoneBeginIdx--;
if( zoneBeginIdx < RESULT_ARR_SIZE - 600 )
{
sort( buff, buff + RESULT_ARR_SIZE, compre);
zoneBeginIdx = minElemIdx = RESULT_ARR_SIZE - 1;
}
continue;
}
int idx = zoneBeginIdx;
int j = idx + 1;
//查找最小元素
for(; j < RESULT_ARR_SIZE; j++)
{
if( buff[idx] > buff[j])
idx = j;
}
minElemIdx = idx;
}
}
}
void solution4(int *buff)
{
int i;
for( i = 0; i < RESULT_ARR_SIZE; i++)
{
s.insert( buff[i]);
}
for(i = RESULT_ARR_SIZE; i < BIG_ARR_SIZE; i++)
{
if( buff[i] > *s.begin() )
{
s.insert(buff[i]);
s.erase( s.begin());
}
}
}
int main(int argc, char* argv[])
{
//生成1亿个整数
int deno = BIG_ARR_SIZE * 10;
int i;
int *buff;
int *backup;
struct tms begTime, endTime;
int result[RESULT_ARR_SIZE];
long beg,end;
buff = new int[BIG_ARR_SIZE];
backup = new int[BIG_ARR_SIZE];
int clocks_per_sec = sysconf( _SC_CLK_TCK);
srand(time(0));
for( i = 0; i < BIG_ARR_SIZE; i++ )
{
buff[i] = rand() % deno;
}
//将原始数据备份,其他解决方案使用同样的数据,才有可比性
memcpy(backup, buff, sizeof(int) * BIG_ARR_SIZE);
#ifdef DEBUG
beg = times(&begTime);
solution1(buff);
end = times(&endTime);
cout << "1.Use sort algorithm in STL: " << (end - beg) * 1000 / clocks_per_sec << " ms" << endl;
for( i = 0; i <9; i++)
{
cout<< buff[i] << "\t";
}
cout << endl;
#endif
memcpy( buff, backup, sizeof(int) * BIG_ARR_SIZE);
beg = times(&begTime);
solution2(buff);
end = times(&endTime);
cout << "2.Assume the first 10000 numbers is larger then the later number: " << (end - beg) * 1000 / clocks_per_sec << " ms" << endl;
#ifdef DEBUG
sort( buff, buff + RESULT_ARR_SIZE, compre);
for( i = 0; i <9; i++)
{
cout<< buff[i] << "\t";
}
cout << endl;
#endif
memcpy( buff, backup, sizeof(int) * BIG_ARR_SIZE);
beg = times(&begTime);
solution3(buff);
end = times(&endTime);
cout << "3.Assume the first 10000 numbers is larger then the later number(with optimization): " << (end - beg) * 1000 / clocks_per_sec << " ms" << endl;
#ifdef DEBUG
sort( buff, buff + RESULT_ARR_SIZE, compre);
for( i = 0; i <9; i++)
{
cout<< buff[i] << "\t";
}
cout << endl;
#endif
memcpy( buff, backup, sizeof(int) * BIG_ARR_SIZE);
beg = times(&begTime);
solution4(buff);
end = times(&endTime);
cout << "4.Assume the first 10000 numbers is larger then the later number(with multiset): " << (end - beg) * 1000 / clocks_per_sec << " ms" << endl;
return 0;
}