Algorithm Performance Analysis


定性研究与定量研究

定量研究与定性研究是社会科学领域两种对立的研究范式,两者在研究目标、对象及方法上都存在着明显的区别。

首先,研究目标上,定量研究重视预测控制,而定性研究重视对意义的理解;

其次,研究对象上,定量研究强调事实的客观实在性,而定性研究强调对象的主观意向性;

第三,研究方法上,定量研究注重经验证实,而定性研究注重解释建构。

由于方法论上的不同取向,导致了在实际应用中定量研究与定性研究存在明显的差别。这主要体现在如下几个方面:

  • 着眼点不同

定量研究着重事物量的方面;定性研究着重事物质的方面。

  • 在研究中所处的层次不同

定量研究是为了更准确地定性。

  • 依据不同

定量研究依据的主要是调查得到的现实资料数据,定性研究的依据则是大量历史事实和生活经验材料。

  • 手段不同

定量研究主要运用经验测量、统计分析和建立模型等方法;定性研究则主要运用逻辑推理、历史比较等方法。

  • 学科基础不同

定量研究是以概率论、社会统计学等为基础,而定性研究则以逻辑学、历史学为基础。

  • 结论表述形式不同

定量研究主要以数据、模式、图形等来表达;定性研究结论多以文字描述为主。定性研究是定量研究的基础,是它的指南,但只有同时运用定量研究,才能在精确定量的根据下准确定性。

如何评估算法性能

如何评估一个算法的性能?什么是大 O 表示法

评估算法性能:

  • 主要评估问题的输入规模 n 和元素的访问次数 f(n) 的关系
  • f(n) = n
  • f(n) = n2 或 f(n) = 2n2 + n + 5

大 O 符号:忽略非主体部分,如常数项、低阶项

  • O(g(n)) = { f(n) :存在正常数 c 和 n0 ,使对所有 n >= n0 ,有 0 <= f(n) <= cg(n) }
  • O(g(n)),表示这个算法是有一个 渐近上界 的,这个渐近上界为 g(n),算法的运行时间 f(n) 趋近并小于等于这个 g(n)

知道算法的时间复杂度之后,我们可以计算每秒能够处理的数据规模。

  • n! 的弱上界是 n ^ n,因此增长速度非常快,这意味着单位时间内可求解的问题很小,换言之,超慢
  • 2^n 这样的指数函数增长非常快,这种算法可以认为超慢
  • O(n2) 和 O(n3) 增长很快,算法很慢,至少优化到 nlgn,O(n2) 的有冒泡排序、直接插入排序、选择排序
  • nlgn 可以认为是及格的算法,一般分治法可以缩小层数为 lgn,而每层的复杂度一般为 O(n),例如归并排序算法、快速排序算法
  • O(n) 叫做线性算法,这种算法比较优秀,或者问题本身比较简单,比如连续求和最大子数组的线性解
  • O(sqrt(n)) 比 O(n) 更快,但是这种算法很少
  • lgn 就是很优秀的算法了,比如二分查找法,但是这种算法往往对输入数据的格式是有要求的,二分查找要求输入数据有序
  • 还有一种是常量,无论规模怎么扩大,都花固定时间,这是为数极少的效率最高的算法了,多数是数据很规则

2 的幂表

2 的幂 准确值(X) 近似值 X 字节转换为 MB、GB 等
7 128
8 256
10 1024 一千 1K
16 65 536 64KB
20 1 048 576 一百万 1MB
30 1 073 741 824 十亿 1GB
32 4 294 967 296 4GB
40 1 099 511 627 776 一万亿 1TB

有了这张表就可以做速算:例如,一个将每个 32 位整数映射为布尔值的散列表可以一台普通的计算机的内存(4GB)填满。

经典算法分析:n 与 lgn

  • 顺序查找:O(n)
  • 二分查找:O(lgn)

经典算法分析:n2 与 nlgn

  • 冒泡、插入、选择排序
  • Array.sort()

三种典型递归形式算法性能分析

看两个问题:

  1. 子问题的规模下降
  2. 子问题求解答案消耗的时间

