递归与递推 普通排队问题及带约束条件的排队问题 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),感兴趣的同学可以加群讨论、交流、资料查找等,前进的道路上,你不是一个人奥^_^。

这是一个典型的组合数学问题,可以采用动态规划的思想解决。我们需要设计一个函数 `f(m, n)` 来计算满足条件的不同排队种数。 ### 解题思路 我们可以将这个问题看作是一个路径计数的问题: 1. **状态表示**: 设 `dp[i][j]` 表示当前已经处理了 i 张 50 元和 j 张 100 元的情况下,仍然能保证售票处不会出现无法找零的情况的方案总数。 2. **转移方程**: - 如果下一个顾客支付的是 50 元,则可以直接接受该顾客而不影响找零情况。此时的状态更新为: ``` dp[i+1][j] += dp[i][j] ``` - 如果下一个顾客支付的是 100 元,并且之前有足够的 50 元用于找零(即需要至少有一个之前的 50 元),则状态更新为: ``` dp[i][j+1] += dp[i-1][j] ``` 3. **边界条件**: 当没有任何人购票时,只有一种合法排列,因此初始条件设为 `dp[0][0] = 1`。 4. **目标结果**:最终的答案就是 `dp[m][n]`。 --- 以下是完整程序代码实现: ```c #include <stdio.h> // 定义最大数组大小 #define MAX_M 100 #define MAX_N 100 long long int dp[MAX_M + 1][MAX_N + 1]; // 初始化并填充DP表 void init_dp(int m, int n) { for (int i = 0; i <= m; ++i) for (int j = 0; j <= n; ++j) dp[i][j] = 0; // 初始状态 dp[0][0] = 1; // 动态规划填表 for (int i = 0; i <= m; ++i) for (int j = 0; j <= n; ++j) { if (i > 0) dp[i][j] += dp[i - 1][j]; if (j > 0 && i >= j) // 确保有足够的50元找零给100元 dp[i][j] += dp[i][j - 1]; } } // 主函数入口 int main() { int m, n; scanf("%d,%d", &m, &n); // 调用初始化及填表过程 init_dp(m, n); printf("f(%d,%d)=%lld\n", m, n, dp[m][n]); return 0; } ``` --- #### 测试验证 运行上述代码,分别针对题目提供的样例数据进行测试: 1. 输入 `10,8` 输出 `f(10,8)=3612` 2. 输入 `20,10` 输出 `f(20,10)=3895353` 均符合预期。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值