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

来源:互联网 发布:简写英文软件 编辑:程序博客网 时间:2024/05/17 04:42

网上的多种解法比较复杂,本文用递归方法,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<=m3N:待打印的数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的时候了,{//输出前缀,当然前缀肯定也全部都是1print_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 listvoid f(int n,list<int>& list1,int start) {if (n==1){//输出前缀,当然前缀肯定也全部都是1print_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)=9for (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)

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 水倒在笔记本上怎么办 32岁了还没结婚怎么办 15天的宝宝上火怎么办 剖腹产7天刀疤痒怎么办 敷完面膜脸发烫怎么办 敷完面膜脸发红怎么办 总打嗝胃里有气怎么办 胃里有气怎么办想吐 肚子受凉了很疼怎么办 车丢了没有定位怎么办 牙齿下面肉肿了怎么办 伤口快愈合很痒怎么办 微信转账满十万怎么办 吃星球杯没勺子怎么办 绿箭口香糖吞了怎么办 吞了一个梅子核怎么办 槟榔渣咽下去了怎么办 槟榔不小心吃了怎么办 牙齿掉到肚子里怎么办 牙掉在了肚子里怎么办 牙掉了咽肚子里怎么办 假牙咽到肚子里怎么办 牙咽肚子里了怎么办 假牙吃肚子里了怎么办 陶瓷牙咽肚子里怎么办 做飞机耳朵疼难忍怎么办 肚子痛又拉不出来怎么办 胃胀气打嗝想吐怎么办 胃胀吐酸水恶心怎么办 一岁宝宝胃胀气怎么办 3岁宝宝腹胀呕吐怎么办 胃胀气想吐怎么办快速 胃胀然后吐了怎么办 1岁宝宝胃胀气怎么办 孩子胃胀气还吐怎么办 肚子里进了凉气怎么办 肠胃涨气肚子变大怎么办 感觉肚子胀胀的怎么办 肚子胀撑的难受怎么办 肚子着凉了很疼怎么办 来月经肚子疼怎么办最快的方法