java - 为什么插入排序在不同的实现中在时间上运行如此不同

标签 java algorithm insertion-sort

我正在对基于数组的不同长度实现的插入排序算法进行计时。

我知道插入排序的平均情况是 O(n^2),所以我意识到当您尝试对大型数组进行排序时它会花费一些时间,但是为什么下面两个实现之间会有将近 6500 毫秒的差异包含 100,000 个条目的数组?

这是我的数组的设置,其中填充了 1 到 1 百万的随机整数

int[] oneHundredThousand = new int[100000];
Random r = new Random();//RANDOMIZER

for(int i = 0; i < oneHundredThousand.length; i++)
        oneHundredThousand[i] = r.nextInt(1000000) + 1; //1 - 1000000

这些是我运行用于测试的 2 种方法,它们使用插入排序

public static long insertionSort1(int[] intArray) {
    long startTime = System.currentTimeMillis();

    int n = intArray.length;
    for (int k = 1; k < n; k++) {         
        int cur = intArray[k];                
        int j = k;                          
        while (j > 0 && intArray[j-1] > cur) {  
            intArray[j] = intArray[j-1];              
            j--;                              
        }
        intArray[j] = cur;                      
    }

    long endTime = System.currentTimeMillis();
    long elapsedTime = endTime - startTime;
    return elapsedTime;
}

public static long insertionSort2(int[] input){
    long startTime = System.currentTimeMillis();
    int temp;
    for (int i = 1; i < input.length; i++) {
        for(int j = i ; j > 0 ; j--){
            if(input[j] < input[j-1]){
                temp = input[j];
                input[j] = input[j-1];
                input[j-1] = temp;
            }
        }
    }
    long endTime = System.currentTimeMillis();
    long elapsedTime = endTime - startTime;
    return elapsedTime;
}

现在像这样在 main 中调用这些方法(复制数组以便通过每个“排序”保留原始顺序),我得到了注释结果,为什么会有如此大的不同?我不认为同一算法的不同实现效率应该低得多。

int[] copy100_1 = Arrays.copyOf(oneHundredThousand, oneHundredThousand.length);
int[] copy100_2 = Arrays.copyOf(oneHundredThousand, oneHundredThousand.length);

//816ms 
System.out.print(insertionSort1(copy100_1));
//7400ms
System.out.print(insertionSort2(copy100_2));

最佳答案

分析插入排序,发现最好的情况。执行时间是 O(n²)。让我们以您的第一个实现为例来了解原因。

public static long insertionSort1(int[] intArray) {
    long startTime = System.currentTimeMillis();

    int n = intArray.length;
    for (int k = 1; k < n; k++) {         
        int cur = intArray[k];                
        int j = k;

        while (j > 0 && intArray[j-1] > cur) { // This loop can break early due 
                                               // to intArray[j-1] > cur
            intArray[j] = intArray[j-1];              
            j--;                              
        }
        intArray[j] = cur;                      
    }

    long endTime = System.currentTimeMillis();
    long elapsedTime = endTime - startTime;
    return elapsedTime;
}

这意味着对于(部分)排序的数组,(这部分)的执行时间减少到 O(n)

您的第二个实现缺少这样的提前中断条件。但是您可以非常简单地添加它:

public static void insertionSort2(int[] input) {
    int temp;
    for (int i = 1; i < input.length; i++) {
        for (int j = i; j > 0; j--) {
            if (input[j] < input[j - 1]) {
                temp = input[j];
                input[j] = input[j - 1];
                input[j - 1] = temp;
            } else {
                break; // this is the "early break" missing.
            }
        }
    }
}

我拍了@amanin's answer作为模板并重新实现所有三个版本。

package benchmark;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@OutputTimeUnit(TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
@Fork(1)
public class Test {

    @State(Scope.Benchmark)
    public static class Input {

        public static final Random rng = new Random();
        final int[] array;
        final int[] expected;

        public Input() {
            final Random r = new Random();
            this.array = new int[200_000];
            for (int i = 0; i < this.array.length; i++) {
                this.array[i] = i;
            }
            this.expected = Arrays.copyOf(this.array, this.array.length);

            // Fisher-Yates shuffle
            for (int i = this.array.length - 1; i > 0; --i) {
                int swap = Input.rng.nextInt(i);
                int tmp = this.array[swap];
                this.array[swap] = this.array[i];
                this.array[i] = tmp;
            }
        }
    }

    @Benchmark
    public void benchSort1(final Input in) {
        insertionSort1(in.array);
    }

    @Benchmark
    public void benchSort2(final Input in) {
        insertionSort2(in.array);
    }

    @Benchmark
    public void benchSort3(final Input in) {
        insertionSort3(in.array);
    }

    public static void insertionSort1(int[] intArray) {
        int n = intArray.length;
        for (int k = 1; k < n; k++) {
            int cur = intArray[k];
            int j = k;
            while (j > 0 && intArray[j - 1] > cur) {
                intArray[j] = intArray[j - 1];
                j--;
            }
            intArray[j] = cur;
        }
    }

    public static void insertionSort2(int[] input) {
        int temp;
        for (int i = 1; i < input.length; i++) {
            for (int j = i; j > 0; j--) {
                if (input[j] < input[j - 1]) {
                    temp = input[j];
                    input[j] = input[j - 1];
                    input[j - 1] = temp;
                }
            }
        }
    }

    public static void insertionSort3(int[] input) {
        int temp;
        for (int i = 1; i < input.length; i++) {
            for (int j = i; j > 0; j--) {
                if (input[j] < input[j - 1]) {
                    temp = input[j];
                    input[j] = input[j - 1];
                    input[j - 1] = temp;
                } else {
                    break;
                }
            }
        }
    }

    public static void main(String[] arg) throws Exception {
        Options option = new OptionsBuilder().include(Test.class.getSimpleName()).build();
        new Runner(option).run();
    }
}

这些是最终结果,benchSort1benchSort2 是您的原始版本,benchSort3 是“更正的”双 版本:

Benchmark        Mode  Cnt      Score   Error  Units
Test.benchSort1  avgt    2      0.307          ms/op
Test.benchSort2  avgt    2  15145.611          ms/op
Test.benchSort3  avgt    2      0.210          ms/op

如您所见,这两个版本现在在时间上非常接近。

关于java - 为什么插入排序在不同的实现中在时间上运行如此不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49902806/

相关文章:

c - 实现插入算法

algorithm - 为什么对于小的情况,插入排序比快速排序和冒泡排序快?

c++ - 如何在插入排序中使用 replace() 使语句变得不必要

java - 使用 ApprovalTest 验证多个图像

java - 将字符串转换为 java.time.ZonedDateTime

java - 对象类型的隐式声明和显式泛型声明之间的区别

arrays - 将哈希表转换为 O(log(k)*k + n) 中的排序数组

arrays - 如何通过跳过一些数字将数字插入到循环数组中?

java - "TransactionRequiredException: no transaction is in progress"即使应用事务拦截器 - hibernate-5 和 spring-4.3

ruby - 实现k最近邻需要哪些数据?