问题 B: 【递归入门】组合的输出

问题概述

排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r < = n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。
现要求你不用递归的方法输出所有组合。
例如n = 5 ,r = 3 ,所有组合为:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5

输入

一行两个自然数n、r ( 1 < n < 21,1 < = r < = n )。
输出

所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,所有的组合也按字典顺序。

思路分析

本题博主采用递归方法解决,假设我们要在[1,6]中选取3个数,对于第一个数,我将它的选取范围用在下图中用left和right指针标出,显然,第一个数只能在[left,right]这个范围内选取:
在这里插入图片描述对于下一个数,它的选取范围又如何求得呢?稍加思索我们很快可以知道,下一个数选取范围的左端点,与其上一个已选取的数有关,假设上一个已选取的数为i,那么这次的选取范围便是[i+1,right+1],其中right就是上一个数选取范围的右端点。
在这里插入图片描述
我们已经讨论完了选取范围的确定方法,现在再考虑如何枚举数的可能取值。我们需要从6个数中选择3个数,对于选择的每一个数都有其选取范围[left,right],我们规定left指针指向当前选取的数值,然后令left指针不断右移靠近right指针,当left指针越过right指针时,便标志着本轮选取结束。

在这里插入图片描述以上就是本题递归算法的主体内容,我们再来具体描述一下算法的执行过程:
初始left指针为1,right指针为4(=6+1-3),进入递归入口。
递归的过程:
若left<=right,执行循环体:
(1)判断right指针是否等于数组右边界6,若为6,输出当前序列(共3个数,为一个组合),继续执行(3),否则,转入(2);
(2)将left指针指向的数加入选取序列,确定下一个数的选取范围[left+1,right+1],进入下一轮递归;
(3)将left指针右移(即执行left++),继续循环。
循环结束返回。

参考代码

在上一个部分,我们已经分析了解题思路并给出了算法执行过程,需要说明的是博主在本题代码中使用动态数组vector存储组合序列,且right以n-r的形式给出。

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
using namespace std;

vector<int> res;

void choose(int L,int n,int &r){
	while(L<=n-r){
		res.push_back(L);
		if((r--)==0){
			for(int i=0;i<res.size();i++)
				printf("%d ",res[i]);
			printf("\n");
		}
		else choose(L+1,n,r);
		res.pop_back();
		r++;
		L++;
	}
	return;
}
int main(){
	int n,r;
	while(scanf("%d%d",&n,&r)!=EOF){
		int x = r-1;
		choose(1,n,x);
	}
	return 0;
}

思考总结

本道题博主的参考代码虽然篇幅不长,但是着实花了很多精力,最后能够成功做出来还是很雀跃的,于是决定写篇博客记录一下。
本道题的难点在于对递归思路的把握,博主能够有所突破还是从画图辅助理解开始的,当分析到枚举当前数的可能值就是一个不断把left指针靠近right指针的过程时,我的思路就一下子全打开了。其实以前数据结构的课上,老师就教导过我们解数据结构题一定要善于画图,画图不仅能够帮助我们理解数据之间的结构与关系,还能使题目的逻辑链条变得明晰。很多问题乍看一头雾水,但拆解成各个小部分后就逐渐清晰起来,从小到大,从部分到整体,从特殊到一般。思维的疆域是无垠的,思考的魅力是不言自明的。
通过这道题,我感觉自己对于递归算法的理解又深入了一点,什么时候可以直接输出,什么时候有又需要将结果存储起来最后输出,考虑到递归是一个自底向上的算法流程这一特性,要根据题目的具体要求来确定。与之相对的递推算法,则是一个自顶向下的算法流程,是一个将大问题不断拆分为小问题然后合并出原问题的解的一个过程,以博主目前的能力还无法对这两个有趣的概念进行深入探讨,但博主会继续努力学习、练习,愿某一日我们在更高处相逢,能接着今天的话题再多聊几句。

后话

写完了再看题目才发现要求不用递归,哈哈哈哈尴尬了(甚至kind of泫然欲泣),容博主再改改代码,写个续篇发出来吧:P

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值