【问题标题】:Java : Testing Array Sum Algorithm EfficiencyJava:测试数组求和算法效率
【发布时间】:2016-05-14 23:53:42
【问题描述】:

我正在大学学习 Java 课程,我的笔记提供了 3 种计算 ArrayList 总和的方法。第一次使用迭代,第二次使用递归,第三次使用数组拆分结合递归。

我的问题是如何测试这些算法的效率?事实上,我认为算法计算值所需的步骤数是告诉你算法效率的因素。

我的 3 种算法代码:

import java.util.ArrayList;
public class ArraySumTester {

    static int steps = 1;

    public static void main(String[] args) {

        ArrayList<Integer> numList = new ArrayList<Integer>();

        numList.add(1);
        numList.add(2);
        numList.add(3);
        numList.add(4);
        numList.add(5);


        System.out.println("------------------------------------------");
        System.out.println("Recursive array sum = " + ArraySum(numList));

        System.out.println("------------------------------------------");
        steps = 1;
        System.out.println("Iterative array sum = " + iterativeSum(numList));

        System.out.println("------------------------------------------");
        steps = 1;
        System.out.println("Array sum using recursive array split : " + sumArraySplit(numList));

    }

    static int ArraySum(ArrayList<Integer> list) {
        return sumHelper(list, 0);
    }

    static int sumHelper(ArrayList<Integer> list, int start) {
        // System.out.println("Start : " + start);
        System.out.println("Rescursive step : " + steps++);
        if (start >= list.size())
            return 0;
        else
            return list.get(start) + sumHelper(list, start + 1);

    }

    static int iterativeSum(ArrayList<Integer> list) {
        int sum = 0;
        for (Integer item : list) {
            System.out.println("Iterative step : " + steps++);
            sum += item;
        }
        return sum;
    }

    static int sumArraySplit(ArrayList<Integer> list) {

        int start = 0;
        int end = list.size();
        int mid = (start + end) / 2;

        System.out.println("Rescursive step : " + steps++);
        //System.out.println("Start : " + start + ", End : " + end + ", Mid : " + mid);
        //System.out.println(list);

        if (list.size() <= 1)
            return list.get(0);
        else
            return sumArraySplit(new ArrayList<Integer>(list.subList(0, mid)))
                    + sumArraySplit(new ArrayList<Integer>(list.subList(mid,
                            end)));

    }
}

输出:

------------------------------------------
Rescursive step : 1
Rescursive step : 2
Rescursive step : 3
Rescursive step : 4
Rescursive step : 5
Rescursive step : 6
Recursive array sum = 15
------------------------------------------
Iterative step : 1
Iterative step : 2
Iterative step : 3
Iterative step : 4
Iterative step : 5
Iterative array sum = 15
------------------------------------------
Rescursive step : 1
Rescursive step : 2
Rescursive step : 3
Rescursive step : 4
Rescursive step : 5
Rescursive step : 6
Rescursive step : 7
Rescursive step : 8
Rescursive step : 9
Array sum using recursive array split : 15

现在从上面的输出中递归数组拆分算法采取的步骤最多,但是根据我的笔记,它与迭代算法一样有效。那么我的代码和笔记哪个不正确?

【问题讨论】:

  • 尝试使用 System.currentTimeMillis() 检查每个执行的时间量
  • 更好的是,使用System.nanoTime()
  • 主要是打印的内容和时间。仅当您实际将 2 个数字加在一起时,才会有一个步骤。所有这些if (list.size() &lt;= 1) 案例都不是步骤,因为根本没有添加。还有例如迭代版本从添加 0 + 1 开始,这也不是一个步骤(第一个是 1+2)。
  • 更简单地看到,你的讲座所说的:全部使用 4 个加法,递归/迭代执行:(((1 + 2) + 3) + 4) + 5),拆分执行:((1 + 2) + (3 + (4 + 5))) - 因此算法复杂度是相同的,与速度有多快无关算法运行。

标签: java arrays algorithm recursion


【解决方案1】:

您只想查看执行速度吗?如果是这样,您将需要查看微基准测试: How do I write a correct micro-benchmark in Java?

主要是因为 JVM 和现代处理器的工作方式,在 FOR 循环中运行一百万次并使用系统计时器 (EDIT) 测量执行速度不会获得一致的结果。

也就是说,“效率”还可以指其他方面,例如内存消耗。例如,任何递归方法都有堆栈溢出的风险,这个网站就是以这个问题命名的 :) 尝试给 ArrayList 数万个元素,看看会发生什么。

【讨论】:

    【解决方案2】:

    使用 System.currentTimeMillis() 是要走的路。在代码之前定义一个开始变量,在代码完成之后定义一个结束变量。这些差异将是您的程序执行所用的时间。最短的时间将是最有效的。

    long start = System.currentTimeMillis();

    //要测试的程序

    long end = System.currentTimeMillis(); 长差异 = 结束 - 开始;

    【讨论】:

      【解决方案3】:

      我建议您抽象地查看这些算法的运行时间和空间复杂度(这些是为了提高效率而使用的更多计算机科学名称)。这就是所谓的Big-Oh notation 的用途。

      当然,确切地说,在使实现尽可能紧密且无副作用之后,您应该考虑编写微基准测试。

      由于您必须能够读取列表中每个元素的值才能将这些元素相加,没有算法会比(线性)O(n) 时间执行得更好, O(1) 空间算法(这是您的迭代算法所做的)在一般情况下(即没有任何其他假设)。这里n 是输入的大小(即列表中元素的数量)。据说这种算法具有线性时间恒定空间复杂度,这意味着它的运行时间随着列表大小的增加而增加,但它确实 em> 需要任何额外的内存;事实上,它需要一些恒定内存来完成它的工作。

      其他两种递归算法充其量只能像这个简单的算法一样执行,因为迭代算法没有递归算法遇到的任何复杂性(例如堆栈上的额外内存)。

      这反映在具有相同O(f(n)) 运行时间的算法的所谓常数项中。例如,如果您以某种方式找到了一个算法,它检查列表中大约一半的元素来解决问题,而另一个算法必须查看所有元素,那么第一个算法的 constant 项比第二,预计会在实践中击败它,尽管这两种算法的时间复杂度都是O(n)

      现在,通过将巨型列表拆分为较小的列表(您可以通过索引到单个列表中来实现效果)来并行化该问题的解决方案是完全可能的,然后使用并行求和操作可能会击败其他算法,如果清单足够长。这是因为每个不重叠的间隔都可以并行(同时)求和,最后你可以对部分求和进行求和。但这不是我们在当前情况下考虑的可能性。

      【讨论】:

        【解决方案4】:

        我会说使用Guava Google Core Libraries For Java 秒表。示例:

        Stopwatch stopwatch = Stopwatch.createStarted();
        // TODO: Your tests here
        long elapsedTime = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
        

        你可以得到你需要的任何单位,而且你不需要任何额外的计算。

        【讨论】:

          【解决方案5】:

          如果你想考虑效率,那么你真的需要看算法结构而不是时间。

          为您正在使用的方法加载源代码,深入研究结构并寻找循环 - 这将为您提供正确的效率衡量标准。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-03-13
            • 2011-06-08
            • 1970-01-01
            • 2023-04-05
            • 2011-11-09
            • 1970-01-01
            相关资源
            最近更新 更多