03 04|复杂度分析

直接实验得到算法时间的事后统计法以及其缺点:

事后统计法:运行代码,通过统计,监控,得到算法执行的时间和占用的内存。

事后统计法的局限性:

1、测试结果依赖于测试环境。

  • I9 cpu 和I3 cpu运算速度的不同,导致代码执行的快慢也不同。

2、测试结果受到数据规模的影响很大。

  • 对于同一个排序算法,待排序数据的有序度不一样,对于执行时间就有很大的区别;测试数据的规模大小,也会影响算法的性能,小规模的数据培训,插入可能反而比快速排序要快。

 

所以我们需要一个不用具体的测试数据来测试,就可以粗略地估计算法的执行效率的方法。这就是我们今天要讲的时间、空间复杂度分析方法。

大O复杂度表示法:从cpu角度来看,每行代码都执行类似的操作:读数据-运算-写数据,尽管每行代码对应cpu执行个数,时间都不一样,但是做粗略运算,每行代码时间近似看做相等,计作unit_time。

例子:求 1,2,3…n 的累加和

int cal(int n ){
    int sum = 0;   //unit_time
    int i = 1;    //unit_time
    int j = 1;     //一个unit_time
    for(;i<=n;i++)     //n个unit_time
    {
        j =1 ;             //n个unit_time
            for(;j<=n;j++)          //n个 n个unit_time
            {
                sum = sum + i * j ;      // n个  n个unit_time
            }
    }
}

总的时间复杂度  T(n) =  (2n² + 2n + 3) * unit_time。类似于高数求极限 所以T(n) = O(n²)。

时间复杂度分析的三个实用的方法:

1、只关心循环次数最多的一段代码      //类似于高数求极限 当n->∞,求T(n)

2、加法法则:总时间复杂度等于量级最大的那段复杂度。

03 04|复杂度分析_第1张图片

代码分为三个部分,sum_1 ,sum_2,sum_3,第一段代码虽然执行了100次,但是仍然是常量执行时间,与n的规模无关。第二段第三段为O(n)和O(n²)  。还是求高数中极限的问题 。当n->∞,lim T(n)  = O(n²)

3、乘法法则:嵌套代码的复杂度等于内外代码复杂度的乘积。

 

几种常见时间复杂度分析:

03 04|复杂度分析_第2张图片

对于复杂度量级可以粗略分为两类:多项式量级和非多项式量级。非多项式用绿色波浪标注。把时间复杂度为非多项式量级的算法称作NP(Non-Deterministic polynomial)非确定多项式问题。当数据规模 n 越来越大时,非多项式量级算法的执行时间会急剧增加,求解时间会无限增长。

 

常见的多项式量级:

1、O(1):代码执行时间不随着n的增大而增大,都称作常数量级的时间复杂度。

2、O(logn)、O(nlogn)

int i = 1;
while(i <= n)
{
    i = i *2 ;
}

变量i的值从1开始取,每循环一次乘以2,取值过程为一个等比数列:

当第x次,恰好大于n,所以x = log2n,时间复杂度O(log2n)

