1.算法复杂度的基础概念

117 阅读6分钟

算法的定义

通俗的说,算法是描述解决问题的方法。在计算机领域中,算法可以说是一组完成任务的指令,计算或者解决问题的步骤,因此,任何代码片段都可视为算法。

算法的特性

算法具有五个基础特性:输入、输出、有穷性、确定性和可行性。

输入,输出

算法具有零个或多个输入。尽管对于绝大多数算法来说,输入参数都是必要的,但对于个别情况,如打印代码,不需要任何输入参数,因此算法的输入可以是零个。

算法至少有一个或多个输出,算法是一定需要输出的。输出的形式可以是打印输出,也可以是返回一个或多个值等。

有穷性

有穷性:指算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。

确定性

确定性:算法的每一步骤都具有确定的含义,不会出现二义性。算法在一定条件下,只有一条执行路径,相同的输入只能有唯一的输出结果。算法的每个步骤被精确定义而无歧义。

可行性

可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成。可行性意味着算法可以转换为程序上机运行,并得到正确的结果。

算法的效率

算法的效率主要由以下两个复杂度来评估:
时间复杂度:评估执行程序所需的时间。可以估算出程序对处理器的使用程度。
空间复杂度:评估执行程序所需的存储空间。可以估算出程序对计算机内存的使用程度。

一个算法具体的时间复杂度可能不是固定的,比如,我们顺序查找一个有n个随机数字数组中的某个数字,最好的情况是第一个数字就是,那么算法的时间复杂度为O(1),但也有可能这个数字就在最后一个位置上待着,那么算法的时间复杂度就是O(n),这是最坏的一种情况了。

对算法的分析,一种方法是计算所有情况的平均值,这种时间复杂度的计算方法称为平均时间复杂度。另一种方法是计算最坏情况下的时间复杂度,这种方法称为最坏时间复杂度。平均运行时间很难通过分析得到,一般在没有特殊说明的情况下,都是指最坏时间复杂度。我们的O表示的时间复杂度就是最坏时间复杂度。

算法的时间复杂度

算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数,记作T(n)=O(f(n)),它称为算法的渐进时间复杂度,简称时间复杂度。

大O表示法

用O()来体现算法时间复杂度的记法,我们称之为大O表示法。

大O的推导

算法的时间复杂度 大O 推导,有几条规则:

  1. 用常数1取代运行时间中的所有加法常数。
  2. 在修改后的运行次数函数中,只保留最高阶项。
  3. 如果最高阶项存在且不是1,则去除与这个项相乘的常数。
  4. 考虑n 变大的情况。去除其他影响较小的部分,保留最大的部分。

个人理解:大O的推导主要是对算法执行次数的计算数据计算公式。根据计算公式的不同,分为不同的时间复杂度。

常见时间复杂度

  1. O(1),也叫常数时间,例如通过索引查找数组元素。
  2. O(n),也叫线性时间,这样的算法包括简单查找。
  3. O(log n),也叫对数时间,这样的算法包括二分查找。
  4. O(n log n),也叫线性对数时间,这样的算法包括快速排序。
  5. O(n²),也叫平方时间,这样的算法包括选择排序。
  6. O(n³),也叫立方时间,不常见。
  7. O(2^n^),也叫指数时间,不常见。
  8. O(n!),也叫阶乘时间,这样的算法包括旅行商问题的解决方案,包括写出1~n的所有全排列,不常见。
  9. O(n^n^),也叫完全平方时间,时间复杂度最高,不常见。

时间复杂度耗费时间从小到大排序如下:

O(1)<O(logn)<O(n)<O(nlogn)<O(n^2^)<O(n^3^)<O(2^n^ ) <O(n!)<O(n^n^)

常见时间复杂度推算实例:

1:
public int func() {
    int a = 10 + 20; // 执行 1 次
    return a; // 执行 1 次
}
T(n) = 1 + 1 = 2
由于是常数,时间复杂度为O(1)

例2:
public void func() {
    int n = 10; // 执行1次
    for(int i = 0; i < n; i++) { // 执行 n 次
     System.out.printf(i); // 执行 n 次
    }
}
T(n) = n + n + 1 = 2n + 1
忽略常数项1,忽略和最高阶相乘的常数2,得到时间复杂度O(n)

例3:
public int func(int n) {
  for(int i = 0; i < n; i++) { // 执行n次
    for(int j = 0; j < n; j++) { //由于外层循环,该语句执行 n * n 次
    System.out.printf("Hello"); // 同理,执行n*n次
    }
  }
}
T(n) = n + n^2 + n^2 = 2n^2 + n
忽略低阶项,和高阶项的常数部分,得到时间复杂度O(n^2)

例3:
public int funcint n) {
  for(int i = 0; i < n; i = i* 2) { // i=2,4,8,16...时执行,可通过对数近似计算次数,执行log2(n)次
  System.out.printf("Hello");//执行log2(n)次
  }
}
T(n) = log2(n)+log2(n)=2log2(n)
忽略常数部分,得到时间复杂度O(log2(n))

算法空间复杂度(了解)

算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作:S(n)=O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。

若算法执行时所需的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工作,空间复杂度为O(1),而一般的递归算法就要有O(n)的空间复杂度了。

通常,我们都使用“时间复杂度”来指运行时间的需求,使用“空间复杂度”指空间需求。一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面衡量。当不用限定词地使用“复杂度”时,由于空间复杂度难以计算,通常都是指时间复杂度。