一个算法的优劣主要取决于执行时间和所占用的存储空间,即时间复杂度T(n)和空间复杂度S(n)。
时间复杂度即通常所说的算法执行所需要耗费的时间,时间越短,算法越好。它反映了程序执行时间随输入规模增长而增长的量级,在很大程度上能很好地反映出算法的优劣与否。但是,一个算法的执行时间往往无法精确估计。通常需要在实际的计算机运行才知道具体的执行时间。但是也可以大致进行估计,得到算法的时间复杂度。算法的执行时间往往和算法代码中语句执行的数量有关。由于一段代码中,每条语句的执行都需要时间,因此,可以这么认为,代码执行次数越多,程序耗费的时间越长,效率越差。因此,我们需要多写一些短小精悍的代码来提高代码的执行效率,这就是能区别一个程序员功底如何的所在之处。
常数阶O(1), 对数阶O(log2 n), 线性阶O(n), 线性对数阶O(n log2 n), 平方阶O(n^2), 立方阶O(n^3) k次方阶O(n^K), 指数阶O(2^n)。 随着n的不断增大,时间复杂度不断增大,算法花费时间越多。
实例1
// 计算Func1的时间复杂度? void Func1(int N) { int count = 0; for (int k = 0; k < 2 * N ; ++ k) { ++count; } int M = 10; while (M--) { ++count; } printf("%d\n", count); }实例2
// 计算Func2的时间复杂度? void Func2(int N, int M) { int count = 0; for (int k = 0; k < M; ++ k) { ++count; } for (int k = 0; k < N ; ++ k) { ++count; } printf("%d\n", count); }实例三
// 计算Func3的时间复杂度? void Func3(int N) { int count = 0; for (int k = 0; k < 100; ++ k) { ++count; } printf("%d\n", count); }实例四
// 计算斐波那契递归Fibonacci的时间复杂度? long long Fibonacci(size_t N) { return N < 2 ? N : Fibonacci(N-1)+Fibonacci(N-2); } 分析: 1.实例1基本操作执行了2N+10次,通过推导大O阶方法知道,时间复杂度为 O(N) 2. 实例2基本操作执行了M+N次,有两个未知数M和N,时间复杂度为 O(N+M) 3. 实例3基本操作执行了10次,通过推导大O阶方法,时间复杂度为 O(1) 4. 实例4通过计算分析发现基本操作递归了2^N次,时间复杂度为 O(2^N)。(建议画图递归栈帧的二叉树理解)(1)最坏情况下的时间复杂度称最坏时间复杂度 一般不特别说明,讨论的时间复杂度均是最坏情况下的时间复杂度。 这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的上界,这就保证了算法的运行时间不会比任何更长。
在最坏情况下的时间复杂度为T(n)=0(n²),它表示对于任何输入实例,该算法的运行时间不可能大于0(n²)。 平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,算法的期望运行时间。
指数阶0(2n),显然,时间复杂度为指数阶0(2n)的算法效率极低,当n值稍大时就无法应用。对于时间复杂度的分析,一般是这两种方法:
(2)最坏时间复杂度
最坏情况运行时间 通常,除非特别指定,我们提到的运行时间都是最坏情况的运行时间
对于追问为什么是最坏时间复杂度的好奇宝宝: 如果最差情况下的复杂度符合我们的要求,我们就可以保证所有的情况下都不会有问题。 也许你觉得平均情况下的复杂度更吸引你(见下),但是:第一,难计算第二,有很多算法的平均情况和最差情况的复杂度是一样的. 第三,而且输入数据的分布函数很可能是你没法知道。
(2)平均时间复杂度
平均时间复杂度也是从概率的角度看,更能反映大多数情况下算法的表现。当然,实际中不可能将所有可能的输入都运行一遍,因此平均情况通常指的是一种数学期望值,而计算数学期望值则需要对输入的分布情况进行假设。平均运行时间很难通过分析得到,一般都是通过运行一定数量的实验数据后估算出来的。
实例1
// 计算阶乘递归Factorial的时间复杂度? long long Factorial(size_t N) { return N < 2 ? N : Factorial(N-1)*N; }实例2
// 计算BubbleSort的空间复杂度? void BubbleSort(int* a, int n) { assert(a); for (size_t end = n; end > 0; --end) { int exchange = 0; for (size_t i = 1; i < end; ++i) { if (a[i-1] > a[i]) { Swap(&a[i-1], &a[i]); exchange = 1; } } if (exchange == 0) break; } }一些常见排序算法的复杂度与稳定性: