解决同一任务的两种算法之间的运行时间明显不同

时间:2019-03-12 22:14:13

标签: java algorithm

在学习算法的过程中,我编写了代码以比较两种算法在运行时间方面的性能。这些算法的任务是找到一个数组中所有相加的数字对,这些数字对加起来就是一个特定的数字。

第一种方法-蛮力。 2个for循环查找与给定数字相加的数字对。时间复杂度基本上是O(n * n)。

第二种方法-高效 首先对数组进行排序,然后将开始和结束作为数组开始和结束的索引,然后根据位置中这些元素的总和向左或向右移动以找到数字对。

我的问题是- 我正在打印每种算法方法的运行时间。但是,蛮力法的运行时间似乎比高效方法快。为什么会这样呢?

在此处查看代码-

public class MainRunner {

final private static int numberRange = 100000;

public static void generateRandomNumbers(int[] array, int[] dupArray) {
    System.out.println("Generated Array: ");
    Random random = new Random();
    for (int i = 0; i < array.length; i++) {
        int generatedRandomInt = random.nextInt(array.length) + 1;
        array[i] = dupArray[i] = generatedRandomInt;
    }
}

public static void main(String[] args) {
    int[] array = new int[numberRange];
    int[] dupArray = new int[numberRange];

    generateRandomNumbers(array, dupArray);

    Random random = new Random();
    int sumToFind = random.nextInt(numberRange) + 1;
    System.out.println("\n\nSum to find: " + sumToFind);

    // Starting Sort and Find Pairs
    final long startTimeSortAndFindPairs = System.currentTimeMillis();
    new SortAndFindPairs().sortAndFindPairsOfNumbers(sumToFind, array);
    final long durationSortAndFind = System.currentTimeMillis() - startTimeSortAndFindPairs;

    // Starting Find Pairs
    final long startTimeFindPairs = System.currentTimeMillis();
    new FindPairs().findPairs(sumToFind, dupArray);
    final long durationFindPairs = System.currentTimeMillis() - startTimeFindPairs;

    System.out.println("Sort and Find Pairs: " + durationSortAndFind);
    System.out.println("Find Pairs: " + durationFindPairs);
    }
}

SortAndFindPairs.java

