了解算法复杂度

算法复杂度是用来作为算法好坏的衡量标准,复杂度分为两个维度:时间和空间:
时间复杂度,代表算法执行时间效率;
空间复杂度,代表算法占用的内存空间。

对于某些算法来说,相同数据规模的不同数据依然会造成算法的运行时间/空间的不同,因此我们通常使用算法的最坏时间复杂度,记为T(n)。

通常用大O渐近时间复杂度来表式算法的复杂度,它代表着代码执行时间增长的变化趋势。

算法复杂度种类及优先级

1.  常数复杂度:O(1)
2.  对数复杂度:O(logn)
3.  一次方复杂度:O(n)
4.  一次方乘对数复杂度:O(nlogn)
5.  乘方复杂度:O(n^2),O(n^3)
6.  指数复杂度:O(2^n)
7.  阶乘复杂度:O(n!)
8.  无限大指数复杂度:O(n^n)
n O(1) O(log n) O(n) O(n log n) O(n2) O(2n) O(n!)
1 < 1 秒 < 1 秒 < 1 秒 < 1 秒 < 1 秒 < 1 秒 < 1 秒
10 < 1 秒 < 1 秒 < 1 秒 < 1 秒 < 1 秒 < 1 秒 4 秒
100 < 1 秒 < 1 秒 < 1 秒 < 1 秒 < 1 秒 40170万亿年 >百年历史
1,000 < 1 秒 < 1 秒 < 1 秒 < 1 秒 < 1 秒 >百年历史 >百年
10,000 < 1 秒 < 1 秒 < 1 秒 < 1 秒 2分钟 >百年 >百年
100,000 < 1 秒 < 1 秒 < 1 秒 1秒 3小时 >百年 >百年
1,000,000 < 1 秒 < 1 秒 1秒 20秒 12天 >百年 >百年

image.png

算法复杂度类型判断

分析方法

下面主要分析前几种常见的时间复杂度:

  • 比较容易判断的几种:恒定O(1)、线性O(n)、多次方O(n^2)
  • 比较不好判断的:对数O(log n)和nO(log n)、指数O(2 ^ n)、n^n

(其中暂时不详情分析指数O(2 ^ n)、n^n)

在 大O符号表示法中,时间复杂度的公式是: T(n) = O( f(n) ),其中f(n) 表示每行代码执行次数之和,而 O 表示正比例关系

O(1)-恒定时间

有恒定的增长率,算法计算花费的时间不会随着输入的值增大而增长:
1、加、减、乘、除操作。

O(n)-线性时间

增长率为n,随着输入的增大,算法计算花费的时间更长:
1、获取数组中的最大值/最小值;
2、在集合中找到给定的元素;
3、打印列表中的所有值。

O(n ^ 2)-二次时间

增长率为n ^ 2,二次时间复杂度为n的for循环:
1、检查集合中是否有重复的值;
2、使用冒泡排序,插入排序或选择排序对集合中的项目进行排序;
3、查找数组中所有可能的有序对。

O(n ^ c)-多项式时间

同上类推,增长率为n ^ c:
1、三层嵌套循环

求方程式 3x + 9y + 8z = 79; x,y,z小于n。
function findXYZ(n) {
  const solutions = [];

  for(let x = 0; x < n; x++) {
    for(let y = 0; y < n; y++) {
      for(let z = 0; z < n; z++) {
        if( 3*x + 9*y + 8*z === 79 ) {
          solutions.push({x, y, z});
        }
      }
    }
  }

  return solutions;
}

console.log(findXYZ(10));

O(log n)-对数时间

如何计算是对数时间复杂度?

输入的值作为结果变量n可以和执行次数x表式成指数函数:

n=2^x

就可以转化成

x = log2^n

此时算法复杂度就为O(log n)

简单例子解释

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

n越大循环计算的次数越多,最大执行x次刚好等于n,于是退出while循环,可以表式:2^x = n。

复杂例子举例
对数时间复杂度通常适用于每次将问题分成两半的算法:
1、二分查找。

二分查找:

function indexOf(array, element, offset = 0) {
  // split array in half
  const half = parseInt(array.length / 2);
  const current = array[half];

  if(current === element) {
    return offset + half;
  } else if(element > current) {
    const right = array.slice(half);
    return indexOf(right, element, offset + half);
  } else {
    const left = array.slice(0, half)
    return indexOf(left, element, offset);
  }
}

// Usage example with a list of names in ascending order:
const directory = ["Adrian", "Bella", "Charlotte", "Daniel", "Emma", "Hanna", "Isabella", "Jayden", "Kaylee", "Luke", "Mia", "Nora", "Olivia", "Paisley", "Riley", "Thomas", "Wyatt", "Xander", "Zoe"];
console.log(indexOf(directory, 'Hanna'));   // => 5
console.log(indexOf(directory, 'Adrian'));  // => 0
console.log(indexOf(directory, 'Zoe'));     // => 18

O(n log n)-线性运算

同上,基于O(log n)算法复杂度执行了n次。

1、高效的排序算法,例如合并排序,快速排序等。

/**
 * Sort array in asc order using merge-sort
 * @example
 *    sort([3, 2, 1]) => [1, 2, 3]
 *    sort([3]) => [3]
 *    sort([3, 2]) => [2, 3]
 * @param {array} array
 */
function sort(array = []) {
  const size = array.length;
  // base case
  if (size < 2) {
    return array;
  }
  if (size === 2) {
    return array[0] > array[1] ? [array[1], array[0]] : array;
  }
  // slit and merge
  const mid = parseInt(size / 2, 10);
  return merge(sort(array.slice(0, mid)), sort(array.slice(mid)));
}

/**
 * Merge two arrays in asc order
 * @example
 *    merge([2,5,9], [1,6,7]) => [1, 2, 5, 6, 7, 9]
 * @param {array} array1
 * @param {array} array2
 * @returns {array} merged arrays in asc order
 */
function merge(array1 = [], array2 = []) {
  const merged = [];
  let array1Index = 0;
  let array2Index = 0;
  // merge elements on a and b in asc order. Run-time O(a + b)
  while (array1Index < array1.length || array2Index < array2.length) {
    if (array1Index >= array1.length || array1[array1Index] > array2[array2Index]) {
      merged.push(array2[array2Index]);
      array2Index += 1;
    } else {
      merged.push(array1[array1Index]);
      array1Index += 1;
    }
  }
  return merged;
}

O(2 ^ n)-指数时间

指数(以2为底)的运行时间意味着,随着输入的增加,算法每次执行的计算都会加倍。
1、功率集:集合上的所有子集;
2、斐波那契;
3、使用动态编程的旅行商问题。

持续优化本文...

参考资料

复杂度

每个程序员都应该知道的8个时间复杂性

算法的时间与空间复杂度

数据结构和算法(Golang实现)(9)基础知识-算法复杂度及渐进符号


贝er
58 声望6 粉丝

不仅仅是程序员


引用和评论

0 条评论