编程珠玑第二章第八题的解答

       问题描述:给定一个具有n个元素的实数集,一个实数t,一个整数k,如何快速的确定该实数集是否存在一个k个元素的子集,
其中各个元素的的总和之多为t

      拿到这个题目,我首先想到的是快排,但是使用快排算法对n个元素进行排序,时间复杂度为nlog(n)。如果你使用的是堆排序的话,
那么效率又提高了,因为在这个题目中,我们需要的只是前k个最小元素,所以使用堆排序更合理,时间复杂度为nlog(k),但是作者在书中给出的提示
是时间复杂度需要达到o(n)级别,所以上述两种方法全部失效了。

     其实这是一个典型的topk问题,就是在一个集合中,找出前k个最小或者最大的数。问题的关键在于找出第k小个数。这里我参考别人的思想:


     本算法跟快排的思想相似,首先在数组中选取一个数centre作为枢纽,将比centre小的数,放到centre的前面将比centre大的数,放到centre的后面。如果此时centre的位置刚好为k,则centre为第k个最小的数;如果此时centre的位置比k前,则第k个最小数一定在centre后面,递归地在其右边寻找;如果此时centre的位置比k后,则第k个最小数一定在centre后面,递归地在其左边寻找。


      注意:centre的位置=其下标值+1,因为数组中的第一个元素的下标为0。

     从上面的描述中,我们可以看到这个算法运用了减治的方法求解。减治的思想与分治非常相似,同样是在一次操作中,削减问题的规模,只是分治把每个子问题求解后,要合并每个子问题的解才能得到问题,而减治的方法,却不用合并子问题的解,子问题的解,直接就是原问题的解。举个例子来说,就像快排和二分查找算法,前者是分治,后者是减治。因为快排要等到所有的子数组都排完序,原数组才有序,而二分查找却不用,它每执行一次查找,直接丢弃一半的数组,而不用合并子问题的解。不过也有不少书,把他们都归为分治法。

        代码如下:

//问题描述:给定一个具有n个元素的实数集,一个实数t,一个整数k,如何快速的确定该实数集是否存在一个k个元素的子集,其中各个元素的的总和之多为t
//	程序的时间复杂度为o(n)。
//	首先需要在数组中找出第k个小的数字。
#include<iostream>
#include <algorithm>
using namespace std;
int find_kth_min(int *array, int left,int right,const int k,const int size )
{
	if (left >= right || left < 0 || k < 1 || k > right + 1){                               //注意考虑极端情况。
		cerr << "接受的参数有误" << endl;
		exit(1);
	}
	int center = array[right];
	int i = left;
	int j = right - 1;
	while(true){                                     //经过这一步,所有的比center大的都在center的右边,所有的比center小的都在center的左边
		while(array[i] <= center && i < size-1)      //不能让数组越界!否则就等着悲剧把
			++ i;
		while(array[j] >= center && j >= 1)          //不能让数组越界!否则就等着悲剧吧
			-- j;
		if (i < j)
				swap(array[i],array[j]);
		else break;
	}
	swap(array[i],array[right]);                    //以arrar[right]为分界线,左边全是比他小的,右边全是比他大的
	if (i+1 == k){
		return array[i];
	}
	else if (i+1 < k){

		find_kth_min(array,i+1,right,k,size);
	}
	else {

		find_kth_min(array,left,i-1,k,size);
	}
}
int main()
{                                                         
	const int k = 6;                                     //返回第六小的数,注意从1开始计数。
	int a[] = {2,4,6,7,5,11,3,5,6,32,5,2,4};       
	const int size = sizeof(a)/sizeof(int);               //这种方式计算数组的长度,应该鼓励使用。
	int k_th_min;
	k_th_min = find_kth_min(a,0,size-1,k, size);          //返回第k小的数
	//下面计算前k个小的数之和是不是小于t。
	int sum = 0;                                          //sum是计算前k个小的数字的总和
	int number = 0;
	for(int i = 0;i < size; ++i){
		if (a[i] < k_th_min){
			sum += a[i];
		    number ++;
		}
	}
	number ++;
	if (number < k){                                      //这里要特别注意了,表明第K个最小的数,不止一个,那么就需要修正上面计算的sum
		sum += (k-number)*k_th_min;
		number += k-number;
	}
	sum += k_th_min;
	cout << number << " " << sum;                        //得到最终的前k个最小的数的总和.
	return 0;
}
     这个算法的时间复杂度,平均来说是o(n),解释来自 王晓东的计算机算法分析与设计中的第二章第9节,即2.9中有这个算法的描述和介绍。里面说它的时间复杂度为O(N),好像计算这个时间复杂度需要用到微分,我没看懂,结论就是这个算法的平均时间复杂度是o(n).惭愧,需要学习的东西很多。如果有问题,欢迎指正,谢谢,我的QQ1527927373.

     另外程序参考了ljianhui的专栏。但是它的程序中出现了一些小错误,已经被我改正。这个算法的好处是在求topk时,平均的时间复杂度是o(N)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值