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

       问题描述:给定一个具有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
    评论
编程珠玑》是由美国计算机科学家Jon Bentley编写的一本经典书籍,第二版修订版pdf是该书的一种电子书格式。该书是一本关于编程和解决方法的指南,涵盖了算法、数据结构、程序设计等方面的知识。 第二版修订版pdf相比于第一版,进行了一些修订和更新。更新内容主要包括了一些新的编程和相应的解决方法,以及对一些已有问的更详细的说明和讨论。此外,修订版还可能加入一些新的案例分析和实践经验,以帮助读者更好地理解和应用书中的内容。 作为一本经典的编程书籍,《编程珠玑》在计算机科学和编程领域具有很高的声誉和影响力。读者通过学习该书,可以提高自己解决编程的能力和思维方式,深入理解算法和数据结构的原理和应用,提升编程技巧和代码质量。 对于想要阅读《编程珠玑》的读者,选择第二版修订版pdf是一种方便和经济的方式。pdf格式可以在电子设备上阅读,无需携带纸质书籍,随时随地都可以学习。而且,修订版相对于第一版,内容更加全面和详细,更适合新手和有一定编程基础的读者阅读和学习。 综上所述,《编程珠玑》第二版修订版pdf是一本经典的编程书籍的电子格式,通过学习该书,读者可以提高自己的编程能力和思维方式,了解算法和数据结构的原理和应用。阅读第二版修订版pdf是一种便捷和经济的方式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值