【问题标题】:Does array.count and array[0 ...< index] slow down a binary search?array.count 和 array[0 ...< index] 会减慢二进制搜索的速度吗?
【发布时间】:2020-05-24 01:03:52
【问题描述】:

今天我做了一份作业的测试,并被要求通过一系列整数搜索,这是问题:

这个练习的目的是检查一个数字在一个 数组。

规格:

项目是按升序顺序排列的整数。

数组最多可以包含 100 万个项目

实现函数existsInArray(_ numbers: [Int], _ k: Int) 所以 它返回 true 如果 k 属于 numbers ,否则该函数 应该返回 false

例子:

let numbers = [-9, 14, 37, 102]
existsInArray(numbers, 102) // returns true
existsInArray(numbers, 36) //returns false

注意:尽量节省 CPU 周期

好的,所以我给出了我的答案,即下面的代码并等待结果

func existsInArray(_ numbers: [Int], _ k: Int) -> Bool {
    if numbers.isEmpty {
        return false
    }
    let numbersHalfIndex: Int = (numbers.count/2)
    if k == numbers[numbersHalfIndex] {
        return true
    } else if k != numbers[0] && numbers.count == 1 {
        return false
    } else if k <= numbers[numbersHalfIndex] {
        let leftHalfNumbersArray = numbers[0 ..< numbersHalfIndex]
        return existsInArray(Array(leftHalfNumbersArray), k)
    } else if k > numbers[numbersHalfIndex] {
        let rightHalfNumbersArray = numbers[numbersHalfIndex ..< numbers.count]
        return existsInArray(Array(rightHalfNumbersArray), k)
    } else {
        return false
    }
}

事实证明 “解决方案在合理的时间内无法处理 100 万个项目” 现在我不知道我做错了什么,因为二进制搜索速度快到 f*ck .

我唯一的猜测是可能 number.countnumbers[0 ... 或 numbers[numbersHalfIndex ...使一切都比预期慢。

我是绊倒了还是怎么了?

编辑: 如果有人好奇,我测试了我的代码和 Martin R 代码,看看使用 ArraySlice 对时间的影响有多大。 从0开始,我使用一系列100.000.000 Itens的升序。 我是这样记录时间的:

print("////////// MINE //////////")
var startTime = CFAbsoluteTimeGetCurrent()
print(existsInArray(numbers, 0))
var timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print("Time elapsed for mine: \(timeElapsed) s.")

print("////////// Martin R //////////")
counter = 0
startTime = CFAbsoluteTimeGetCurrent()
print(existsInArrayOptimal(numbers, 0))
timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print("Time elapsed for Martin R: \(timeElapsed) s.")

结果如下:

/////////////////////////////

是的

我的时间过去了:

1.2008800506591797秒。

//////////马丁R //////////

是的

Martin R的时间:0.00012993812561035156 s。

大约快 1000 倍!

【问题讨论】:

  • 你是在操场上测试吗?您是否尝试过使用endIndex而不是Count? span>
  • 顺便说一句,您正在使用其切片初始化一个新数组
  • 使用Array(...) 将切片转换回数​​组是您在其中做的最慢的事情。如果您在切片上的所有工作 span>

标签: arrays swift algorithm testing binary-search


【解决方案1】:

访问number.count 不是问题,因为这是对数组的 O(1) 操作。使用numbers[0 ...&lt; numbersHalfIndex] 切片也不是问题。但是Array(leftHalfNumbersArray) 从切片创建一个新数组,并复制所有元素。

有两种可能的方法来避免这种情况:

  • 更新数组索引(用于当前搜索范围的下限和上限),而不是创建向下递归传递的数组。
  • 向下递归传递数组切片。切片与原始数组共享元素(只要它们没有发生变异)。

第二种方法的演示:

func existsInArray(_ numbers: ArraySlice<Int>, _ k: Int) -> Bool {
    if numbers.isEmpty {
        return false
    }
    let numbersHalfIndex = numbers.startIndex + numbers.count / 2
    if k == numbers[numbersHalfIndex] {
        return true
    } else if k < numbers[numbersHalfIndex] {
        return existsInArray(numbers[..<numbersHalfIndex], k)
    } else {
        return existsInArray(numbers[(numbersHalfIndex + 1)...], k)
    }
}

请注意,数组切片与原始数组共享它们的索引,因此索引不一定从零开始。这就是为什么numbers.startIndex 用于索引计算。

还有一个接受“真实”数组参数的包装函数:

func existsInArray(_ numbers: [Int], _ k: Int) -> Bool {
    return existsInArray(numbers[...], k)
}

正如@Leo 所建议的,您可以将其实现为一个集合方法,而不是实现两个单独的方法。集合索引不一定是整数,但对于RandomAccessCollection,索引计算保证为 O(1)。您还可以将其推广到任意可比较元素的集合而不是整数。

这是一个可能的实现:

extension RandomAccessCollection where Element: Comparable {
    /// Returns a Boolean value indicating whether the collection contains the
    /// given element. It is assumed that the collection elements are sorted
    /// in ascending (non-decreasing) order. 
    ///
    /// - Parameter element: The element to find in the collection.
    /// - Returns: `true` if the element was found in the collection; otherwise,
    ///   `false`.
    ///
    /// - Complexity: O(log(*n*)), where *n* is the size of the collection.
    func binarySearch(for element: Element) -> Bool {
        if isEmpty {
            return false
        }
        let midIndex = index(startIndex, offsetBy: count / 2)
        if element == self[midIndex] {
            return true
        } else if element < self[midIndex] {
            return self[..<midIndex].binarySearch(for: element)
        } else {
            return self[index(after: midIndex)...].binarySearch(for: element)
        }
    }
}

用法:

let numbers = [-9, 14, 37, 102]
print(numbers.binarySearch(for: 102)) // true
print(numbers.binarySearch(for: 36))  // false

另外一种更新搜索范围索引的非递归方法:

extension RandomAccessCollection where Element: Comparable {
    func binarySearch(for element: Element) -> Bool {
        var lo = startIndex
        var hi = endIndex

        while lo < hi {
            let mid = index(lo, offsetBy: distance(from: lo, to: hi) / 2)
            if element == self[mid] {
                return true
            } else if element < self[mid] {
                hi = mid
            } else {
                lo = index(after: mid)
            }
        }
        return false
    }
}

【讨论】:

  • 是的,就是这样。事实证明,创建一个新数组的速度慢到足以触发一个错误的答案。
  • @DouglasPfeifer:那么您将定义一个func existsInArray(_ numbers: [Int], lowIndex: Int, highIndex: Int _ k: Int) -&gt; Bool 函数。或者没有递归,类似于stackoverflow.com/a/26679191/1187415
  • @LeoDabus:谢谢你的建议,你当然是对的。 (我的目的主要是解释问题,而不是提供最通用的二分查找方法。)
  • @LeoDabus:这正是我在想的 :)
  • 在非递归版本中,slicing 不是更高效吗? func binarySearch(for element: Element) -&gt; Bool { var slice : SubSequence = self[...] while !slice.isEmpty { let mid = slice.index(slice.startIndex, offsetBy: slice.count / 2) if element == slice[mid] { return true } else if element &lt; slice[mid] { slice = slice[index(after: mid)...] } else { slice = slice[..&lt;mid] } } return false }
猜你喜欢
  • 2011-01-09
  • 1970-01-01
  • 1970-01-01
  • 2011-02-23
  • 2014-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多