从一亿个数中找出最大的一万个数

问题定义:

        从一亿个数中找出最大的一万个数

不假思索:

        拿到这道题,马上就会想到的方法是建立一个数组把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;
}

测试结果:



结论:

        经过层层优化,这个难题只需要半秒时间就解决了,可见算法好坏对于程序效率的影响是巨大的。

参考资料:

        赖永浩 . 算法优化 . 某某杂志期刊.51--54.





展开阅读全文

没有更多推荐了,返回首页