先看基础的:

  • 阶层
    • 递推公式:f(n) = n * f(n - 1)
      • 子问题规模下降:n -> n * (n - 1) 忽略常数得 T(n - 1)
      • 处理子问题:O(1)
      • 总结:T(n) = T(n - 1) + O(1) 下降一次消耗的时间是线性时间
      • 共要下降 n 次,F(n) = n * T(n) = O(n)
      • 总结:阶层递归的时间复杂度是 O(n)

再看复杂的:

  • 汉诺塔递归形式算法分析
    • 和斐波那契类似都是 O(2n)
  • 斐波那契数列递归形式算法分析
    • 递推公式:f(n) = f(n - 1) + f(n - 2)
    • 子问题下降规模:n - > (n -1) 和 n -> (n - 2) 两者用的时间是一样,可以合并:n -> 2(n - 1)
    • 处理子问题:O(1)
    • 一次子问题规模下降需要的时间:T(n) = 2T(n - 1) + O(1)
    • 斐波那契数列递归形式比较特殊,规模下降是成等比数列:2、4、8、16 …… 即 2n
    • 总结斐波那契数列递归的时间复杂度:F(n) = 2n * T(n) = O(2n)
  • 最大公约数算法分析
    • 辗转相除法
    • 等价替换:f(m, n) = f(n, m % n)
    • 子问题规模下降:(m, n) -> (n, m%n),这里利用了一个数学上的知识点:
      • m -> n
      • n - > m % n
      • ….

推导:前置条件 m > n

次数 m n 数学性质
1 n m%n m%n < n/2
2 m%n n%(m%n) n%(m%n) < (m%n)/2 < n/2
3 n%(m%n) (m%n)%(n%(m%n)) (m%n)%(n%(m%n)) < n%(m%n)/2 < n/4
4 … < n/4
5
6 …<n/8

我们可以从定性的角度去研究这个问题,可以推出每下降两次,n 就会减半一次。

次数 x 和结果的关系: 结果 = n/2x/2

我们知道递归的出口是 n = 1,那么当 n 降低 1 时需要多少次呢?

1 = n/2x/2

两边同时取对数最后化简可得近似值 x = 2lgn

所以递归调用 2lgn 次(小技巧:这里的 2 是因为每隔两次下降一半),递归就可以结束了

注意:正常情况下 lg 是以 10 为底,log 需要指定底数,但是在计算机学界,2 作为底数比较常见,所以这里的 lg 指的是以 2 为底,同样 logn 也是以 2 为底数

综上:最大公约数辗转相除法的时间复杂度可以表示为 O(lgn)

评估递归算法的时间复杂度

递归关系 结果 举例
T(n) = T(n/2) + O(1) T(n) = O(logn) 二分查找,欧几里得 GCD
T(n) = T(n - 1) + O(1) T(n) = O(n) 线性查找
T(n) = 2T(n/2) + O(1) (特例 T(n) = O(n)
T(n) = 2T(n/2) + O(n) T(n) = O(nlogn) 归并、快排
T(n) = 2T(n/2) + O(nlogn) T(n) = O(nlog2n)
T(n) = T(n - 1) + O(n) T(n) = O(n2) 选择排序、插入排序
T(n) = 2T(n - 1) + O(1) T(n) = O(2^n) 汉诺塔
T(n) = T(n - 1) + T(n - 2) + O(1) T(n) = O(2^n) 递归的斐波那契

希尔排序的性能分析

  • 如果原始数据的大部分元素已经排序,那么插入排序的速度很快(因为需要移动的元素很少)
  • 为什么 “快”?
    • 无序的时候,元素少
    • 元素多的时候,已经基本有序

原理:在增量不断缩小的过程中,增量产生的子序列的个数越来越少,子序列中元素的数量越来越多,但是元素越来越有序

刚开始:子序列多,但是子序列中元素的数量少

后来:子序列少,但是子序列中元素的数量多

  • 最好与最坏

由于希尔排序的特点,我们不太好进行评估算法性能,这时我们可以从最好和最坏两个角度去看

排序算法的稳定性

  • 稳定:如果 a 原本在 b 的前面,而 a = b,排序之后 a 仍然在 b 的前面
  • 不稳定:如果 a 原本在 b 的前面,而 a = b,排序之后 a 可能会出现在 b 的后面


Author: NaiveKyo
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source NaiveKyo !
  TOC