有可能比线性搜索更快的算法?

时间:2015-10-29 23:43:33

标签: java algorithm search

我听说没有比线性搜索更快的算法(对于未排序的数组),但是,当我运行这个算法时(线性):

public static void search(int[] arr, int value){
    for(int i = 0; i < arr.length; i++){
        if(arr[i] == value) return;
    }
}

使用长度为1000000的随机数组, 找到一个值的平均时间是75ns, 但是使用这个算法:

public static void skipSearch(int[] arr, int value){
    for(int i = 0; i < arr.length; i+=2){
        if(arr[i] == value) return;
    }
    for(int i = 1; i < arr.length; i+=2){
        if(arr[i] == value) return;
    }
}

我得到一个更短的平均值,68ns?

编辑:很多人说我没有做适当的基准测试,这是侥幸的,但我运行这些功能1000000次并得到了平均值。每次运行1000000次函数时,第一个算法得到75-76ns,第二个算法得到67-69ns。

我用过java System.nanoTime() 测量这个。

代码:

int[] arr = new int[1000];
Random r = new Random();
for(int i = 0; i < arr.length; i++){
    arr[i] = r.nextInt();
}
int N = 1000000;
long startTime = System.nanoTime();
for(int i = 0; i < N; i++){
    search(arr, arr[(int) Math.floor(Math.random()*arr.length)]);
}
System.out.println("Average Time: "+(System.nanoTime()-startTime)/(float)N+"ns");
startTime = System.nanoTime();
for(int i = 0; i < N; i++){
    skipSearch(arr, arr[(int) Math.floor(Math.random()*arr.length)]);
}
System.out.println("Average Skip Search Time: "+(System.nanoTime()-startTime)/(float)N+"ns");

8 个答案:

答案 0 :(得分:31)

很有可能,因为您的search()方法没有返回任何内容,并且循环中没有任何操作,JVM中的JIT compiler 优化代码 - 换句话说,在将字节代码加载到JVM之前修改字节代码,这样你的search()方法很可能都不会(几乎)任何。哪个是最重要的,它可能也完全消除了循环。 JIT优化非常智能,当不需要将任何代码加载到JVM中时,它可以识别很多情况(但是代码在字节代码.class文件中)。 / p>

然后你只测量随机数 - 而不是你方法的实时复杂性。

阅读,例如how to make sure no jvm and compiler optimization occurs,应用它并再次运行您的基准测试。

同时更改search()方法,使它们返回索引 - 从而使优化器的生命更难。但是,有时创建一个无法优化的代码却非常困难:)关闭优化(如上面的链接)更可靠。

通常,对未经优化的代码进行基准测试是没有意义的。但是,在这种情况下,OP想要测量理论算法。他想测量实际的传球次数。他必须确保实际执行循环。这就是他应该关闭优化的原因。

OP认为他所测量的是算法的速度,而事实上算法根本没有机会运行。在此特定情况下关闭的JIT优化可修复基准。

答案 1 :(得分:18)

这就是为什么我们不关心字面上计算执行时间需要多长时间,以及随着输入复杂性的增加,事物的规模增长。看看渐近运行时分析:

https://en.wikipedia.org/wiki/Analysis_of_algorithms

答案 2 :(得分:7)

value的统计信息是什么? 在你的情况下,它很可能甚至是值。 很明显,对于这两种情况,算法O(n)O(n/2) + O(n/2)的复杂性几乎相同 - 线性时间

答案 3 :(得分:5)

这只是偶然的,它更快&#34;。您可能会注意到的是,您的值更常出现在偶数索引上,而不是奇数索引上。

答案 4 :(得分:3)

理论上,两种算法的时间复杂度相同O(n)。一个猜测为什么skipSearch在运行时更快的是你正在搜索的元素恰好位于偶数索引处,因此它将在第一个循环中找到,而在最坏的情况下,它将执行一半的迭代次数线性搜索。在这些基准测试中,您不仅需要考虑数据的大小,还需要考虑数据的外观。尝试搜索一个不存在的元素,一个存在于偶数索引的元素,一个存在于奇数索引处的元素。

此外,即使skipSearch使用适当的基准测试表现更好,它仍然只能缩短几纳秒,因此没有显着增加,并且在实践中不值得使用它。

答案 5 :(得分:3)

某人提到的问题之一是您为每种算法使用不同的索引。所以,为了解决这个问题,我重新编写了一些代码。这是我的代码:

int[] arr = new int[1000];
Random r = new Random();
for(int i = 0; i < arr.length; i++){
    arr[i] = r.nextInt();
}
int N = 1000000;
List<Integer> indices = new ArrayList<Integer>();
for(int i = 0; i < N; i++){
    //indices.add((int) Math.floor(Math.random()*arr.length/2)*2); //even only
    indices.add((int) Math.floor(Math.random()*arr.length/2)*2+1); //odd only
    //indices.add((int) Math.floor(Math.random()*arr.length)); //normal
}

long startTime = System.nanoTime();
for(Integer i : indices)
{
    search(arr, arr[i]);
}
System.out.println("Average Time: "+(System.nanoTime()-startTime)/(float)N+"ns");

startTime = System.nanoTime();
for(Integer i : indices)
{
    skipSearch(arr, arr[i]);
}
System.out.println("Average Skip Search Time: "+(System.nanoTime()-startTime)/(float)N+"ns");

所以你注意到我做了一个ArrayList<Integer>来保存索引,我提供了三种不同的方法来填充该数组列表 - 一个只有偶数,一个只有奇数,你的原始随机方法。

仅使用偶数运行会产生此输出:

  

平均时间:175.609ns

     

平均跳过搜索时间:100.64691ns

使用奇数运行只会产生此输出:

  

平均时间:178.05182ns

     

平均跳过搜索时间:263.82928ns

使用原始随机值运行会产生此输出:

  

平均时间:175.95944ns

     

平均跳过搜索时间:181.20367ns

这些结果中的每一个都是有意义的。

当仅选择偶数索引时,您的skipSearch算法为O(n / 2),因此我们处理不超过一半的索引。通常情况下,我们并不关心时间复杂度的常数因素,但如果我们真的关注运行时,那就很重要了。在这种情况下,我们确实将问题减少了一半,这样就会影响执行时间。而且我们认为实际执行时间几乎减少了一半。

当只选择奇数索引时,我们会看到对执行时间的影响更大。这是可以预料到的,因为我们正在处理不少于一半的指数。

当使用原始随机选择时,我们看到skipSearch表现更差(正如我们所料)。这是因为,平均而言,我们会有偶数个偶数指数和奇数指数。偶数将很快找到,但奇数将很慢找到。线性搜索将在早期找到索引3,而skipSearch在它找到索引3之前大致处理O(n / 2)个元素。

至于为什么你的原始代码给出了奇怪的结果,就我所关注的而言,它是悬而未决的。可能是伪随机数生成器略微偏向偶数,可能是由于优化,可能是由于分支预测器的疯狂。但通过为两种算法选择随机指数,它肯定不是比较苹果和苹果。其中一些可能仍会影响我的结果,但至少这两种算法现在试图找到相同的数字。

答案 6 :(得分:2)

两种算法都在做同样的事情,哪一种更快取决于地点,你正在寻找的价值在哪里放置 所以这是巧合,哪一个在特定情况下更快。

但是第一个是更好的编码风格。

答案 7 :(得分:0)

当人们将线性搜索称为“最快搜索”时,这是一个纯粹的学术陈述。它与基准测试无关,而是与搜索算法的Big O complexity无关。为了使此测量有用,Big O仅定义给定算法的最坏情况场景。

在现实世界中,数据并不总是符合最坏情况,因此不同数据集的基准会发生波动。在您的示例中,两种算法之间存在7ns的差异。但是,如果您的数据如下所示会发生什么:

linear_data = [..., value];
skip_search_data = [value, ...];

7ns的差异会变得更大。对于线性搜索,复杂度每次都是O(n)。对于跳过搜索,每次都是O(1)。

在现实世界中,“最快”的算法并不总是最快的。有时,您的数据集适用于不同的算法。