繁体   English   中英

使用记忆化/动态编程的Java组合学

[英]Java Combinatorics using Memoization/Dynamic Programming

我正在编写一个程序,给出给定两个数字的可能组合的数量,例如N选择K。我有一个递归解决方案,如下所示:

public static int combinations(int group, int members) {
    if (members == 1) {
        return group;
    }
    else if (members == group) {
        return 1;
    }
    else {
        return(combinations(group - 1, members - 1) + 
                combinations(group - 1, members));
    }
}

这是可行的,但是我需要使用备忘录来提高时间复杂度并加快处理大量数字的速度,但我不确定该如何处理。 我将如何去做呢?

通过n choose k = ( n - 1 choose k - 1) + ( n-1 choose k )的公式n choose k = ( n - 1 choose k - 1) + ( n-1 choose k )自底向上的动态编程方法将是:

dp[n][k] = dp[n-1][k-1] + dp[n-1][k] if n > k 
else if n == k
dp[n][k] = 1
else
dp[n][k] = 0

n = 1k = 1

dp[1][1] = 1; dp[1][0] = 1; 

然后填充一个二维数组,直到dp[n][k]

如您的情况,也可以通过记忆来完成。 您的方法可以更改为:

int[][] dp = new int[group][members];

public static int combinations(int group, int members, int[][] dp ) {

    if (members == 1) {
        return group;
    } else if (members == group) {
        return 1;
    }

    if ( dp[group][members] != 0 ) {
       return dp[group][members];
    }

    int first = 0, second = 0;
    if ( members <= group - 1) {
      first = combinations( group - 1, members - 1, dp );
      second = combinations( group - 1, members );
    } else if ( members - 1 <= group - 1 ) {
      first = combinations( group - 1, members - 1, dp );
    }
    dp[group][members] = first + second;

    return dp[group][members];
}

一种方法是进行缓存,这伴随着巨大的内存使用成本。

public static int combinations(int group, int members) {
    if (members > group - members) {
        members = group - members; // 21 choose 17 is same as 21 choose 4
    }

    final int[][] cache = new int[group][members];
    return combinations(group, members, cache);
}
private static int combinations(int group, int members, int[][] cache) {
    if (cache[group - 1][members - 1] > 0) {
        return cache[group - 1][members - 1];
    }
    else if (members == 1) {
        cache[group - 1][members - 1] = group;
        return group;
    }
    else if (members == group) {
        cache[group - 1][members - 1] = 1;
        return 1;
    }
    else {
        return (combinations(group - 1, members - 1, cache) + combinations(group - 1, members, cache));
    }
}

我进行了一些快速测试(非专业基准测试),发现原始方法花费了缓存方法一半的时间。 看起来所有这些对阵列缓存的读/写操作都在极大地减慢速度。

另一种方法是更改​​整个公式。

public static int combinations(int group, int members) {
    if (members > group - members) {
        members = group - members;
    }

    int answer = 1;
    for (int i = group; i > group - members; i--) {
        answer *= i;
    }

    for (int i = 1; i <= members; i++) {
        answer /= i;
    }

    return answer;
}

再次,我用原始方法测试了新方法(我让他们使用BigInteger进行测试),并且新方法的速度令人难以置信(原始方法为26秒,后者为0.00秒(35为15))。

补充一点,我认为使用递归调用的时间复杂度是O((group)(log members)) ,而使用新公式只是O(members)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM