递归与递推 普通排队问题及带约束条件的排队问题 c代码

先看下题目:

电影院买票排队,票价50,排队的人中携带50元的有20个人,携带100的有10个人,售票处开始时没有余额,
问最多有多少种排队方式使得售票处能够找的开(携带相同数额的人交换位置算一种排队方式)。

画图的话比较容易理解,二叉图,根节点为50,然后往下排列,从根节点到叶子节点即为一个排队方式。设f(m,n),其中m为50的个数,n为100的个数,f(m,n)表示排队方式,当m>=20或者n>=10时,排队结束,这是再插入节点只有一种方式,当m>n时,有两个子节点,分别时50和100,当m==n时,只有一个节点,即50节点。这样能够得到递归公式。

int f(int m50, int n100, int m, int n)
{
	if (m50 == m || n == n100)
		return 1;
	else if (m50 > n100)
		return f(m50+1, n100, m, n) + f(m50, n100+1, m, n);
	else if (m50 == n100)	
		return f(m50+1, n100, m, n);
	else
		printf("逻辑错误!");
	return 0;	
}

二叉图:

 

通过递归公式也很容易得到递推的方法,f[m][n] = f[m-1][n] + f[m][n-1],这里m>=n,边界条件f[m][0] = 1, f[m][n] = 0(m< n)。

这里最好使用递推来解决,因为递推使用两层循环,时间复杂度为O(n^2),使用递归的话时间复杂度为O(3^n),差距太大。

这道题还有一种扩展,扩展题目增加了约束条件,"第五位必须是50,第八位必须是100",如果还是使用二叉图来看可能不是那么容易分析,我们可以使用下面的图来分析:

横坐标为50,纵坐标为100,坐标图上的点f[m][n]表示排队方式,约束条件是在f(4,1)和f(3,2)这个点,没有约束条件情况下

f(4,1) = f(4,0) + f(3,1),f(3,2) = f(3,1) + f(2,2),即有从左到右和从下到上两种方式,根据约束条件,到达f(4,1)这个点不能是f(4,0),因为第五位必须是50,同理可以看到f(3,2)不能由f(3,1)得到,对于第八位是100的约束,可以采用同样的分析方法,在递归或者递推处理中针对这几个点做特殊处理。带约束条件的扩展题通过图2可能更容易理解。

下面是普通排队和扩展题的c代码:

/* 
 *	排队问题
 */

#include <stdio.h>
#define NUM 100

int f(int m50, int n100, int m, int n)
{
	if (m50 == m || n == n100)
		return 1;
	else if (m50 > n100)
		return f(m50+1, n100, m, n) + f(m50, n100+1, m, n);
	else if (m50 == n100)	
		return f(m50+1, n100, m, n);
	else
		printf("逻辑错误!");
	return 0;	
}

void main()
{
	int i,j,m,n,k[NUM][NUM]={0};

	//递归处理
	printf("分别输入持有50和100币值的人数:");	scanf("%d %d", &m, &n);
	printf("当持有50的人数为%d,持有100的人数为%d,总的排队方式有:%d\n",m,n, f(1,0,m,n));

	//递推方式
	for (i = 1; i <= m; i++)
		k[i][0] = 1;

	for (j = 1; j <= n; j++)	
	for (i = 0; i < j; i++)
		k[i][j] = 0;

	for (i = 1; i <= m; i++)
	for (j = 1; j <= n && j <= i; j++)
		k[i][j] = k[i-1][j] + k[i][j-1];
		
	printf("当持有50的人数为%d,持有100的人数为%d,总的排队方式有:%d\n",m,n, k[m][n]);
}

带约束条件的扩展题:

/*
 *  带约束条件的排队问题
 */

#include <stdio.h>

#define NUM 100

void main()
{
	int i,j,m,n,k[NUM][NUM]={0};
	
	printf("分别输入持有50和100币值的人数:"); scanf("%d %d", &m, &n);

	//递推方式
	for (i = 1; i <= 7; i++)
		k[i][0] = 1;

	for (j = 1; j <= n; j++)	
	for (i = 0; i < j; i++)
		k[i][j] = 0;

	for (i = 1; i <= m; i++)
	for (j = 1; j <= n && j <= i; j++)
	{
		if ((i == 4 && j == 1) || (i == 3 && j == 2) )
			k[i][j] = k[i-1][j];
		if((i == 7 && j == 1) ||(i == 6 && j == 2)||(i == 5 && j == 3))
			k[i][j] = k[i][j-1];
		else	
			k[i][j] = k[i-1][j] + k[i][j-1];
	}
		
	printf("当持有50的人数为%d,持有100的人数为%d,总的排队方式有:%d\n",m,n, k[m][n]);
}
 

参考资料:

1. 数据结构 : C语言版/ 严蔚敏,吴伟民编著

=============================================================================================

Linux应用程序、内核、驱动开发交流讨论群(745510310),感兴趣的同学可以加群讨论、交流、资料查找等,前进的道路上,你不是一个人奥^_^。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值