某笔试题:内存中有一个长数组,有100W条记录, 每个记录为一个struct array, sizeof( array ) = 512, 在这个struct里有一个int型成员变量weight, 现要取得按个weight值从大到小排序的前500个数组单元(求N里的前K个大的数)
直接贴代码吧,废话少讲(先说方法吧)~~~~解释在注释里:)
const static long N = 10000000;
const static long K = 100;
static long cnt;
struct Elem
{
Elem()
{
weight = rand()%100000;
}
long weight;
//char data[512];
};
void main()
{
srand(time(NULL));
//直接用数据会越过程序初始栈大小,所在要在堆里申请空间~~~运行时可以断一下点,看一下任务管理器占用多少内存
Elem * p = new Elem[N];
long take = GetTickCount();
//time_t first, second;
//first = time(NULL); /* Gets system */
//方法一
//先将最大的k个交换到前面,再对其全排
DirectSearch( p, K );
QuickSort( p, 0, K - 1 );
PrintElem( p, K );
//方法二
//先用半快排,求出长度为k的区间后,再对其全排
// PartQuickSort( p, 0, N - 1, K );
// QuickSort( p, 0, K - 1 );
// PrintElem( p, K );
//方法三
//用一个小根堆保存这k个数, 遍历一次即可,每次与第一个比较, 如果更新了就调整
//HeapK( p, K );
//估计做出以上三种方法的实现,面试官都基本满意啦,一般软件公司的offer也是八九不离十了.
//但是,继续还有方法...
//解法四: 如果加上限制条件(1) 所有数为整数 (2) 所有数的变化范围不大 这样就可以利用记数排序法的思想
//Countsort( p, K )
//second = time(NULL); /* Gets system time again */
cout<<"tick="<< GetTickCount() - take <<endl;
cout<<"count="<< cnt <<endl;
//cout<<"tick="<< difftime(second,first)<<endl;
delete p;
}
下面者是实现~~~
view plaincopy to clipboardprint?
//解法一: 直接查找 O( N*k), 其实当 k < log2(N) 时是可以用的, 因为要完全排序需要 O( n* log2(n) )
//但是如果要求k个数排序的话,还要至少加上 k*log2(k)的时间
//直接将其交换到前面位置
void DirectSearch( Elem * src, int k )
{
long i =0, j, l,idx;
long max;
Elem t;
for( l=0; l<K; l++ )
{
max = src[l].weight;
for( j=l; j <N; j++)
{
if( max < src[j].weight )
{
max = src[j].weight;
idx = j;
}
//计数器
++cnt;
}
//交换到前面
memcpy( &t, &src[idx], sizeof(Elem) );
memcpy( &src[idx], &src[l], sizeof(Elem) );
memcpy( &src[l], &t, sizeof(Elem) );
}
}
//解法二: 部分快排
//前置声明
int PartPartion( Elem * src, int start, int end );
//部分快排,实现将前K个最大的放在前端(N个无素无序)
//完成后前k个最大数将会放在数组的前k的位置,无序
void PartQuickSort( Elem * src, int start , int end, int k )
{
//按照快排的思想,以第一个为标记元素M,大于M的放交换到左边,小于M放到右边, 当左分区的要少于N时结束
//返回上一次分区的大小
//---------------
//其实上面的想法,会导致分组过大的,如要在10个里找出前5个,当计算到m为3时,但前一个可能为8,
//这里就直接全排这个8个元素就会做很我多无用的排序了
//优化:
//(1) 当找到 m < N 时, 继续在大的分区找出 N - m个
//(2)递归进行,真到有m == N 时结束
//(3)算法复杂度为O(N* log2(k) )
//if( k == 0 )
//return;
int m = 0;
//m = PartPartion( src, cur , ElemLen - 1 );
//if( m <= k )
//PartQuickSort( src + m + 1, ElemLen - m -1 , k - m - 1, m + 1);
if( start < end )
{
m = PartPartion( src, start , end );
if( m <= k )
PartQuickSort( src, m+1 , end, k - m + 1 );
else
PartQuickSort( src, start , m - 1, k );
}
}
//部分快排的分解
int PartPartion( Elem * src, int start, int end )
{
Elem t, mid;
int i,j;
i = start;
j = end;
mid = src[start];
while( i< j )
{
while( i<j && src[j].weight <= mid.weight )
j--;
while( i < j && src[i].weight >= mid.weight )
i++;
if( i<j)
{
//如果Elem结构里有数组,就不能直接赋值,所以改用memcpy直接复制内存
memcpy( &t, &src[i], sizeof(Elem) );
memcpy( &src[i], &src[j], sizeof(Elem) );
memcpy( &src[j], &t, sizeof(Elem) );
}
}
memcpy( &t, &src[i], sizeof(Elem) );
memcpy( &src[i], &src[start], sizeof(Elem) );
memcpy( &src[start], &t, sizeof(Elem) );
++cnt;
return i;
}
//对前N个元素进行快排
void QuickSort( Elem * src, int start , int end )
{
//完全排序
if( start < end )
{
int m = PartPartion( src, start , end );
QuickSort( src, start, m -1 );
QuickSort( src, m +1, end );
}
}
//解法三: 将一个K个数据的堆,遍历一次即可,以最小堆形式,这样只要比较第一个元素F, 如果比F大,就替找F,之后调整堆
//同样算法复杂度为 O(N* log2(k) ), 而且实现简单
//前置声明
bool AdjHeap( int * WArray, int i, int len );
void MinHeap( int * WArray, int len );
void HeapSort( int * WArray, int len );
void HeapK( Elem * src, int k )
{
//所有元素以一个数组表示, 再按堆操作调整
//这里只保存weight值
int * weight = new int[ k ];
memset( weight, 0, k );
long i;
int idx = 0;
//先将前k 个元素的weight加入到堆中
for( i=0; i<k; i++ )
weight[i] = src[i].weight;
//调整为小根堆,方便比较
MinHeap( weight, k );
//遍历一次即可
for( i=k; i<N; i++ )
{
if( weight[0] < src[k].weight )
{
//每置换一次堆顶就要重新调整
weight[0] = src[k].weight;
AdjHeap( weight, 0, k - 1 );
}
}
//最后weight数组为前k个最大的元素,如果要排序输出,那么要做一次堆排序
HeapSort( weight, k );
for( i=0; i<K; i++ )
cout<< weight[i] << " ";
cout<<endl;
delete weight;
}
void HeapSort( int * WArray, int len )
{
int i;
int t;
for( i=0; i<len ; i++ )
{
t = WArray[0];
WArray[0] = WArray[len - i -1];
WArray[len - i - 1] = t;
//当没有调整时,完成排序
AdjHeap( WArray, 0, len - i - 2 );
}
}
bool AdjHeap( int * WArray, int i, int len )
{
bool change = false;
int j,t;
for( j =i*2+1; j <= len; j = j*2+1 )
{
if( j +1 <= len && WArray[j] > WArray[ j +1 ] )
j++;
if( WArray[j] < WArray[(j-1)/2] )
{
change = true;
t = WArray[(j-1)/2];
WArray[(j-1)/2] = WArray[j];
WArray[j] = t;
}
}
return change;
}
void MinHeap( int * WArray, int len )
{
if( len <= 0 )
return;
//根据堆的性质: 左孩子为 2i +1 , 右孩为 2i + 2( 因为从0开始计数 )
int i;
//从最后一个有孩子的结点开始,向前调整
len--;
for( i = (len-1)/2; i >=0; i-- )
AdjHeap( WArray, i, len );
}
//解法四: 如果加上限制条件(1) 所有数为整数 (2) 所有数的变化范围不大 这样就可以利用记数排序法的思想
//遍历一次N, 找出最大值MAX
//开一个数组 T[MAX];
//再遍历一次N, 对每个数计数 T[ A[i] ]++; ( 0<= i <= N )
//由于是求前K个最大的数,所以就可以从后面起取
void Countsort( Elem * src, int k )
{
long i,j;
int max;
int wt;
int * kArray = new int[k];
long take = GetTickCount();
//遍历一次N, 找出最大值MAX
max = src[0].weight;
for( i=0; i<N; i++)
{
wt = src[i].weight;
max < wt ? max = wt:max;
}
//开一个数组 T[MAX];
int * T = new int[ max + 1 ];
memset( T, 0, sizeof(int)*(max + 1) );
//再遍历一次N, 对每个数计数 T[ A[i] ]++; ( 0<= i <= N )
for( i=0; i<N; i++)
{
wt = src[i].weight;
T[ wt ]++;
}
//由于是求前K个最大的数,所以就可以从后面起取
int n = k;
int idx = 0;
for( i = max; i>0 ; i-- )
{
//跳过没有的数
if( !T[i] )
continue;
if( T[i] >= n )
{
//保存结果在另一个数组,以免影响计算时间
for( j =0; j<n; j++ )
{
kArray[idx++] = i;
//cout<<i<<" ";
}
break;
}
else
{
//输出这么多个计数
for( j =0; j<T[i]; j++ )
{
kArray[idx++] = i;
//cout<<i<<" ";
}
n -= T[i];
}
}
//输出结果
for( j =0; j<n; j++ )
cout<<kArray[i]<<" ";
cout<<endl;
cout<<"tick="<< GetTickCount() - take <<endl;
delete T;
delete kArray;
}
//输出
void PrintElem( Elem * src, int Len )
{
for( int i =0; i< Len; i++ )
cout<<src[i].weight<<" ";
cout<<endl;
}
//解法一: 直接查找 O( N*k), 其实当 k < log2(N) 时是可以用的, 因为要完全排序需要 O( n* log2(n) )
//但是如果要求k个数排序的话,还要至少加上 k*log2(k)的时间
//直接将其交换到前面位置
void DirectSearch( Elem * src, int k )
{
long i =0, j, l,idx;
long max;
Elem t;
for( l=0; l<K; l++ )
{
max = src[l].weight;
for( j=l; j <N; j++)
{
if( max < src[j].weight )
{
max = src[j].weight;
idx = j;
}
//计数器
++cnt;
}
//交换到前面
memcpy( &t, &src[idx], sizeof(Elem) );
memcpy( &src[idx], &src[l], sizeof(Elem) );
memcpy( &src[l], &t, sizeof(Elem) );
}
}
//解法二: 部分快排
//前置声明
int PartPartion( Elem * src, int start, int end );
//部分快排,实现将前K个最大的放在前端(N个无素无序)
//完成后前k个最大数将会放在数组的前k的位置,无序
void PartQuickSort( Elem * src, int start , int end, int k )
{
//按照快排的思想,以第一个为标记元素M,大于M的放交换到左边,小于M放到右边, 当左分区的要少于N时结束
//返回上一次分区的大小
//---------------
//其实上面的想法,会导致分组过大的,如要在10个里找出前5个,当计算到m为3时,但前一个可能为8,
//这里就直接全排这个8个元素就会做很我多无用的排序了
//优化:
//(1) 当找到 m < N 时, 继续在大的分区找出 N - m个
//(2)递归进行,真到有m == N 时结束
//(3)算法复杂度为O(N* log2(k) )
//if( k == 0 )
//return;
int m = 0;
//m = PartPartion( src, cur , ElemLen - 1 );
//if( m <= k )
//PartQuickSort( src + m + 1, ElemLen - m -1 , k - m - 1, m + 1);
if( start < end )
{
m = PartPartion( src, start , end );
if( m <= k )
PartQuickSort( src, m+1 , end, k - m + 1 );
else
PartQuickSort( src, start , m - 1, k );
}
}
//部分快排的分解
int PartPartion( Elem * src, int start, int end )
{
Elem t, mid;
int i,j;
i = start;
j = end;
mid = src[start];
while( i< j )
{
while( i<j && src[j].weight <= mid.weight )
j--;
while( i < j && src[i].weight >= mid.weight )
i++;
if( i<j)
{
//如果Elem结构里有数组,就不能直接赋值,所以改用memcpy直接复制内存
memcpy( &t, &src[i], sizeof(Elem) );
memcpy( &src[i], &src[j], sizeof(Elem) );
memcpy( &src[j], &t, sizeof(Elem) );
}
}
memcpy( &t, &src[i], sizeof(Elem) );
memcpy( &src[i], &src[start], sizeof(Elem) );
memcpy( &src[start], &t, sizeof(Elem) );
++cnt;
return i;
}
//对前N个元素进行快排
void QuickSort( Elem * src, int start , int end )
{
//完全排序
if( start < end )
{
int m = PartPartion( src, start , end );
QuickSort( src, start, m -1 );
QuickSort( src, m +1, end );
}
}
//解法三: 将一个K个数据的堆,遍历一次即可,以最小堆形式,这样只要比较第一个元素F, 如果比F大,就替找F,之后调整堆
//同样算法复杂度为 O(N* log2(k) ), 而且实现简单
//前置声明
bool AdjHeap( int * WArray, int i, int len );
void MinHeap( int * WArray, int len );
void HeapSort( int * WArray, int len );
void HeapK( Elem * src, int k )
{
//所有元素以一个数组表示, 再按堆操作调整
//这里只保存weight值
int * weight = new int[ k ];
memset( weight, 0, k );
long i;
int idx = 0;
//先将前k 个元素的weight加入到堆中
for( i=0; i<k; i++ )
weight[i] = src[i].weight;
//调整为小根堆,方便比较
MinHeap( weight, k );
//遍历一次即可
for( i=k; i<N; i++ )
{
if( weight[0] < src[k].weight )
{
//每置换一次堆顶就要重新调整
weight[0] = src[k].weight;
AdjHeap( weight, 0, k - 1 );
}
}
//最后weight数组为前k个最大的元素,如果要排序输出,那么要做一次堆排序
HeapSort( weight, k );
for( i=0; i<K; i++ )
cout<< weight[i] << " ";
cout<<endl;
delete weight;
}
void HeapSort( int * WArray, int len )
{
int i;
int t;
for( i=0; i<len ; i++ )
{
t = WArray[0];
WArray[0] = WArray[len - i -1];
WArray[len - i - 1] = t;
//当没有调整时,完成排序
AdjHeap( WArray, 0, len - i - 2 );
}
}
bool AdjHeap( int * WArray, int i, int len )
{
bool change = false;
int j,t;
for( j =i*2+1; j <= len; j = j*2+1 )
{
if( j +1 <= len && WArray[j] > WArray[ j +1 ] )
j++;
if( WArray[j] < WArray[(j-1)/2] )
{
change = true;
t = WArray[(j-1)/2];
WArray[(j-1)/2] = WArray[j];
WArray[j] = t;
}
}
return change;
}
void MinHeap( int * WArray, int len )
{
if( len <= 0 )
return;
//根据堆的性质: 左孩子为 2i +1 , 右孩为 2i + 2( 因为从0开始计数 )
int i;
//从最后一个有孩子的结点开始,向前调整
len--;
for( i = (len-1)/2; i >=0; i-- )
AdjHeap( WArray, i, len );
}
//解法四: 如果加上限制条件(1) 所有数为整数 (2) 所有数的变化范围不大 这样就可以利用记数排序法的思想
//遍历一次N, 找出最大值MAX
//开一个数组 T[MAX];
//再遍历一次N, 对每个数计数 T[ A[i] ]++; ( 0<= i <= N )
//由于是求前K个最大的数,所以就可以从后面起取
void Countsort( Elem * src, int k )
{
long i,j;
int max;
int wt;
int * kArray = new int[k];
long take = GetTickCount();
//遍历一次N, 找出最大值MAX
max = src[0].weight;
for( i=0; i<N; i++)
{
wt = src[i].weight;
max < wt ? max = wt:max;
}
//开一个数组 T[MAX];
int * T = new int[ max + 1 ];
memset( T, 0, sizeof(int)*(max + 1) );
//再遍历一次N, 对每个数计数 T[ A[i] ]++; ( 0<= i <= N )
for( i=0; i<N; i++)
{
wt = src[i].weight;
T[ wt ]++;
}
//由于是求前K个最大的数,所以就可以从后面起取
int n = k;
int idx = 0;
for( i = max; i>0 ; i-- )
{
//跳过没有的数
if( !T[i] )
continue;
if( T[i] >= n )
{
//保存结果在另一个数组,以免影响计算时间
for( j =0; j<n; j++ )
{
kArray[idx++] = i;
//cout<<i<<" ";
}
break;
}
else
{
//输出这么多个计数
for( j =0; j<T[i]; j++ )
{
kArray[idx++] = i;
//cout<<i<<" ";
}
n -= T[i];
}
}
//输出结果
for( j =0; j<n; j++ )
cout<<kArray[i]<<" ";
cout<<endl;
cout<<"tick="<< GetTickCount() - take <<endl;
delete T;
delete kArray;
}
//输出
void PrintElem( Elem * src, int Len )
{
for( int i =0; i< Len; i++ )
cout<<src[i].weight<<" ";
cout<<endl;
}
呵呵,相信在面试时能详细说出以上几钟算法的优缺点,并现场写出一两个,估计他会当场拍板:我找的就是你!
呼~~~当时就是衰在这个上啦, 所以决定完整的做一遍, 基础还是很重要的!!大家也加油啦~~~
本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/kikikind/archive/2009/06/22/4288411.aspx