先看下题目:
电影院买票排队,票价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),感兴趣的同学可以加群讨论、交流、资料查找等,前进的道路上,你不是一个人奥^_^。