比 O(n) 更快地获取数组元素的索引

Posted

技术标签:

【中文标题】比 O(n) 更快地获取数组元素的索引【英文标题】:Get index of array element faster than O(n) 【发布时间】:2011-09-08 16:43:14 【问题描述】:

鉴于我有一个巨大的数组,并从中获得一个值。我想获取数组中值的索引。有没有其他方法,而不是打电话给Array#index 得到它?问题来自需要保持非常大的数组并调用Array#index 大量时间。

经过几次尝试后,我发现 缓存 通过使用 (value, index) 字段而不是值本身存储结构来在元素内部建立索引,从而在性能上迈出了一大步(20 倍的胜利)。

我仍然想知道是否有一种更方便的方法来查找 en 元素的索引而无需缓存(或者有一种很好的缓存技术可以提高性能)。

【问题讨论】:

【参考方案1】:

为什么不使用 index 或 rindex?

array = %w( a b c d e)
# get FIRST index of element searched
puts array.index('a')
# get LAST index of element searched
puts array.rindex('a')

索引:http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-index

rindex:http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-rindex

【讨论】:

这正是 OP 所说的他们不想要的,因为他们的数组很大。 Array#index 是 O(n) 并且多次这样做会影响性能。哈希查找是 O(1)。 @tim,嗯,我不记得在我回答的时候这是 same 问题,也许 OP 稍后修改了这个问题,这会使这个问题无效回答。 那不是说是在特定的时间编辑过的吗? 呵呵,没错。好吧,我和另外 30 个人当时正在阅读它。我猜:/【参考方案2】:

将数组转换为哈希。然后寻找钥匙。

array = ['a', 'b', 'c']
hash = Hash[array.map.with_index.to_a]    # => "a"=>0, "b"=>1, "c"=>2
hash['b'] # => 1

【讨论】:

如果数组很长,最快 根据您的用例,如果存在重复值,这可能会出现问题。上面描述的方法将返回等价或#rindex(值的最后出现)要获得#index等价结果,这意味着哈希返回值的第一个索引,您需要在创建之前按照反转数组的方式做一些事情哈希然后从初始数组的总长度中减去返回的索引值 - 1. # (array.length - 1 ) - hash['b'] 转换成哈希不是需要 O(n) 时间吗?我想如果它会被多次使用,那么哈希转换的性能会更高。但是对于单次使用,和遍历数组没有区别吗? 是的,如果它真的很重要,那么单次使用可能会更糟,因为哈希计算不会像比较那样快速短路。 如果有重复元素,@hololeap 的回答会更完整。【参考方案3】:

其他答案未考虑数组中多次列出的条目的可能性。这将返回一个散列,其中每个键是数组中的一个唯一对象,每个值是对应于对象所在位置的索引数组:

a = [1, 2, 3, 1, 2, 3, 4]
=> [1, 2, 3, 1, 2, 3, 4]

indices = a.each_with_index.inject(Hash.new  Array.new ) do |hash, (obj, i)| 
    hash[obj] += [i]
    hash
end
=>  1 => [0, 3], 2 => [1, 4], 3 => [2, 5], 4 => [6] 

这允许快速搜索重复条目:

indices.select  |k, v| v.size > 1 
=>  1 => [0, 3], 2 => [1, 4], 3 => [2, 5] 

【讨论】:

这是一个比@sawa 更完整的答案。【参考方案4】:

是否有充分的理由不使用哈希?数组的查找是 O(1)O(n)

【讨论】:

重点是——我在哈希上调用#keys,它返回一个我正在使用的数组。不过,我可能也会考虑我的架构......【参考方案5】:

如果您的数组具有自然顺序,请使用二进制搜索。

使用二分搜索。

二分搜索有O(log n) 访问时间。

这里是如何使用二分查找的步骤,

阵列的顺序是什么?例如,是否按名称排序? 使用bsearch 查找元素或索引

代码示例

# assume array is sorted by name!

array.bsearch  |each| "Jamie" <=> each.name  # returns element
(0..array.size).bsearch  |n| "Jamie" <=> array[n].name  # returns index

【讨论】:

【参考方案6】:

如果它是 排序 数组,您可以使用二进制搜索算法 (O(log n))。例如,使用此功能扩展 Array 类:

class Array
  def b_search(e, l = 0, u = length - 1)
    return if lower_index > upper_index

    midpoint_index = (lower_index + upper_index) / 2
    return midpoint_index if self[midpoint_index] == value

    if value < self[midpoint_index]
      b_search(value, lower_index, upper_index - 1)
    else
      b_search(value, lower_index + 1, upper_index)
    end
  end
end

【讨论】:

它实际上并不难阅读。第一部分,如果下限大于上限(递归已归档),则返回。第二部分通过将中点 m 与该点的值与 e 进行比较来检查我们是否需要左侧或右侧。如果我们没有我们想要的答案,我们会递归。 我认为它对人们投反对票而不是编辑的自我感觉更好。【参考方案7】:

结合@sawa 的回答和那里列出的评论,您可以在数组类上实现“快速”索引和 rindex。

class Array
  def quick_index el
    hash = Hash[self.map.with_index.to_a]
    hash[el]
  end

  def quick_rindex el
    hash = Hash[self.reverse.map.with_index.to_a]
    array.length - 1 - hash[el]
  end
end

【讨论】:

【参考方案8】:

我仍然想知道是否有一种更方便的方法来查找 en 元素的索引而无需缓存(或者有一种很好的缓存技术可以提高性能)。

你可以使用二分搜索(如果你的数组是有序的并且你存储在数组中的值在某种程度上是可比较的)。为此,您需要能够告诉二进制搜索它是应该寻找当前元素的“左侧”还是“右侧”。但我相信在插入时存储 index 然后在从同一个数组中获取元素时使用它并没有错。

【讨论】:

以上是关于比 O(n) 更快地获取数组元素的索引的主要内容,如果未能解决你的问题,请参考以下文章

数据结构之树状数组

获取数组中 n 个最小元素的索引

有啥方法可以比 for 循环更快地遍历数组吗?

哈希表 - 为啥它比数组快?

排序向量查找的更快版本 (MATLAB)

获取向量的最后 n 个元素。有没有比使用 length() 函数更好的方法?