但是实际上,不管是以几为底( i = i * 3 ;//以三为底, i = i * 10 ;//以十为底),都可以把所有对数阶时间复杂度记作O(logn)

因为对数之间是可以相互转换的,log3n等于log3 2 * log 2 n 所以相差一个常量 C = log 3 2 ,可以忽略。所以在对数阶时间复杂度表达里面,忽略对数的“底”,统一记作O(log n),所谓O(nlogn) 就是把时间复杂度O(logn)的代码执行n次,如归并排序和快速排序。

3、O(m+n) 、 O(m * n)

int cal(int m, int n) {
    int sum_1 = 0;
    int i = 1;
    for (; i < m; ++i) {
        sum_1 = sum_1 + i;
    }
    int sum_2 = 0;
    int j = 1;
    for (; j < n; ++j) {
        sum_2 = sum_2 + j;
    }
    return sum_1 + sum_2;
}

m和n是两个数据规模,无法事先评估m和n的大小,所以上述代码时间复杂度O(m + n)。

 

时间复杂度全称:渐进时间复杂度,表示算法的执行时间数据规模之间的增长关系。

空间复杂度全称:渐进空间复杂度,表示算法的存储空间数据规模之间的增长关系。

03 04|复杂度分析_第3张图片

课后问答:我们上算法课,老师讲到存储一个二进制数,输入规模(空间复杂度)是O(logn) bit。请问如何理解?

回答:比如数据规模为8,8个数字,用2进制的表示的话,只用3位就可以表示出0~7八个数字;数据规模为16,用2进制表示的话,只用4位就可以表示出来0~15个数字。

 

四个复杂度分析方面的知识点:

  • 最好时间复杂度(best case time complexity)
  • 最坏情况时间复杂度(worst case time complexity)
  • 平均情况时间复杂度(average case time complexity)
  • 均摊时间复杂度(amortized time complexity)

best case time complexity:O(1)            worst case time complexity:O(n)

情形:在长度为n的数组中查找某个数。

int find(int[] array, int n, int x) {
  int i = 0;
  int pos = -1;
  for (; i < n; ++i) {
    if (array[i] == x) pos = i;
  }
  return pos;
}

average case time complexity:

查找变量x在数组中的位置,有n+1种情况:在数组的0~n-1位置中不在数组中,考虑每种情况,然后再除以情况n + 1种情况数。

03 04|复杂度分析_第4张图片

根据时间复杂度的大O标记法,省去系数,常量,低阶,简化后为:O(n)。

但是实际情况并不是这样,数组的0~n-1位置中不在数组中发生的概率是不同的。假设在数组中和不在数组中的概率1/2,另外查找数据出现在数组的0~n-1位置中任意位置的概率就是1/(2n),所以平均时间复杂度:

03 04|复杂度分析_第5张图片

该值叫做概率论中加权平均值,也叫期望值,所以平均时间复杂度全称:加权平均时间复杂度或者期望时间复杂度。

根据时间复杂度的大O标记法,省去系数,常量,低阶,简化后为:O(n)。

 

amortized time complexity          对应的分析方法:摊还分析(平摊分析)

平均时间复杂度只在某些特殊情况会用到,而均摊时间复杂度的应用场景更加特殊和有限

数组的插入:如果数组满了,则遍历数组,把所有累加和存放到第一个位置;否则直接插入。

时间复杂度O(n)和O(1)。每一次O(n)的操作后面都会跟着n-1次O(1)的操作,所以把耗时多的那次操作均摊到接下来n-1次耗时少的操作,均摊下来,这一组连续操作的均摊时间复杂度就是O(1)。

然而什么时候使用均摊时间复杂度呢?

对于一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间满足前后连续的时序关系,这样就可以把这一组操作放在一起分析,看是否能把较高的时间复杂度的耗时,平摊到其他时间复杂度比较低的操作上,并且能够使用均摊时间复杂度分析的场合,一般均摊时间复杂度等于最好情况时间复杂度。

tips:重要的不是区别平均时间复杂度和均摊时间复杂度,而是掌握均摊分析

课后例题:


// 全局变量,大小为10的数组array,长度len,下标i。
int array[] = new int[10]; 
int len = 10;
int i = 0;

// 往数组中添加一个元素
void add(int element) {
   if (i >= len) { // 数组空间不够了
     // 重新申请一个2倍大小的数组空间
     int new_array[] = new int[len*2];
     // 把原来array数组中的数据依次copy到new_array
     for (int j = 0; j < len; ++j) {
       new_array[j] = array[j];
     }
     // new_array复制给array,array现在大小就是2倍len了
     array = new_array;
     len = 2 * len;
   }
   // 将element放到下标为i的位置,下标i加一
   array[i] = element;
   ++i;
}

最好时间复杂度:O(1)

最差时间复杂度:O(n)

平均时间复杂度

如果用平均时间复杂度分析

(1+1+...+1+n) / (n+1) = 2n/(n+1) = O(1)

如果是加权平均时间复杂度分析

1*(1/n+1) + 1*(1/n+1) + ... + 1*(1/n+1) + n * (1/n+1) = O(1)

如果是使用均摊法

前面n个操作的时间复杂度都是O(1),第n+1次操作的时间复杂度是O(n),所以均摊到前面n次,均摊下来时间复杂度为O(1)

 

你可能感兴趣的:(数据结构与算法之美)