作为一名初学程序员,我最近买了Robert Sedgewick / Kevin Wayne的“算法 - 第四版”这本书,我非常感谢每一章末尾的练习。然而,有一个练习(看起来很简单)让我疯狂,因为我无法找到解决方案。
你必须采用这种递归算法,找出在n次试验中获得完全k次成功的概率,其中p是一个事件成功的可能性。给出的算法基于递归二项分布论坛。
public static double binomial(int n, int k, double p) {
if (n == 0 && k == 0)
return 1.0;
else if (n < 0 || k < 0)
return 0.0;
return (1 - p) * binomial(n - 1, k, p) + p * binomial(n - 1, k - 1, p);
}
本练习的目的是通过在数组中保存计算值来加快此算法的速度。通过使用另一种获得二项分布[p(x)= nCr * p ^ k *(1 - p)^(n - k)]的方法,我已经使这个算法变得更快了,它使用迭代方法来寻找阶乘。但是,我不明白如何使用数组来改善此上下文中的执行时间。
非常感谢任何帮助!
......在有人问之前,这不是作业!
答案 0 :(得分:5)
本书试图教你一种名为memoization的特殊编程技术,这种技术被称为dynamic programming。当然,在现实生活中,了解封闭形式的解决方案要好得多,但不能解决这个问题。
无论如何,我们的想法是将2D数组作为第四个参数传递,最初用NaN
填充它,然后检查是否存在n
和{k
的给定组合的解决方案计算任何东西之前在数组中{1}}。如果有,请归还;如果没有,则递归计算,存储在数组中,然后才返回。
答案 1 :(得分:3)
此处的递归算法最终会反复调用特定条件。例如:
3, 3
2, 3
1, 3
0, 3
0, 2
1, 2
0, 2
0, 1
2, 2
1, 2
0, 2
0, 1
1, 1
0, 1
0, 0
通过记住,例如,出现什么值(1,2),并在再次使用这些参数调用时立即返回,可以提高效率。使用Guava的Table
,这看起来像:
public static double binomial(int n, int k, double p, Table<Integer, Integer, Double> memo) {
if(memo.contains(n, k))
return memo.get(n, k);
double result;
if (n == 0 && k == 0)
result = 1.0;
else if (n < 0 || k < 0)
result = 0.0;
else
result = (1 - p) * binomial(n - 1, k, p) + p * binomial(n - 1, k - 1, p);
memo.put(n, k, result);
return result;
}
答案 2 :(得分:0)
有点晚了但是对于那些正在寻找完整解决方案的人来说,这是我的下方。首先,我建议其他人阅读这里给出的答案:https://stackoverflow.com/a/6165124/4636721以了解动态编程,记忆和制表意味着什么
无论如何关于我的解决方案所以基本上我们有给定的方法:
// Not efficient at all
private static double binomial(int N, int k, double p)
{
if (N == 0 && k == 0)
{
return 1.0;
}
else if ((N < 0) || (k < 0))
{
return 0.0;
}
else
{
return (1.0 - p) * binomial(N - 1, k, p) + p * binomial(N - 1, k - 1, p);
}
}
是的,这真的很慢......递归调用的数量有点大(约~N ^ 2)
是的,你可以使用memoization方法,基本上正如其他人已经说明的那样,它基本上是先前计算过的缓存值。 对于某些人而言,memoization意味着保持递归策略并检查我们需要的值是否已计算,如果不是程序必须计算并缓存它,它实际上很容易实现:
private static double binomialTopDown(int N, int k, double p)
{
double[][] cache = new double[N + 1][k + 1];
for (int i = 0; i < (N + 1); i++)
{
Arrays.fill(cache[i], Double.NaN);
}
return binomialTopDown(N, k, p, cache);
}
// More efficient
private static double binomialTopDown(int N, int k, double p, double[][] cache)
{
if ((N == 0) && (k == 0))
{
return 1.0;
}
else if ((N < 0) || (k < 0))
{
return 0.0;
}
else if (Double.isNaN(cache[N][k]))
{
cache[N][k] = (1.0 - p) * binomialTopDown(N - 1, k, p, cache) + p * binomialTopDown(N - 1, k - 1, p, cache);
}
return cache[N][k];
}
诀窍实际上是使用自下而上的方法(也称为制表)以更有效的方式对计算进行排序。这通常通过使用上述算法的迭代版本来实现。
// Much more efficient
private static double binomialBottomUp(int N, int k, double p)
{
/*
double[][] cache = new double[N + 1][k + 1];
cache[0][0] = 1.0;
for (int i = 1; i <= N; i++)
{
cache[i][0] = Math.pow(1.0 - p, i);
for (int j = 1; j <= k; j++)
{
cache[i][j] = p * cache[i - 1][j - 1] + (1.0 - p) * cache[i - 1][j];
}
}
return cache[N][k];
*/
// Optimization using less memory, swapping two arrays
double[][] cache = new double[2][k + 1];
double[] previous = cache[0];
double[] current = cache[1];
double[] temp;
previous[0] = 1.0;
for (int i = 1; i <= N; i++)
{
current[0] = Math.pow(1.0 - p, i);
for (int j = 1; j <= k; j++)
{
current[j] = p * previous[j - 1] + (1.0 - p) * previous[j];
}
temp = current;
current = previous;
previous = temp;
}
return previous[k];
}
使用自下而上方法进行动态编程是最有效的方法。
希望这有帮助。