01_算法的复杂度分析

复杂度分析

大O时间复杂度表示法: 公式中的低阶、常量、系数三部分并不左右增长趋势,所以都可以忽略。我们只需要记录一个最大量级就可以了

时间复杂度分析:
1.只关注循环执行次数最多的一段代码
2.加法法则:总复杂度等于量级最大的那段代码的复杂度
3.乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

几种常见时间复杂度实例分析
多项式量级
非多项式量级,NP问题 O(2n)和O(n!) 非常低效

几种常见的多项式时间复杂度
O(1): 不存在循环、递归

O(logn)、O(nlogn):如下代码

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

变量i的取值是一个等比数列
在这里插入图片描述

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

在这里插入图片描述
如果一段代码的时间复杂度是 O(logn),我们循环执行 n 遍,时间复杂度就是 O(nlogn) 了。而且,O(nlogn) 也是一种非常常见的算法时间复杂度。比如,归并排序、快速排序的时间复杂度都是 O(nlogn)。

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)

空间复杂度分析:只需要看空间分配的大小, 常见的空间复杂度就是 O(1)、O(n)、O(n2 )

总结
复杂度也叫渐进复杂度,包括时间复杂度和空间复杂度,用来分析算法执行效率与数据规模之间的增长关系,可以粗略地表示,越高阶复杂度的算法,执行效率越低。常见的复杂度并不多,从低阶到高阶有:O(1)、O(logn)、O(n)、O(nlogn)、O(n2 )。
在这里插入图片描述

最好、最坏时间复杂度、平均时间复杂度

case1:

// n表示数组array的长度
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;
}

时间复杂度为O(n)
case2:

// n表示数组array的长度
int find(int[] array, int n, int x) {
  int i = 0;
  int pos = -1;
  for (; i < n; ++i) {
    if (array[i] == x) {
       pos = i;
       break;
    }
  }
  return pos;
}

时间复杂度存在多种可能
最好:O(1)
最坏:O(n)

那么平均怎么算呢?
x在数组中的位置,有n+1种情况:在数组的0-n-1位置中和不在数组中
我们把每种情况下,查找需要遍历的元素个数累加起来,然后再除以 n+1,就可以得到需要遍历的元素个数的平均值
在这里插入图片描述
时间复杂度的大 O 标记法中,可以省略掉系数、低阶、常量,所以,咱们把刚刚这个公式简化之后,得到的平均时间复杂度就是 O(n)
OK,考虑到概率因素,假设x出现在数组内的概率和数组外的概率分别为1/2,则上式变为
在这里插入图片描述
这个值就是概率论中的加权平均值,也叫作期望值,所以平均时间复杂度的全称应该叫加权平均时间复杂度或者期望时间复杂度
不过这段代码的加权平均复杂度依然是O(n)

均摊时间复杂度

操作之间存在前后连贯的时序关系,我们就把较高时间复杂度的耗时,平摊到其他复杂度比较低的操作上,如下

// array表示一个长度为n的数组
// 代码中的array.length就等于n
int[] array = new int[n];
int count = 0;
void insert(int val) {
    if (count == array.length) {
       int sum = 0;
       for (int i = 0; i < array.length; ++i) {
          sum = sum + array[i];
       }
       array[0] = sum;
       count = 1;
    }


    array[count] = val;
    ++count;
}

这段代码实现了一个往数组中插入数据的功能。当数组满了之后,也就是代码中的 count == array.length 时,我们用 for 循环遍历数组求和,并清空数组,将求和之后的 sum 值放到数组的第一个位置,然后再将新的数据插入。但如果数组一开始就有空闲空间,则直接将数据插入数组。
可以发现,每一次清空数组(耗时O(n)),接下来n-1都会耗时O(1),我们就把高耗时的平摊到低耗时上,求得均摊复杂度为O(1)

参考课程:极客时间王争老师的《数据结构与算法之美》

猜你喜欢

转载自blog.csdn.net/Yungang_Young/article/details/112566643