美文网首页iOS学习笔记
【译】Swift算法俱乐部-大O表示法

【译】Swift算法俱乐部-大O表示法

作者: Andy_Ron | 来源:发表于2018-09-16 19:35 被阅读29次

    本文是对 Swift Algorithm Club 翻译的一篇文章。
    Swift Algorithm Clubraywenderlich.com网站出品的用Swift实现算法和数据结构的开源项目,目前在GitHub上有18000+⭐️,我初略统计了一下,大概有一百左右个的算法和数据结构,基本上常见的都包含了,是iOSer学习算法和数据结构不错的资源。
    andyRon/swift-algorithm-club-cn是我对Swift Algorithm Club,边学习边翻译的项目。欢迎有兴趣学习算法和数据结构,有时间的小伙伴一起参与翻译,欢迎issue,或者直接提交pull request。

    知道某个算法的运行速度和占用的内存空间,对于选择正确的算法来解决问题非常有帮助。

    大O表示法 能让你对一个算法的运行时间和占用内存有个大概概念。当有人说,“这个算法在最糟情况下的运行时间是 O(n^2),而且占用了 O(n) 大小的空间”时,他的意思是这个算法有点慢,不过没占多大内存。

    要知道一个算法的大O 表示法通常要通过数学分析。在这里我们不会涉及具体的数学,不过知道不同的值意味着什么会很有用。所以这里有一张方便的表。n 在这里代表的意思是数据的个数。举个例子,当对一个有 100 个元素的数组进行排序时,n = 100

    Big-O表示符号 名字 描述
    O(1) 常数级 最好的。不论输入数据量有多大,这个算法的运行时间总是一样的。例子: 基于索引取出数组中对应的元素。
    O(log n) 对数级 相当好。这种算法每次循环时会把需要处理的数据量减半。如果你有 100 个元素,则只需要七步就可以找到答案。1000 个元素只要十步。100,0000 元素只要二十步。即便数据量很大这种算法也非常快。例子:二分查找。
    O(n) 线性级 还不错。如果你有 100 个元素,这种算法就要做 100 次工作。数据量翻倍那么运行时间也翻倍。例子:线性查找。
    O(n log n) 线性对数级 还可以。比线性级差了一些,不过也没那么差劲。例子:最快的通用排序算法。
    O(n^2) 二次方级 有点慢。如果你有 100 个元素,这种算法需要做 100^2 = 10000 次工作。数据量 x 2 会导致运行时间 x 4 (因为 2 的 2 次方等于 4)。例子:循环套循环的算法,比如插入排序。
    O(n^3) 三次方级 特别慢。如果你有 100 个元素,那么这种算法就要做 100^3 = 100,0000 次工作。数据量 x 2 会导致运行时间 x 8。例子:矩阵乘法。
    O(2^n) 指数级 超级慢。这种算法你要想方设法避免,但有时候你就是没得选。加一点点数据就会把运行时间成倍的加长。例子:旅行商问题。
    O(n!) 阶乘级 比蜗牛还慢!不管干什么都要跑个 N 年才能得到结果。

    以下是每种大O表示法的示例:

    O(1)

    O(1)复杂性的最常见示例是访问数组索引。

    let value = array[5]
    

    Another example of O(1) is pushing and popping from Stack.
    另外一个O(1)的例子是栈的推进和弹出。

    O(log n)

    var j = 1
    while j < n {
      // do constant time stuff
      j *= 2
    }
    

    不是简单地递增,'j'在每次运行中增加2倍。二分搜索算法是O(log n)复杂度的示例。

    O(n)

    for i in stride(from: 0, to: n, by: 1) {
      print(array[I])
    }
    

    数组遍历和线性搜索是O(n)复杂性的示例。

    O(n log n)

    for i in stride(from: 0, to: n, by: 1) {
    var j = 1
      while j < n {
        j *= 2
        // do constant time stuff
      }
    }
    

    for i in stride(from: 0, to: n, by: 1) {
      func index(after i: Int) -> Int? { // multiplies `i` by 2 until `i` >= `n`
        return i < n ? i * 2 : nil
      }
      for j in sequence(first: 1, next: index(after:)) {
        // do constant time stuff
      }
    }
    

    合并排序和堆排序是O(n log n)复杂度的示例。

    O(n^2)

    for i  in stride(from: 0, to: n, by: 1) {
      for j in stride(from: 1, to: n, by: 1) {
        // do constant time stuff
      }
    }
    

    遍历简单的二维数组和冒泡排序是O(n^2)复杂度的示例。

    O(n^3)

    for i in stride(from: 0, to: n, by: 1) {
      for j in stride(from: 1, to: n, by: 1) {
        for k in stride(from: 1, to: n, by: 1) {
          // do constant time stuff
        }
      }
    }
    

    O(2^n)

    具有运行时间O(2^N)的算法通常是递归算法,其通过递归地解决大小为N-1的两个较小问题来解决大小为N的问题。
    以下示例打印了解决著名的N盘“汉诺塔”问题所需的所有动作。

    func solveHanoi(n: Int, from: String, to: String, spare: String) {
      guard n >= 1 else { return }
      if n > 1 {
        solveHanoi(n: n - 1, from: from, to: spare, spare: to)
      } else {
        solveHanoi(n: n - 1, from: spare, to: to, spare: from)
      }
    }
    

    O(n!)

    下面给出了O(n!)的最简单的例子。

    func nFactFunc(n: Int) {
      for i in stride(from: 0, to: n, by: 1) {
        nFactFunc(n: n - 1)
      }
    }
    

    大部分情况下你用直觉就可以知道一个算法的大O 表示法,不需要使用数学。比如说,如果你的代码用一个循环遍历你输入的每个元素,那么这个算法就是 O(n)。如果是循环嵌套循环,那就是 O(n^2)。如果3个循环嵌套在一起就是 O(n^3),以此类推。

    注意,大O 表示法只是一种估算,当数据量大的时候才有用。举个例子,插入排序的最糟情况运行时间是 O(n^2)。 理论上来说它的运行时间比归并排序要慢一些,归并排序是 O(n log n)。但对于小数据量,插入排序实际上更快一些,特别是那些已经有一部分数据是排序好的数组。

    如果你看完没懂,也不要太纠结了。这种东西仅仅在比较两种算法哪种更好的时候才有点用。但归根结底,你还是要实际测试之后才能得出结论。而且如果数据量相对较小,哪怕算法比较慢,在实际使用也不会造成太大的问题。

    相关文章

      网友评论

        本文标题:【译】Swift算法俱乐部-大O表示法

        本文链接:https://www.haomeiwen.com/subject/vrdagftx.html