问题描述
求一维数组中最小的K个数。
方法一:排序
先把数组从小到大排序,取前K个数。时间复杂度为O(nlogn)。如果数组过大,机器内存无法同时容纳整个数组,则需要使用外部排序。
以下程序使用的是标准库algorithm中的排序方法----std::sort,代码如下:
//排序法,算法复杂度O(nlogn)
void GetTopK_Sort( int nArray[], int nCount, int k )
{
//标准库中的排序算法
sort( nArray, nArray+nCount );
if ( K_SIZE <= 20 )
{
for( int i = 0; i < k; ++i ) cout << nArray[i] << " ";
cout << endl << "----------------------------------------" << endl;
}
}
方法二:使用堆
初始化一个大顶堆,然后扫描一遍数组,往堆里插入数据,如果堆的元素个数已经到达K,那么新元素就要和堆顶比较,如果小于堆顶,则移除堆顶,插入新元素;最终我们得到k个最小的元素。时间复杂度为O(nlogk)。
代码实现见后面
方法三:快速排序思想
使用快速排序分区函数:选择一个数,把数组的数分在两部分,把比选中的数小的或者相等的数移到数组的左边,把比选中的数大的移到数组的右边,返回分区后选中数的下标。
分区结束后,如果返回的下标为K-1,那么左边k个元素即我们所找;如果返回的下标大于K-1,那么我们需要在右半部分找;如果返回的下标小于K-1,则在左半部分继续找。这样重复操作,直到找到分区函数返回的下标为K-1。由于我们并没有实现真正的快速排序,每次只会在两部分中的一部分中查找,而且划分的基准都是随机的,所以算法的平均时间复杂度为O(n)(本人还没太理解这个复杂度是怎么计算得到的,嘿嘿)。
代码说明:由于快速排序在最坏的情况下,时间复杂度为O(n^2),比如找最小元素时,总是在最大的元素处划分,或者所有元素都相同。所以代码中对分区函数作了一个优化,在一次划分中,记录是否有元素交换,如果没有,说明这部分已经有序,无需在操作下去了。
代码实现见后面
代码实现与运行性能对比
代码:
/
//求一维数组中最小的K个数
#include <iostream>
#include <algorithm>
#include <ctime>
#include <cstdlib>
using namespace std;
#define MAX_TEST 10000/*0000*/ //数据数量
#define K_SIZE 20 //k值
#define SRAND_TEST 7 //随机种子
//排序法,算法复杂度O(nlogn)
void GetTopK_Sort( int nArray[], int nCount, int k )
{
//标准库中的排序算法
sort( nArray, nArray+nCount );
if ( K_SIZE <= 20 )
{
for( int i = 0; i < k; ++i ) cout << nArray[i] << " ";
cout << endl << "----------------------------------------" << endl;
}
}
//
//使用堆 O(nlogk)
//数组nHeap的0号单元存放堆的大小
//从底部往上调整成大顶堆
void AdjustHeapFromDown( int nHeap[], int i )
{
int j = i / 2;
while( j > 0 )
{
if ( nHeap[j] < nHeap[i] )
{
int temp = nHeap[j];
nHeap[j] = nHeap[i];
nHeap[i] = temp;
j = j / 2;
}
else break;//调整结束(因为是在大顶堆的前提下插入元素)
}
}
//从上面往下调整成大顶堆
void AdjustHeapFromUp( int nHeap[], int i )
{
int s = i;
while ( s <= nHeap[0]/2 )//堆中最后一个非终端叶子结点为nHeap[0]/2
{
int m = 2*s;
if ( (2*s+1) <= nHeap[0] && nHeap[2*s+1] > nHeap[m] ) m = 2*s+1;//右孩子更大
if ( nHeap[s] < nHeap[m] )
{
int temp = nHeap[s];
nHeap[s] = nHeap[m];
nHeap[m] = temp;
//转到下一层
s = m;
}
else
{
break;
}
}
}
void InsertHeap( int nHeap[], int val, int k )
{
if ( nHeap[0] < k )
{
nHeap[++nHeap[0]] = val;
AdjustHeapFromDown( nHeap, nHeap[0] );
}
else
{
//堆个数已达到k,需要和堆顶比较,如果小于堆顶,则移除堆顶,插入新元素
if ( nHeap[1] > val )
{
nHeap[1] = val;
AdjustHeapFromUp( nHeap, 1 );
}
}
}
void GetTopK_Heap( int nArray[], int nCount, int k )
{
//创建大小为k+1的堆,0号单元放堆的大小
int* pHeap = new int[k+1];
pHeap[0] = 0;
for( int i = 0; i < nCount; ++i )
{
InsertHeap( pHeap, nArray[i], k );
}
//输出结果
if ( K_SIZE <= 20 )
{
for( int i = 1; i <= k; ++i ) cout << pHeap[i] << " ";
cout << endl << "----------------------------------------" << endl;
}
if ( NULL != pHeap )
{
delete pHeap;
pHeap = NULL;
}
}
///
//使用快速排序思想
// int Partition( int* pArray, int low, int high )
// {
// int temp = pArray[low];
// while( low < high )
// {
// while( low < high && pArray[high] >= temp ) --high;
// pArray[low] = pArray[high];
//
// while( low < high && pArray[low] <= temp ) ++low;
// pArray[high] = pArray[low];
// }
// pArray[low] = temp;
//
// return low;
// }
//int PartitionRandom( int* pArray, int low, int high )
int PartitionRandom( int* pArray, int low, int high, bool& bLeftSwap, bool& bRightSwap )
{
//随机产生基准元素
int i = low + rand() % (high-low);
int temp = pArray[i];
pArray[i] = pArray[low];
pArray[low] = temp;
temp = pArray[low];
bLeftSwap = bRightSwap = false;
while( low < high )
{
while( low < high && pArray[high] >= temp )
{
--high;//同时进行起泡操作。。。
if ( !bRightSwap &&/* pArray[high] >= temp &&*/ high != low && pArray[high] > pArray[high+1] )
{
bRightSwap = true;
int temp2 = pArray[high];
pArray[high] = pArray[high+1];
pArray[high+1] = temp2;
}
}
//pArray[low] = pArray[high];
if ( pArray[low] != pArray[high] )
{
pArray[low] = pArray[high];
bRightSwap = true;
}
while( low < high && pArray[low] <= temp )
{
++low;//同时进行起泡操作。。。
if ( !bLeftSwap &&/* pArray[low] <= temp && */low != high && pArray[low] < pArray[low-1] )
{
bLeftSwap = true;
int temp2 = pArray[low];
pArray[low] = pArray[low-1];
pArray[low-1] = temp2;
}
}
//pArray[high] = pArray[low];
if ( pArray[high] != pArray[low] )
{
pArray[high] = pArray[low];
bLeftSwap = true;
}
}
pArray[low] = temp;
return low;
}
void QSortSelectK( int nArray[], int low, int high, int k )
{
if ( low < high )
{
bool bLeftSwap = false;
bool bRightSwap = false;
int pivot = PartitionRandom( nArray, low, high, bLeftSwap, bRightSwap );
if ( pivot == k-1 ) return;
else if ( pivot > k-1 )
{
if ( !bLeftSwap ) return;
QSortSelectK( nArray, low, pivot-1, k );
}
else
{
if ( !bRightSwap ) return;
QSortSelectK( nArray, pivot+1, high, k );
}
}
}
void GetTopK_QSort( int nArray[], int nCount, int k )
{
// //int pivot = Partition( nArray, 0, nCount - 1 );
// int pivot = PartitionRandom( nArray, 0, nCount - 1 );
// while( pivot != k - 1 )
// {
// if ( pivot > k - 1 )
// {
// //pivot = Partition( nArray, 0, pivot - 1 );
// pivot = PartitionRandom( nArray, 0, pivot - 1 );
// }
// else
// {
// //pivot = Partition( nArray, pivot + 1, nCount - 1 );
// pivot = PartitionRandom( nArray, pivot + 1, nCount - 1 );
// }
// }
QSortSelectK( nArray, 0, nCount-1, k );
//结果输出
if ( K_SIZE <= 20 )
{
for( int i = 0; i < k; ++i ) cout << nArray[i] << " ";
cout << endl << "----------------------------------------" << endl;
}
}
//生成测试数据
void RandomTestData( int* pArray, int nCount )
{
for( int i = 0; i < nCount; ++i ) pArray[i] = rand()%100000000;
//for( int i = 0; i < nCount; ++i ) pArray[i] = nCount-i;
//for( int i = 0; i < nCount; ++i ) pArray[i] = 2;
//for( int i = 0; i < nCount; ++i ) pArray[i] = i;
}
void PrintArray( int nArray[], int nCount )
{
for( int i = 0; i < nCount; ++i )
{
cout << nArray[i] << " ";
}
cout << endl;
}
int main()
{
clock_t start, finish;
double duration = 0.0;
// int nArray1[] = { 1, 2, 3, 4 };
// QuickSort( nArray1, 0, _countof(nArray1)-1 );
// PrintArray( nArray1, _countof(nArray1) );
// GetTopK_Sort( nArray1, _countof(nArray1), 10 );
//
// int nArray2[] = { 2, 1, 56, 34, 23, 56, 12, 10, 6, 4, 19, 22, 89, 3, 5, 7 };
// GetTopK_QSort( nArray2, _countof(nArray2), 5 );
// GetTopK_Heap( nArray2, _countof(nArray2), 10 );
///
//排序法
//srand( (unsigned)time(NULL) );
srand( SRAND_TEST );
int* pArray = new int[MAX_TEST];
RandomTestData( pArray, MAX_TEST );
//
start = clock();
/
GetTopK_Sort( pArray, MAX_TEST, K_SIZE );
///
finish = clock();
duration = (double)(finish-start);
cout << "使用排序法----时间消耗:" << duration << "ms" << endl << endl;
///
//使用堆
srand( SRAND_TEST );
RandomTestData( pArray, MAX_TEST );
//
start = clock();
/
GetTopK_Heap( pArray, MAX_TEST, K_SIZE );
///
finish = clock();
duration = (double)(finish-start);
cout << "使用堆----时间消耗:" << duration << "ms" << endl << endl;
///
///
//使用快速排序思想
srand( SRAND_TEST );
RandomTestData( pArray, MAX_TEST );
//
start = clock();
/
//srand( (unsigned)time(NULL) );
//int nArray[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
//GetTopK_QSort( nArray, _countof(nArray), K_SIZE );
GetTopK_QSort( pArray, MAX_TEST, K_SIZE );
///
finish = clock();
duration = (double)(finish-start);
cout << "使用快排分区----时间消耗:" << duration << "ms" << endl << endl;
///
if ( NULL != pArray )
{
delete pArray;
pArray = NULL;
}
return 0;
}
运行性能对比:
1、以下是从100000000条有序数据中找最小的2000个数的运行结果
2、以下是从1000000条有序数据中找最小的20个数的运行结果
3、以下是为10000条随机数据中找最小的20个数的运行结果
4、以下是在1亿条随机数据中找最小的20个数的运行结果
系列文章说明:
1.本系列文章[算法练习],仅仅是本人学习过程的一个记录以及自我激励,没有什么说教的意思。如果能给读者带来些许知识及感悟,那是我的荣幸。
2.本系列文章是本人学习陈东锋老师《进军硅谷,程序员面试揭秘》一书而写的一些心得体会,文章大多数观点均来自此书,特此说明!
3.文章之中,难免有诸多的错误与不足,欢迎读者批评指正,谢谢.
作者:山丘儿
标签:int,pArray,练习,nHeap,high,算法,TopK,low,nArray From: https://blog.51cto.com/u_15905375/5919886