在未排序的数组中查找元素的最快方法

41

今天我偶然看到了这个问题,尝试找到一个比O(N)更好的解决方案,但却没有想出来。

在SO上搜索了一下,但没有找到这个问题。

有没有比O(n)更好的解决方案,或者说这是一个不能比O(n)更好地解决的问题?

我的第一个想法是二分查找,但是为了使用它,需要对其进行排序,这又是>n的复杂度。我还考虑过只对可能包含搜索元素的数组的一半应用快速排序,但是我们最初要进行n次比较,之后才将另一半丢弃。我是正确理解问题,还是我的解决方向有误?

我正在尝试在c++中找到一个解决方案,而不是使用javascript的IndexOf()、C#的Array.find()或LINQ。


如果没有排序,我认为你无法做得比O(n)更好。 - Mysticial
从数组的两端开始搜索,如果元素不存在,则在中间相遇。这适用于固定大小的数组或循环链表。 - user6001430
@user6001430 比较的双倍时间 - Eduardo Sebastian
14个回答

34

并行化处理。将数组分成多个块,并进行并行搜索。虽然复杂度仍为O(n),但运行时间会大大缩短。实际上,它与处理器数量成正比。

在C++中,您可以使用并行模式库


1
我敢打赌,在你获得任何显著加速之前,它将成为内存限制的问题。 - Mysticial
在这种情况下,您可以将搜索分布到集群上,并在文件足够大时将其分成块。 - Muhammad Hasan Khan
1
是的,我也考虑过将数组分解并尝试使用线程处理它们,但这不是解决这个问题的算法视角。这又是实现特定的,就像IndexOf()或find()一样。 - Ajai
@MuhammadHasanKhan 这与将涉及的 CPU 核心数量成比例的情况不符。请参阅阿姆达尔定律 - https://en.wikipedia.org/wiki/Amdahl%27s_law - nickolay

11

您说得对,最快的方法是简单地遍历数组并查找它。没有更多信息的情况下,您无法做得更好。

除非您拥有一个量子计算机


9
我希望你不会用C++编程量子计算机。 - Foo Bah
如果你要考虑并行性,那么是的,你可以做得比O(n)更好。 :) - Mysticial
1
除非您拥有与 n 相当数量的处理器(即可数无限多的处理器),否则它不会改变渐近时间。 - user684934
2
有时候我在想,是否有人真正关注过 O 到底意味着什么。-_- - ELLIOTTCABLE

6
如果您只想查找一个元素,请遍历它。没有更快的方式来获取它。
如果您需要多次搜索,则值得对其进行索引(或排序),以使以下搜索变得快速(log(n))。

是的...你说得对...但找到一种只需要做一次的方法可能会很惊人,甚至可以使用n次,而不必在索引数字上使用二进制搜索。 - Ajai
2
这是不可能的。不是像“你不可能跳到3米高空那样的不可能”,因为那可以通过大量使用类固醇和有弹性的地面来实现,而是像1 + 1不可能等于3那样的不可能。 - user684934
哈哈...!同意。我只是发布了这个问题,想知道是否只有我遇到了这个问题,还是还有其他人? :P - Ajai
这应该是被接受的答案。"如果你需要进行多次搜索"是决定性因素。 - HalfWebDev

3
如果没有排序,您需要检查每个元素。

3
通常情况下,我们在每次迭代中检查数组的一个元素...这需要n次迭代才能完全循环遍历数组... 因此,最坏情况时间复杂度变为O(n)。
for(int i=0;i<s;i++){   // s = array size
    if(arr[i] == n)     // n = element to be searched
        return i;
}

但我实验的是在单个迭代中检查多个元素,例如每次5个元素。因此,在这种情况下,for循环将如下所示:

// s = array size
// n = element to be searched
for(int i=0;i<s;i+=5){  // notice the increment in i here...
    if(arr[i] == n)   
        return i;
    
/* check the next four indexes as well as if arr[i] is the last element of the array */ 
    else if( arr[i+1] == n && i+1 < s)
        return i+1;
    else if(arr[i+2] == n && i+2 < s)
        return i+2;
    else if(arr[i+3] == n && i+3 < s)
        return i+3;
    else if(arr[i+4] == n && i+4 < s)
        return i+4;
}

理论上,时间复杂度应该变为 O(n/5)…

但是,当我使用大小为1000000的数组进行测试,其中元素1到1000000随机排列,并计算两个循环对于相同数组大小的不同测试用例所需的时间……结果如下:

每次迭代一个元素

  1. 时间复杂度(以微秒为单位):4105 4180 4108 4115 4087 4137 4094 4089 4141 4167 4082 4084 4114 4118 4099

每次迭代五个元素

  1. 时间复杂度(以微秒为单位):1318 1382 1384 1297 1364 1289 1351 1617 1300 1289 1395 1385 1349 1329 1369

因此,我发现这确实对时间复杂度产生了显着影响!


1
O(n/5) 实际上是 O(n)。大 O 表示法在变量趋近无穷时测量复杂度。5 除以无穷仍然是无穷。虽然执行时间非常重要且可变,正如你所指出的那样,但它是另一回事。 - Kriil
听起来像是“在调试模式下进行基准测试”的典型案例。 - slowvomit

1
如果您不进行并行搜索,则可以将键插入到数组末尾作为哨兵值,并且只需进行'n'次比较而不是2n次比较即可进行搜索。
有关更多详细信息,请参阅以下问题: 使用哨兵进行线性搜索有什么意义?

0

使用这种方法可以在O(1)时间内搜索元素。

只需创建一个MAP。当您插入一个值时,为该键分配值'1',再次搜索它时,只需检查该数组是否存在即可。

以下是代码:

#include<bits/stdc++.h>

using namespace std;

int main(){
    int n;
    cin>>n;
    map<int,int> map;
    for(int i=0;i<n;i++){
        int k;
        cin>>k;
        map[k]=1;
    }
    int num;
    cin>>num;

    if(map[num]){
        cout<<"FOUND"<<endl;
    }else{
        cout<<"NOT FOUND"<<endl;
    }

    return 0;
}



Input: 
5    // *no. of elements*
6 4 7 3 2  //*elements* 
3    // *number to find*

输出:找到了


1
这假设你有额外的内存。通常不适用于大型数组。除非搜索次数足够多,否则这通常不是一个好的交换。 - 0xc0de
如果你正在数组中搜索数字,那么你可以应用这个。 - Yadvendra Kumar
根据 C++ 中 map 的文档:搜索、删除和插入操作具有对数复杂度,因此不是 O(1)。 - Kriil

0

只需遍历一次数组,检查有多少个数字小于该特定数字。


如果可能的话,请添加一些代码。 - Nitin

0

0

这个问题可以通过使用一些技巧来解决。在未排序的数组中,如果我们遍历它,最坏情况下(当元素位于最后一个索引时)的复杂度将为O(N),其中N是数组的大小。所以,这里有一个技巧。首先检查最后一个索引,这样如果元素位于最后一个索引(最坏情况),我们的代码将在O(1)中执行。然后再执行遍历和查找元素的代码。因此,现在最坏情况下的复杂度将为O(N-1)。

int findElement(int N, int arr[], int element){
  if(arr[N]==element){
    return i;
  }
  for(int i=0; i<N-1; i++){
    if(arr[i]==element)
      return i;
  }
  return -1;
}

这仍然是O(n)。如果数字在n-1处,那么你将会查看n个数字。 - Kriil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接