public class SortAndFindPairs {

public void sortAndFindPairsOfNumbers(int argNumberToFind, int[] array) {
    Arrays.sort(array);

    System.out.println("\n\nResults of Sort and Find Pairs: \n");
    int startIndex = 0;
    int endIndex = array.length - 1;

    while (startIndex < endIndex) {
        int sum = array[startIndex] + array[endIndex];
        if (argNumberToFind == sum) {
            //System.out.println(array[startIndex] + ", " + array[endIndex]);
            startIndex++;
            endIndex--;
        } else if (argNumberToFind > sum) {
            startIndex++;
        } else {
            endIndex--;
        }
    }
}

和FindPairs.java

public class FindPairs {

public void findPairs(int argNumberToFind, int[] array) {
    System.out.println("\nResults of Find Pairs: \n");
    int randomInt1 = 0;
    int randomInt2 = 0;
    for (int i = 0; i < array.length - 1; i++) {
        for (int j = i + 1; j < array.length; j++) {
            int sum = array[i] + array[j];
            if (argNumberToFind == sum) {
                //System.out.println(array[i] + ", " + array[j]);
                //randomInt1++;
                //randomInt2--;

            }
        }
    }
}}

仅在FindPairs.java中添加两个变量randomInt1randomInt2时,才会看到运行时间差异。否则,FindPairs.java的运行时间比SortAndFindPairs.java少得多。那么,为什么只添加两个变量操作会增加这么多时间呢?按照惯例,简单的操作应消耗可忽略的时间。我在这里错过了什么吗?

numberRange = 1000000的结果

查找对的结果:

排序和查找对:641 查找对:57

4 个答案:

答案 0 :(得分:1)

如LIuxed所述,排序操作需要一些时间。如果您花时间进行排序,那为什么不利用列表项已排序的事实呢?

如果列表元素已排序,则可以使用二进制搜索算法...从数组的中间开始,然后检查是向上1/2还是向下1/2。结果,使用排序数组来查找值可以获得更快的性能。这样的算法已经在Arrays.binarySearch方法中实现。

请参见https://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html#binarySearch(int[],%20int)

当您只排序一次但搜索多次时,您会注意到差异。

答案 1 :(得分:1)

我认为问题在于您的编译器优化对您有帮助。我尝试了代码的不同排列,并发现FindPairs中的double for循环几乎什么也不做。因此,编译器可能会剥离一些代码。

我得到了这个数字以及您的代码的确切副本:

排序和查找对:43 查找对:13

始终如一(我多次运行以进行双重检查)每次的排序和查找速度都很慢。

但是后来我改变了内循环,什么也不做:

for (int j = i + 1; j < array.length; j++) {
        //int sum = array[i] + array[j];
        //if (argNumberToFind == sum) {
            //System.out.println(array[i] + ", " + array[j]);
            //randomInt1++;
            //randomInt2--;

        //}

你猜怎么着?我得到了:

排序和查找对:20 查找对:11

尝试了几次,数字非常相似。通过删除两个循环,查找对的运行时间为1。因此,我的猜测是,编译器的优化步骤可能是假设内部循环内的代码没有任何作用,因此将其删除。排序和查找中的代码更加智能,因此可以保留。

现在,我尝试了另一件事,我注释掉randomInt1的增量,但是保留了总和,如果有注释,

for (int j = i + 1; j < array.length; j++) {
        //int sum = array[i] + array[j];
        //if (argNumberToFind == sum) {
            //System.out.println(array[i] + ", " + array[j]);
            randomInt1++;
            //randomInt2--;

        //}

然后我得到了:

排序和查找对:42 查找对:5

哇,突然变得更快了! (也许编译器通过使用循环边界将for替换为randomInt1的算术计算?)

我的最后一次尝试。您会注意到这不是一个公平的比较,排序和查找涉及很多逻辑,而查找则没有。当找到一对时,它实际上什么也不做。因此,要使它变成苹果,我们要确保查找对确实可以做某事,并确保排序和查找执行相同的额外量(例如在方程式的两边加相同的数字)。因此,我更改了计算匹配对数的方法。像这样:

        System.out.println("\nResults of Find Pairs: \n");                                      
        long randomInt1 = 0;                                                                    
        int randomInt2 = 0;                                                                     
        int count = 0;                                                                          
        for (int i = 0; i < array.length - 1; i++) {                                            
            for (int j = i + 1; j < array.length; j++) {                                        
                int sum = array[i] + array[j];                                                  
                if (argNumberToFind == sum) {                                                   
                    count++;                                                                    
                }                                                                               
            }                                                                                   
        }                                                                                       
        System.out.println("\nPairs found: " + count + "\n"); 

    public void sortAndFindPairsOfNumbers(int argNumberToFind, int[] array) {                   
        Arrays.sort(array);                                                                     

        System.out.println("\n\nResults of Sort and Find Pairs: \n");                           
        int startIndex = 0;                                                                     
        int endIndex = array.length - 1;                                                        
        int count = 0;                                                                          

        while (startIndex < endIndex) {                                                         
            int sum = array[startIndex] + array[endIndex];                                      
            if (argNumberToFind == sum) {                                                       
                //System.out.println(array[startIndex] + ", " + array[endIndex]);               
                startIndex++;                                                                   
                endIndex--;                                                                     
                count++;                                                                        
            } else if (argNumberToFind > sum) {                                                 
                startIndex++;                                                                   
            } else {                                                                            
                endIndex--;                                                                     
            }                                                                                   
        }                                                                                       
        System.out.println("\nPairs found: " + count + "\n");                                   
    }

然后得到:

排序和查找对:38 查找对:4405

寻找配对的时间炸毁了!排序和查找与我们之前看到的保持一致。

因此,最有可能解决您的问题的方法是编译器正在优化某些东西,而几乎为空的for循环肯定是编译器可以用来优化的东西。而对于排序和查找,复杂的逻辑可能会使优化器后退。找到您的算法课程。在这里,java在耍你。

您可以尝试的另一件事是使用不同的语言。我很确定您这样做会发现有趣的东西!

答案 2 :(得分:0)

因为使用选择算法,所以调用Array.sort(MyArray)方法需要很长时间。这意味着,Sort方法将遍历所有数组x次(x = array.lenght),以查找最小/最大值,并将其设置在数组顶部,依此类推。

这就是为什么使用此方法需要很长时间,具体取决于数组大小。

答案 3 :(得分:0)

我从您的 sortAndFindPairsOfNumbers 方法中删除了所有内容,只是保留了

Arrays.sort(array);

但是时差仍然更大。 这意味着大部分时间是通过排序方法进行的。

因此,您认为第二种方法有效的想法是不正确的。其全部与输入大小有关。

如果您保留numberRange,假设为 1000 ,那么 SortAndFindPairs 将会更快。