递归算法(求n的加法组合,将一个整数拆分成多个整数相加的形式, O(N)时间,O(N)空间)4.0版

网上的多种解法比较复杂,本文用递归方法,22行代码搞定。时间和空间复杂度已经降到最低!

第三版:加入创作思路。

这个函数的主要功能就是输出所有组合。既然是输出所有的组合,那就意味着内部有一个遍历所有组合的过程。既然是遍历,而且是O(N)时间,那就说明这个遍历是按照某种输出次序,从“第一个组合”遍历到“最后一个组合”。

如何给组合定义次序呢?举例说明

例1:

3=1+1+1

3=1+2

3=3

上面的例子就说明了次序,即,按照组合中出现数字的从小到大顺序。

定义了次序,剩下的就是如何让程序按照这个程序一个一个的遍历。遍历的过程不会那么完美的一个不重复,当然也会重复,这就涉及到过滤。过滤那些重复的元素,举例说明

例2:

3=1+1+1

3=1+2

3=2+1

3=3

可以看出这个例2中的3=2+1被过滤掉了,并没有输出,这也是必须的,为什么呢?因为3=2+1和之前出现的3=1+2本质上就是一种组合,还要交换一下数字的位置就可以了。而加法自然有交换率。所以就不必输出了。从这里还可以看出来过滤的依据,那就是让一个组合中的所有数字也保持从小到大出现,这样就不会出现3=2+1了,因为2比1大,之前肯定出现过了。这样一来就解决了输出的唯一性

至此,就剩下如递归的进行了。递归的思路是这样的,例如拆分3:

既然

 3=1+后续组合

那么递归也就自然的变成了对“后续组合”进行继续拆分,只要“后续组合“的所有排列找到了,后续组合的每个排列前面加上1这个前缀自然就解决了1作为前缀的所有情况,这样一来就会遍历到

3=1+1+1

3=1+2

由于组合次序的定义可以知道,1作为前缀的情况被遍历完之后,自然就变成了遍历2开头的数字

2开头的数字还需要遍历吗?基于如下事实

n=m1+m2+...+mk, m1<m2<...<mk

第一个数字m1不会超过n/2,因为m1后面的数字要比m1大。所以可以看出,遍历的时候第一个数字最多尝试到n/2.

但是m1最大取n/2是合理的,比如

11=5+6

也就是说拆分的时候总是有拆分成两个数的和的形式,其中n=m1+m2,m1<m2,这样一来m1取n/2就是合适的。

那么下面就是递归程序的实现了。

f(int n,list l,int start)

参数说明:

n:这里n表示要对n进行拆分

l:这里表示子拆分的时候前缀的那些子递增序列,当n被初次拆分的时候,list当然是空的,只有子拆分才会有非空的前缀

start:表示在遍历的时候当前组合的第一个数字,这个数字用来去除重复,参考输出的唯一性



第二版:添加了输出函数和头文件(可直接运行)

附上说明:



//n give the sum of a list,and start give the first number of the list
#include <iostream>
#include <list>
using namespace std;

void print_list(list<int>::const_iterator iter_begin,list<int>::const_iterator iter_end)
{
		for (iter_begin;iter_begin!=iter_end;++iter_begin)
		{
			cout<<*iter_begin<<'+';//输出前缀,最后一个后缀单独输出
		}
}
/*
	这个函数会打印N的所有加法组合
	打印时将重复的组合去掉,只剩下N = m1+m2+m3的形式
	其中m1<=m2<=m3
	N:待打印的数
	list1:临时变量,用于存放动态的子序列
	start:m1开始打印(默认从m1=1开始打印,m1会自动增大到N完成全部打印),如果m1=N,则只打印N本身
*/
void f(int n,list<int>& list1,int start) 
{
	if (n==1)//当函数递归到n=1的时候,说明n已经被拆分成N=m1+m2+...+mk+n的时候了,
	{
		//输出前缀,当然前缀肯定也全部都是1
		print_list(list1.begin(),list1.end());
		//换行
		cout<<1<<endl;
		return;
	} 
	else
	{
		//1+2 和 2+1 认为是同一种情况 ,所以只输出n1+n2+n3+..., n1<=n2<=n3... 
		// 只输出后续组合从start开始的那些组合(1+2)( + 3+3=6)=9
		//(1)
		for (int i=start;i<=n/2;i++) 
		{
			//( + 3+3=6)
			list1.push_back(i); 
			//print 1+f(n-1)   print 2+f(n-2)...
			//每个函数打印它自己的所有子情况,
			//同时借助上层函数遗留的链表作为前缀,
			//有start决定从哪一个数字开始打印

			f(n-i,list1,i); 
			//the loop goto a new number at the end of the list
			//(1+2)                        =9 为进入下一个循环迭代做好准备
			list1.pop_back();   
		}
		//for循环之外的情况,即n自身的输出(当然也要先输出前缀)
		print_list(list1.begin(),list1.end());
		//输出自己,单独输出一行
		cout<<n<<endl;
	}
}
void get(int N)
{
	list<int> list1 ;
	f(N,list1,1);
}
int main( void ) 
{
	get(9);
	return 0;
}


 第一版: 没有输出函数,下面的代码不可运行,直接使用第二版的代码即可。

//n give the sum of a list,and start give the first number of the list
void f(int n,list<int>& list1,int start) 
{
	if (n==1)
	{
		//输出前缀,当然前缀肯定也全部都是1
		print_list(list1.begin(),list1.end());
		//换行
		cout<<1<<endl;
	} 
	else
	{
		//1+2 和 2+1 认为是同一种情况 ,所以只输出n1+n2+n3+..., n1<=n2<=n3... 
		// 只输出后续组合从start开始的那些组合(1+2)( + 3+3=6)=9
		for (int i=start;i<=n/2;i++) 
		{
			//( + 3+3=6)
			list1.push_back(i); 
			//print 1+f(n-1)   print 2+f(n-2)...
			//每个函数打印它自己的所有子情况,
			//同时借助上层函数遗留的链表作为前缀,
			//有start决定从哪一个数字开始打印
			
			f(n-i,list1,i); 
			//the loop goto a new number at the end of the list
			//(1+2)                        =9 为进入下一个循环迭代做好准备
			list1.pop_back();   
		}
		//for循环之外的情况,即n自身的输出(当然也要先输出前缀)
		print_list(list1.begin(),list1.end());
		//输出自己,单独输出一行
		cout<<n<<endl;
	}
}
int main( void ) 
{
	list<int> list1 ;
	f(9,list1,1);
	return 0;
}


 

 输出:f(9,null,1)

  • 6
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C++程序员Carea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值