您的位置:首页 > 职场人生

【July程序员编程艺术】之最小的k个数问题

2015-10-12 11:36 375 查看
题目描述:查找最小的k个元素

题目:输入n个整数,输出其中最小的k个。

例如输入1,2,3,4,5,6,7和8这8个数字,则最小的4个数字为1,2,3和4。

我的解题思路:

1.最容易想到的思路

对于本题,最容易想到的思路就是先进行一次排序(如快速排序),排完序之后,直接取前k个数即可。时间复杂度为O(n*logn+k)=O(n*logn)。接下来就是要思考可以优化的地方。使用排序算法是把所有n个数都进行了排序,但是实际上有n-k个数是不需要进行排序的,而且k个最小的数之间的排序也是不需要的。这些就是可以优化的地方。对源问题换一种表述方式就转换为这样的问题,在n个数中寻找k个数,使得k个数中的最大值小于其余n-k数的任一个。这样就容易想到思路2.

2.先取最先的k个数,假设这k个数就是我们要找的k个数。(这也是一种思维方式,我们要找k个最小的数,但是没有太好的办法知道k个数中第一个数是哪个,第二个数是哪个,那只能摸着石头过河,就假定最先遍历到的k个数为最小的k个数,然后再进行后续的弥补)然后遍历其余的n-k个数,对于遍历得到的每个数,都要比较其和k个数中最大数的关系,然后再进行相应的操作。如果仅仅使用数组的数据结构,那么每次都要从k个数中遍历寻找最大的数,这也是可以优化的地方,然后就可以想到利用最大堆的数据结构。

3.思路2的改进–利用最大堆的数据结构

最大堆数据结构的实现程序:

#include <iostream>
using namespace std;
template <class T>
void max_heap(T * src,int idx,int num);
template <class T>
void swap(T * a,T * b );
/*对大小为num数组src,节点idx的左右子树均满足最大堆的性质,现在将整个树调整为最大堆*/
/*就地操作*/
template <class T>
void max_heap(T * src,int idx,int num)
{
int left  = 2*(idx+1)-1;
int right = 2*(idx+1)-1+1;
int tmp_max=src[idx];
int tmp_idx=idx;
if((left<num)&&(src[left]>src[idx]))
{
tmp_max = src[left];
tmp_idx = left;
}
if((right<num)&&(src[right]>tmp_max))
{
tmp_max = src[right];
tmp_idx = right;
}
if(tmp_idx!=idx)
{
swap(src+tmp_idx,src+idx);
max_heap(src,tmp_idx,num);
}

}

template <class T>
void swap(T * a,T * b )
{
T tmp;
tmp=*a;
*a=*b;
*b=tmp;
}

void main()
{
int src[]={4,1,3,2,16,9,10,14,8,7};
int num = sizeof(src)/sizeof(int);
int st_idx =num/2 -1;
for(int i=st_idx;i>=0;i--)
max_heap(src,i,num);
for(int i=0;i<num;i++)
{
cout << src[i] << endl;
}

}


利用最大堆的数据结构,先取前k个数,建立最大堆,耗时为O(k)。接下来遍历后n-k个数,每个数都与前k个数的最大值进行比较。因为最大堆的插入删除等操作的复杂度均为O(log k),比数组要快,因此最后整个程序的时间复杂度为O(k+(n-k)*logk)=O(n*logk)。

注:对最大堆数据结构的思考:为什么采用最大堆的数据结构可以优化时间效率?需要付出何种代价?对我们有何启示?

最大堆之所以提高时间效率,从根本上说还是减少了重复遍历数组的次数,第一次需要完整的遍历整个数组,然后建立最大堆。之后的遍历就可以利用之前最大堆的性质,从而不必遍历数组的所有元素,因而可以改进时间效率。付出的代价是需要维护最大堆的数据结构,数组元素的添加和删除都要进行额外的操作。对我们的启示是:为了降低时间复杂度,应当根据题目要求建立合适的数据结构以充分上一次完整遍历的结果,减少总共的遍历次数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: