利用递归和动态规划来求解组合数

组合数定义:从m个不同元素中,任取n(n≤m)个元素并成一组,叫做从m个不同元素中取出n个元素的一个组合;从m个不同元素中取出n(n≤m)个元素的所有组合的个数,叫做从m个不同元素中取出n个元素的组合数。

下面是一种比较通俗的计算公式:

其递归公式为:

c(n,m)=c(n-1,m-1)+c(n-1,m)

下面是c++实现该递归算法:

#include <iostream>
#include <stdlib.h>
#define EXIT -1
using namespace std;
/************************************************************************/
/* m	组合数下标
   n    组合数上标
/************************************************************************/
int combine(int m,int n){
	if(m<n){
		cout<<"输入组合数无效"<<endl;
		exit(EXIT);
	}
	if(n==0||m==n){
		return 1;
	}else{
		return combine(m-1,n-1)+combine(m-1,n);
	}
	return EXIT;
}

int main(void){

	cout<<"请输入你要求解的组合数的上标和下标m为上标,n为下标"<<endl;
	int m,n;
	cout<<"m = ";
	cin>>m;
	cout<<"n = ";
	cin>>n;
	cout<<"您要求解的组合数的值为:"<<combine(m,n)<<endl;
	system("pause");
	return 1;
}

java版:

package cn.demo;

import java.util.Scanner;

public class Combine {
	static int combine(int m,int n){
		if(m<n){
			System.out.println("输入组合数无效");
		}
		if(n==0||m==n){
			return 1;
		}else{
			return combine(m-1, n-1)+combine(m-1, n);
		}
	}
	public static void main(String[] args) {
		@SuppressWarnings("resource")
		Scanner in = new Scanner(System.in);
		System.out.println("请输入你要求解的组合数,m为下标,n为上标");
		int m,n;
		System.out.print("m=");
		m = in.nextInt();
		System.out.print("n=");
		n = in.nextInt();
		int result = combine(m, n);
		System.out.println("您要求解的组合数结果为:"+result);
	}
}

但是在反复递归调用的时候,我们发现重复计算了一些值,可能这么看是看不出什么,下面用一张图来说明其调用过程:

我只画了部分,如果该组合数足够大的话,那么在递归调用的过程中则会有很多重复计算,效率是十分低的,因此在求类似问题的时候,我们都会去实用动态规划来求解.那什么是动态规划呢?

动态规划算法,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式。

因此,在实用动态规划的算法中,这个临时线性表是非常重要的,该表是一个二维数组;

下面是c++版:

#include <iostream>
#include <stdlib.h>
using namespace std;
int combine(int m,int n){
	if(m<n){
		cout<<"组合数无效"<<endl;
	}
	if(n==0||n==m){
		return 1;
	}
	int **temporary = new int *[m];//申请m个一维数组,或动态的二维数组,之所以不直接实用二维数组,因为避免下标问题
	for (int i=0;i<m;i++)
	{
		temporary[i] = new int[i+2];//二维数组的每一行长度都自动加2
		temporary[i][0] = 1;//二维数组的每一行的首尾值都是1
		temporary[i][i+1] = 1;//二维数组的每一行的末尾值也为1
		int j;          //二维数组的列
		for (j=1;j<=i;j++)///因为上述的每行的第一列都设置为1,因此从第2列开始
		{
			temporary[i][j] = temporary[i-1][j-1]+temporary[i-1][j];//计算第(i+1)行第(j+1)列的值
		}
		//打印出二维数组
		for (j=0;j<=i+1;j++)
		{
			cout<<temporary[i][j]<<"  ";
		}
		cout<<"\n";
	}
	return temporary[m-1][n];
}
int main(void){
	cout<<"请输入你要求解的组合数:m为下标,n为下标"<<endl;
	int m,n;
	cout<<"m = ";
	cin>>m;
	cout<<"n = ";
	cin>>n;
	int result = combine(m,n);
	cout<<"你要求的组合数的值为:"<<result<<endl;
	system("pause");
	return 1;
}

java版:

package cn.demo;

import java.util.Scanner;

public class Combine {
	static int combine(int m,int n){
		if(m<n){
			System.out.println("输入组合数无效");
		}
		if(n==0||m==n){
			return 1;
		}
		int temp[][] = new int [m][];//申请而为数组
		for (int i = 0; i < temp.length; i++) {
			temp[i] = new int[i+2];//动态增加数组长度
			temp[i][0] = 1;//每行首尾为1
			temp[i][i+1] = 1;//每行末尾为1
			int j;
			for (j = 1; j <= i; j++) {
				temp[i][j] =temp[i-1][j-1]+temp[i-1][j];
			}
			for(j=0;j<=i+1;j++){
				System.out.print(temp[i][j]+" ");
			}
			System.out.println();
		}
		return temp[m-1][n];
	}
	public static void main(String[] args) {
		@SuppressWarnings("resource")
		Scanner in = new Scanner(System.in);
		System.out.println("请输入你要求解的组合数,m为下标,n为上标");
		int m,n;
		System.out.print("m=");
		m = in.nextInt();
		System.out.print("n=");
		n = in.nextInt();
		int result = combine(m, n);
		System.out.println("您要求解的组合数结果为:"+result);
	}
}

从上面的程序可以看出,使用动态规划的时候,我们必须对所求问题的子结构了解的很透彻,在分解组合数的时候,它的两个子结构之间并不像分治算法那样相互独立,之间基本上是相互包含的,因此,如果使用递归,则其子结构在调用的时候,另一个子结构则会重复该子结构中已经存在的过程,如果m和n足够大的时候,重复率我们无法接受,因此这个使用使用动态规划则能很好的解决问题,尽管在算法的实现上有些麻烦,但是在很大程度上减少空间和时间复杂度,这一点,还是很让人值得尝试.

使用动态规划的核心是我们必须有一个容器来保存计算过程中产生的结果,而且这个容器是随着问题域大小二动态变化的.有时候会是一个栈,有时候是一个线性表,如果问题更复杂,则会是一个动态树或图.

利用递归和动态规划来求解组合数

时间: 2024-10-11 14:01:01

利用递归和动态规划来求解组合数的相关文章

动态规划算法求解0,1背包问题

首先我们来看看动态规划的四个步骤: 1. 找出最优解的性质,并且刻画其结构特性: 2. 递归的定义最优解: 3. 以自底向上的方式刻画最优值: 4. 根据计算最优值时候得到的信息,构造最优解 其中改进的动态规划算法:备忘录法,是以自顶向下的方式刻画最优值,对于动态规划方法和备忘录方法,两者的使用情况如下: 一般来讲,当一个问题的所有子问题都至少要解一次时,使用动态规划算法比使用备忘录方法好.此时,动态规划算法没有任何多余的计算.同时,对于许多问题,常常可以利用其规则的表格存取方式,减少动态规划算

70. Climbing Stairs【leetcode】递归,动态规划,java,算法

You are climbing a stair case. It takes n steps to reach to the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? Note: Given n will be a positive integer. 题目分析:每次只能走1或2步,问n步的话有多少中走法???? 可以用动态规划和递归解

斐波那契数列的实现(简单递归和动态规划)

斐波那契数列的实现(简单递归和动态规划) 一.简单递归的实现 1 #include "stdafx.h" 2 #include <string> 3 using namespace std; 4 int f(int n) 5 { 6 if (n == 0) 7 { 8 return 0; 9 } 10 if (n == 1) 11 { 12 return 1; 13 } 14 return f(n - 1) + f(n - 2); 15 } 16 int _tmain(in

关于递归和动态规划的简单理解

1.递归的定义 简单的来说,递归就是一个概念能够用自身来解释,比如说一本字典,每个字词的解释是依靠字典中其他的字词来解释的.一般来说,计算机中遇到的递归问题大多是把一个问题分解成规模更小的子问题求解,再进行合并. 递归的性质 一个具有递归性质的问题,大多具有两个特征,第一个是状态转移方程也就是递归方程,比如在求解阶乘时,n!=n*(n-1)!,就将求解n的阶乘转换为求解n-1的阶乘.第二个特征就是终止条件,一个递归是一类问题的求解,必定有一个结果 无法一只递归下去,有一个结束条件,也就是当问题规

【动态规划专题】1:斐波拉契数列问题的递归和动态规划

<程序员代码面试指南--IT名企算法与数据结构题目最优解> 左程云 著 斐波拉契数列问题的递归和动态规划 [题目]:给定整数N,返回斐波拉契数列的第N项.补充问题1:给定整数N,代表台阶数,一次可以跨2个或者1个台阶,返回有多少种走法.补充问题2:假设农场中成熟的母牛每年只会生产1头小母牛,并且永远不会死.第一年农场只有1只成熟的母牛,从第2年开始,母牛开始生产小母牛.每只小母牛3年后成熟又可以生产小母牛.给定整数N,求出N年后牛的数量. [举例]斐波拉契数列f(0)=0, f(1)=1,f(

利用递归 实现UIScrollView无限滚动的效果

项目需求 利用递归 实现UIScrollView无限滚动的效果. 上机试题, #import "ViewController.h" @interface ViewController (){ UIScrollView *mainScroll; BOOL isFinish; int x; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; x=0; isFinish = YES;

DevExpress TreeList利用递归绑定数据

private void TreeListBind(DataTable dt, int p) { treeList1.Nodes.Clear(); if (dt.Rows.Count < 1) return; DataView dv = new DataView(dt); dv.RowFilter = "ParentFieldName=" + p; if (dv.Count < 1) return; TreeListNode Node = treeList1.AppendN

利用递归求两个数字的最大公约数。

<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title></title> <script type="text/javascript"> //利用递归求两个数字的最大公约数. //公因数,如果一个整数同时是几个整数的约数,则这个整数为它们的"公约数" function getNum(a, b) { va

利用递归统一化函数参数的不固定数据类型

为了用户调用函数时更方便和灵活,所以我们定义的参数需要不固定数据类型,比如像这样: mylibs.on('event',fn); mylibs.on({    'event1':fn1,    'event2':fn2,    'event3':fn3}); mylibs.on('event1 event2 event3',fn); 这是一个自定义事件的例子,有三种传参的方式:1.事件名+回调   2.传递一个对象包含事件名和回调    3.多个事件名+一个回调 你觉得这个函数怎么写才好呢,也许