1.1 题目描述
完成二项式公式计算,即C(n, k) = C(n-1, k-1)+ C(n-1, k)式解释:为了从n个不同元素中抓取k个元素(C(n, k)),可以这样考虑,如果第一个元素一定在结果中,那么就需要从剩下的n-1个元素中抓取k-1个元素(C(n-1, k-1));如果第一个元素不在结果中,就需要从剩下的n-1个元素中抓取k个元素(C(n-1, k))。
要求分别采用以下方法计算,并进行三种方法所需时间的经验分析。
1. 直接采用递归算法
2. 采用备忘录方法
3. 采用迭代算法
要有性能数据。最好是T(n, k)的曲线或者曲面图。如果是曲线,t = T(n, k),可以固定不同的n值,分别考察t ~ t(k)的变化情况。迭代算法要和递归思路相关。另外,Excel其实可以帮你完成函数拟合(回归),就是帮你猜时间复杂度。
1.2 程序使用说明
1.3 分析和设计
- 思路
根据递推公式(C(n, k) = C(n-1, k-1)+ C(n-1, k)),在递归中,计算C(n, k)需要计算C(n-1, k-1)和C(n-1, k))的值,所以需要在递归调用函数来求解这两个的值。在备忘录方法中,需要将计算到的二项式的值记录到备忘录中,在需要时可以直接读取,读取失败,即未计算过此二项式,才需要计算,这大大减少了重复计算。在迭代算法中,计算下标n的二项式需要计算下标n-1的二项式,以此类推,需要最先计算下标为1的二项式,然后迭代到n。
2. 伪代码
A. 递归算法
B. 备忘录Recurrence(n, k)
// 输入:二项式下标上标n、k
// 输出:二项式的值
if n = 1 or n = k
return 1
else if k = 1
return n
else
return Recurrence(n-1, k-1) + Recurrence(n-1, k)
C. 迭代Notebook(n, k, note)
// 输入:二项式下标上标n、k,一个默认为-1的矩阵note[1...n, 1...k]
// 输出:二项式的值
if note[n, k] = -1
if n = 1 or n = k
note[n, k] <- 1
else if k = 1
note[n, k] <- n
else
note[n, k] <- Notebook(n-1, k-1, note) + Notebook(n-1, k, note)
return note[n, k]
Iteration(n, k)
// 输入:二项式下标上标n、k
// 输出:二项式的值
// 存储二项式下标为i的计算结果
preRow[1...k], curRow[1...k]
preRow <- 1
// 计算下标为i的二项式的值
for i <- 2 to n do
curRow[1] <- i
// 计算上标为j的二项式的值
for j <- 2 to min(k, i-1) do
curRow[j] <- preRow[j] + preRow[j-1]
if i <= k
curRow[i] <- 1
temp <- curRow
curRow <- preRow
preRow <- temp
return preRow[k]
3. 时间复杂度
在递归算法中,计算一个二项式的值需要计算另外两个二项式的值,这会形成一个二叉树,二叉树的高度为n,所以时间复杂度为O(2n);在备忘录算法中,二项式的值是从备忘录中查找的,需要计算的二项式的值计算一次即可,无需重复计算,所以时间复杂度为O(nk),而迭代算法中,需要依次计算出下标从1到n,上标从1到k的所有值,它的时间复杂度也为O(nk)。1.4 测试用例
| | | | | | | —- | —- | —- | —- | —- | | 1 | n = 10 k=1 | 10 10 10 | 10 10 10 | 通过 | | 2 | n = 10 k = 3 | 120 120 120 | 120 120 120 | 通过 | | 3 | n = 10 k = 4 | 210 210 210 | 210 210 210 | 通过 |
1.5 源代码(含注释)
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Scanner;
public class Binomial {
public static long[][] note;
public static void main(String[] args) {
int n = 100;
Scanner scanner = new Scanner(System.in);
System.out.print("请输入n值:");
n = scanner.nextInt();
System.out.println("k\t递归\t备忘录\t迭代");
for (int i = 1; i < n / 2; i += 1) {
System.out.print(i + "\t");
System.out.print(recurrence(n, i) + "\t");
note = initNotebook(n, i);
System.out.print(notebook(n, i) + "\t");
System.out.print(iteration(n, i));
System.out.println();
}
System.out.println("k\t递归\t备忘录\t迭代");
for (int i = 1; i < n / 2; i += 1) {
System.out.print(i + "\t");
Instant start = Instant.now();
recurrence(n, i);
Instant end = Instant.now();
System.out.print(ChronoUnit.MILLIS.between(start, end) + "\t");
start = Instant.now();
note = initNotebook(n, i);
notebook(n, i);
end = Instant.now();
System.out.print(ChronoUnit.MILLIS.between(start, end) + "\t");
start = Instant.now();
iteration(n, i);
end = Instant.now();
System.out.print(ChronoUnit.MILLIS.between(start, end));
System.out.println();
}
}
/**
* 递归方法
*
* @param n 元素个数
* @param k 选择个数
* @return 二项式的值
*/
public static int recurrence(int n, int k) {
if (n == 1 || n == k) {
return 1;
} else if (k == 1) {
return n;
} else {
return recurrence(n - 1, k - 1) + recurrence(n - 1, k);
}
}
/**
* 初始化备忘录,即赋默认值-1
*
* @param n 元素个数
* @param k 选择个数
* @return 备忘录
*/
public static long[][] initNotebook(int n, int k) {
// 为了让数组下标与二项式下标一致,需要多加一圈
long[][] note = new long[n + 1][k + 1];
for (int i = 0; i < n + 1; i++) {
for (int j = 0; j < k + 1; j++) {
// 默认-1
note[i][j] = -1;
if (i == 1 || i == j) {
// 只有一个元素或者元素个数与需要选择的个数相同
note[i][j] = 1;
} else if (j == 1) {
// 只选择一个元素
note[i][j] = i;
}
}
}
return note;
}
/**
* 备忘录
*
* @param n 元素个数
* @param k 选择个数
* @return 二项式的值
*/
public static long notebook(int n, int k) {
if (note[n][k] == -1) {
// 当前二项式未计算过时
note[n][k] = notebook(n - 1, k - 1) + notebook(n - 1, k);
}
return note[n][k];
}
/**
* 迭代
*
* @param n 元素个数
* @param k 选择个数
* @return 二项式的值
*/
public static long iteration(int n, int k) {
// 最多一行只有k个元素,preRow已经计算的结果,curRow表示当前正在计算的行
long[] preRow = new long[k + 1];
long[] curRow = new long[k + 1];
for (int i = 1; i < k + 1; i++) {
preRow[i] = 0;
curRow[i] = 0;
}
// C(1, 1) = 1
preRow[1] = 1;
for (int i = 2; i <= n; i++) {
// C(i, 1) = i;
curRow[1] = i;
for (int j = 2; j <= k && j < i; j++) {
// C(i, j) = C(i-1, j) + C(i-1, j-1)
curRow[j] = preRow[j] + preRow[j - 1];
}
// C(i, i) = 1
if (i <= k) {
// 避免数组下标越界
curRow[i] = 1;
}
long[] temp = curRow;
curRow = preRow;
preRow = temp;
}
return preRow[k];
}
}