冰与火之歌:「时间」与「空间」复杂度

算法(Algorithm)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,比如排序就有前面的十大经典排序和几种奇葩排序,虽然结果相同,但在过程中消耗的资源和时间却会有很大的区别,比如快速排序与猴子排序:)。

那么我们应该如何去衡量不同算法之间的优劣呢?

主要还是从算法所占用的「时间」和「空间」两个维度去考量。

时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。

空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。

冰之哀伤:时间复杂度

时间的流逝宛若寒冰的融化,散发着恐惧。

大O符号表示法

大O表示法:算法的时间复杂度通常用大O符号表述,定义为 **T[n] = O(f(n)) **。称函数T(n)以f(n)为界或者称T(n)受限于f(n)。

如果一个问题的规模是n,解这一问题的某一算法所需要的时间为T(n)。T(n)称为这一算法的“时间复杂度”。

上面公式中用到的 Landau符号是由德国数论学家保罗·巴赫曼(Paul Bachmann)在其1892年的著作《解析数论》首先引入,由另一位德国数论学家艾德蒙·朗道(Edmund Landau)推广。Landau符号的作用在于用简单的函数来描述复杂函数行为,给出一个上或下(确)界。在计算算法复杂度时一般只用到大O符号,Landau符号体系中的小o符号、Θ符号等等比较不常用。这里的O,最初是用大写希腊字母,但现在都用大写英语字母O;小o符号也是用小写英语字母o,Θ符号则维持大写希腊字母Θ。

大O符号是一种算法「复杂度」的「相对」「表示」方式。

这个句子里有一些重要而严谨的用词:

相对(relative):你只能比较相同的事物。你不能把一个做算数乘法的算法和排序整数列表的算法进行比较。但是,比较2个算法所做的算术操作(一个做乘法,一个做加法)将会告诉你一些有意义的东西;

表示(representation):大O(用它最简单的形式)把算法间的比较简化为了一个单一变量。这个变量的选择基于观察或假设。例如,排序算法之间的对比通常是基于比较操作(比较2个结点来决定这2个结点的相对顺序)。这里面就假设了比较操作的计算开销很大。但是,如果比较操作的计算开销不大,而交换操作的计算开销很大,又会怎么样呢?这就改变了先前的比较方式;

复杂度(complexity):如果排序10,000个元素花费了我1秒,那么排序1百万个元素会花多少时间?在这个例子里,复杂度就是相对其他东西的度量结果。

常见的时间复杂度量级

我们先从常见的时间复杂度量级进行大O的理解:

常数阶O(1)

线性阶O(n)

平方阶O(n²)

对数阶O(logn)

线性对数阶O(nlogn)

O(1)

无论代码执行了多少行,其他区域不会影响到操作,这个代码的时间复杂度都是O(1)

1void swapTwoInts(int &a, int &b){
2  int temp = a;
3  a = b;
4  b = temp;
5}
O(n)

在下面这段代码,for循环里面的代码会执行 n 遍,因此它消耗的时间是随着 n 的变化而变化的,因此可以用O(n)来表示它的时间复杂度。

1int sum int n ){
2   int ret = 0;
3   for ( int i = 0 ; i <= n ; i ++){
4      ret += i;
5   }
6   return ret;
7}

特别一提的是 c * O(n) 中的 c 可能小于 1 ,比如下面这段代码:

1void reverse string &s ) {
2    int n = s.size();
3    for (int i = 0 ; i < n/2 ; i++){
4      swap ( s[i] , s[n-1-i]);
5    }
6}
O(n²)

冰与火之歌:「时间」与「空间」复杂度


当存在双重循环的时候,即把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyszyx.html