如何使这个Java代码段更有效?

时间:2012-01-21 05:50:24

标签: java algorithm

在计算机竞赛中,我遇到了一个问题,我不得不操纵输入数据。输入已经split()到一个数组中,其中data [0]是重复次数。最多可重复10 ^ 18次。我的程序返回Exception in thread "main" java.lang.OutOfMemoryError: Java heap space,但我没有参加比赛。

这是我的代码占用了内存和CPU:

long product[][]=new long[data[0]][2];
product[0][0]=data[1];
product[0][1]=data[2];
for(int a=1;a<data[0];a++){
  product[a][0]=((data[5]*product[a-1][0] + data[6]) % data[3]) + 1; // Pi = ((A*Pi-1 + B) mod M) + 1 (for all i = 2..N)
  product[a][1]=((data[7]*product[a-1][1] + data[8]) % data[4]) + 1; // Wi = ((C*Wi-1 + D) mod K) + 1 (for all i = 2..N)
}

以下是一些输入数据:

980046644627629799 9 123456 18 10000000 831918484 451864686 840000324 650000765
972766173386786486 123 1 10000000 10000000 590000001 680000000 610000001 970000002
299896237124947938 681206 164538 2280874 981991 416793690 904023823 813682336 774801135

我的程序最多只能工作7位或8位数,然后运行需要几分钟。有18个数字,几乎在我点击Eclipse中的“Run”时就崩溃了。

我很好奇如何在普通计算机上操作那么多数据。如果我的问题不清楚或您需要更多信息,请告诉我。谢谢!

3 个答案:

答案 0 :(得分:3)

你不能拥有也不需要如此庞大的数组。您只需跟踪最近的2个值。例如,只有product1和product2。

此外,考虑测试每次迭代后其中任何一个数字是否为NaN。如果是这样,抛出一个Exception并给出迭代号。 因为一旦你得到NaN,他们都将是NaN。除了你使用很长,所以刮。 “没关系”。 : - )

答案 1 :(得分:2)

long product[][]=new long[data[0]][2];

这是您粘贴的代码中唯一分配内存的行。您分配一个长度为data[0]的数组!随着数据的增长,数组也在增长。你想在这里申请的公式是什么?

您提供的第一个输入数据:

980046644627629799

已经太大,甚至无法声明数组。尝试使用它作为长度创建单个维度数组,看看会发生什么......

你确定你不只是想要一个积累的1 x 2矩阵吗?清楚地解释您的预期算法,我们可以帮助您提供更优化的解决方案。

答案 2 :(得分:0)

让我们把数字放在一边。

内存:一个long占用8个字节。 10 18 long需要 16,000,000太字节。太多了。

时间:10,000,000次操作≈1秒。 10 18 步骤≈ 30世纪。也太过分了。

您可以通过意识到您只需要最新的值来解决内存问题,并且整个阵列都是多余的:

long currentP = data[1];
long currentW = data[2];
for (int a = 1; a < data[0]; a++)
{
    currentP = ((data[5] * currentP + data[6]) % data[3]) + 1;
    currentW = ((data[7] * currentW + data[8]) % data[4]) + 1;
}

解决时间问题有点棘手。由于使用了模数,您可以观察到数字必须在某个点进入循环。一旦找到循环,就可以预测n次迭代后的值,而不必手动完成每次迭代。

查找周期的最简单方法是跟踪您是否访问过每个元素,然后直到遇到您之前看过的元素。在这种情况下,所需的存储量与M和K(数据[3]和数据[4])成比例。如果它们太大,则必须使用更节省空间的循环检测算法。

以下是找到P:

的值的示例
public static void main(String[] args)
{
    // value = (A * prevValue + B) % M + 1

    final long NOT_SEEN = -1; // the code used for values not visited before

    long[] data = { 980046644627629799L, 9, 123456, 18, 10000000, 831918484, 451864686, 840000324, 650000765 };
    long N = data[0];   // the number of iterations
    long S = data[1];   // the initial value of the sequence
    long M = data[3];   // the modulus divisor
    long A = data[5];   // muliply by this
    long B = data[6];   // add this
    int max = (int) Math.max(M, S);     // all the numbers (except first) must be less than or equal to M
    long[] seenTime = new long[max + 1];    // whether or not a value was seen and how many iterations it took

    // initialize the values of 'seenTime' to 'not seen'
    for (int i = 0; i < seenTime.length; i++)
    {
        seenTime[i] = NOT_SEEN;
    }

    // find the cycle
    long count = 0;
    long cycleValue = S;    // the current value in the series
    while (seenTime[(int)cycleValue] == NOT_SEEN)
    {
        seenTime[(int)cycleValue] = count;
        cycleValue = (A * cycleValue + B) % M + 1;
        count++;
    }

    long cycleLength = count - seenTime[(int)cycleValue];
    long cycleOffset = seenTime[(int)cycleValue];

    long result;

    if (N < cycleOffset)
    {
        // Special case: requested iteration occurs before the cycle starts
        // Straightforward simulation
        long value = S;
        for (long i = 0; i < N; i++)
        {
            value = (A * value + B) % M + 1;
        }
        result = value;
    }
    else
    {
        // Normal case: requested iteration occurs inside the cycle
        // Simulate just the relevant part of one cycle
        long positionInCycle = (N - cycleOffset) % cycleLength;
        long value = cycleValue;
        for (long i = 0; i < positionInCycle; i++)
        {
            value = (A * value + B) % M + 1;
        }
        result = value;
    }

    System.out.println(result);
}

我只是给你解决方案,因为看起来比赛结束了。从中学到的重要教训是,在开始编码之前,应始终检查边界,看看您的解决方案是否切实可行。