我听说没有比线性搜索更快的算法(对于未排序的数组),但是,当我运行这个算法时(线性):
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");
答案 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)
这就是为什么我们不关心字面上计算执行时间需要多长时间,以及随着输入复杂性的增加,事物的规模增长。看看渐近运行时分析:
答案 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)。
在现实世界中,“最快”的算法并不总是最快的。有时,您的数据集适用于不同的算法。