编程之美 读书笔记(寻找最大的k个数)

实际的情况我觉得往往要考虑到数的范围,数的特点(整数还是浮点,重复多不多)以及K与数组长度N的比例关系,数组长度N是否超出内存大小等等因素。
问题:有很多无序的数,姑且假定它们各不相等,怎么选出其中最大的若干个数呢?
实际可能的应用场景
1从1亿个qq号码中选出最值钱的5000个
2从2亿张图片中选出点击率最高的100张
算法一:
直接选择排序、冒泡排序
时间复杂度: o(N^2)
适用条件:N不大,批量请求
优点:简单直观,容易实现。
缺点:对于N很大的情况,难以接受
适用情况:N不是很大,k和N数量级接近,后续多次选择最大的K个数,有一批请求,这时可以对N个数按大小顺序进行保存。
算法二:
快速排序、堆排序
时间复杂度:O(NlogN)
适用条件:N,K较大,批量请求
优点:N很大时最好全排序,在同等级时间复杂度中平均性能最好的全排序
缺点:在记录有序时,快排o(N^2)
适用情况:logN<k时,可以代替部分冒泡排序,又能达到全排序的效果
算法三:
部分冒泡,部分选择
时间复杂度:O(Nk)
适用条件;k较小(k<logN),单个请求
优点:简单直观,容易实现
缺点:对于K较大的情况下,接近o(N^2)
适用情况:K较小,比如top10等,实际实现中可能是最常用,最佳选择,单个请求,后续不必对N个数按大小顺序进行排序。
算法四:
类快速排序
平均复杂度:O(NlogK)
优点:比快速排序有较大的提升
缺点:实现较难,最坏情况下o(NK)
算法:假设N个数存储在s中,随机找出一个元素x,把数组分成两部分Sa和Sb,Sa中元素大于等于X,Sb中元素小于X
分两种情况:1 Sa中元素个数小于K,Sa中所有数和Sb中最大的K-a中的元素个数
2 Sa中元素个数大于或等于K,需要返回Sa中最大的k个元素

算法五:
二分搜索法
复杂度:o(Nlog(vmax-vmin)/delta)//时间复杂度跟数据分布有关
优点:实现简单,实现性能不错
缺点:任意两个元素的差值如果很小,性能较差
适用情况:N很大,追求高性能
如果全部数据不能载入内存,可以每次都遍历一遍文件,经过统计,更新解所在区间后,在遍历一次文件,把在新的区间中的元素存入新的文件,在所有元素能够全部载入内存以后,就可以不再通过读写文件的方式来操作了。
算法:寻找K个数中最小的那一个
两种算法:1.判断在数组中大于等于某一个数的个数是否K,二分搜索,确定这个数
2 整数的情况下,用bit位表示,二分搜索,找寻这个数

//二分搜索法
int coutnum(int *list,int length,int mid)
{
	int num=0;
	for(int j=0;j<length;j++)
		if(list[j]>=mid)
			num++;
		return num;

}
void apart(int *list,int k,int length)
{
	int vmax=100;
	int vmin=0;
	int vmid;
	while((vmax-vmin)>1)
	{
	vmid=vmin+(vmax-vmin)*0.5;
	if(coutnum(list,length,vmid)>=k)
		vmin=vmid;
	else
		vmax=vmid;

	}
	vmid=vmin+(vmax-vmin)*0.5;


	int cou=0;
	for(int j=0;j<length;j++)
	{
		if(list[j]>vmid)
		{
			cout<<list[j]<<'\n';
			cou++;
			if(cou==k)
				break;
		}
		

	}
	if(cou<k)
	{
		while(cou<k)
		{
            cout<<vmid<<'\n';
			cou++;
		}
	}
	

	
}



算法六:
改进堆排序
复杂度:o(NlogK)
优点:效率高,所需空间小,可逐个元素读入。
缺点:无明显缺点
适用情况:N很大,追求高性能,需要逐个读入元素
算法描述;
用容量为k的最小堆来存储最大的k个数,最小堆的堆顶元素就是最大的k个数中最小的一个,每次新考虑一个数x,如果x比堆顶元素小,则不改变,如果比堆顶元素大,那么用x替换堆顶元素y,替换后从新排列最小堆

//改进堆排序
sift(int *list,int low,int k)
{
	int p=low;
	int q;
	while(p<k)
	{
		q=2*p+1;
		if(q>=k)
			break;
		if((q<k-1)&&(list[q+1]<list[q]))
			q++;
		if(list[q]<list[p])
		{
			int t=list[q];
			list[q]=list[p];
			list[p]=t;
			p=q;
		}
		else 
			break;
		
	}
}
void duitopk(int *list,int k,int length)
{
	int i;
	for(i=k/2;i>0;i--)
        sift(list,i,k);
	for(int j=k;j<length;j++)
	{
		if(list[j]>list[0])
		{
			list[0]=list[j];
			sift(list,0,k);
		}
	}
   for(int g=0;g<k;g++)
	cout<<list[g]<<'\n';
}



算法七:桶排序
复杂度:O(N)
优点:最快方法
缺点:有局限性,可能要通过转换
适用情况:元素的值在给定的范围内有限个,或可分区间
具体实现方法:
算法描述:
如果N个都是整数,取值范围不太大,可以考虑申请空间,记录每个整数出现的次数,然后在从大到小取出最大的k个。例如count[i]表示整数i在所有整数中出现的个数,只需扫描一遍就可以得到count数组,然后,寻找第k大的元素

//桶排序
void topK(int *list,int k,int length)
{
	int count[70]={0};
	for(int i=0;i<length;i++)
	{
		int temp=list[i];
		count[temp]++;
	}
	int c=0;
	for(int j=69;j>=0;j--)
	{
		if(count[j]!=0)
		{
			for(int g=count[j];g>0;g--)
			{
			cout<<j<<'\n';
			c++;
			 if(c ==k)
			 	break;
			}
       
		}
		if(c ==k)
		break;	
	}	
}


 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值