再也不怕别人问我时间复杂度了,包看懂

713 阅读3分钟

我们经常把时间复杂度作为算法效率的指标之一。那么我们怎么去计算一个算法的时间复杂度呢?

计算原则

首先,算法时间复杂度并不是指算法的总执行时间,而是基本操作的总次数。用一句话来概括:算法中基本操作的执行次数可以作为算法时间复杂度的度量

因此:对一个算法的复杂度分析有以下的几个步骤:

  1. 确定算法中的基本操作以及问题的规模n
  2. 根据基本操作执行情况确定执行次数f(n)
  3. 利用大O表示法,省略系数低阶常量,具体就是找出n中变化最快的项作为时间复杂度的度量T(n)=O(f(n)中变化最快的项)

从上面我们可以得到下面结论:

  • f(n)n无关时,T(n)= O(1)
  • f(n)n是线性关系时,T(n)= O(n)
  • f(n)n是二次关系时,T(n)= O(n^2)
  • 以此类推

并且根据数学性质,我们还有以下结论:

O(1) \le O(n) \le O(nlog_2(n) \le O(n^2) \le O(n^3) \le O(n^k) \le ... \le O(2^n)

上面说的比较抽象,我们来用demo来学习一下如何计算

案例

案例1

    function run(n){
        let i = 0,j=100;
        while(i<n){
            ++j;
            i+=2;
        }
    }

分析:

  • 找出基本规模,确定规模n
    • 因为依赖于内层的while循环,所以++j,i+=2为基本操作
    • 循环条件i<n,所以规模n就是参数n
  • 确定执行次数f(n)
    • 循环的开始与结束与i有关,i每次自增2,假设自增m次后循环结束,最后i=2m+1,n=2m+1+K(K为修正n和m的接近程度的常数)
    • 最后,得到m = (n - 1 - K) / 2,即f(n)= (n - 1 - K) / 2
  • 利用大O表示法,省略系数低阶常量
    • 很显然,n/2是增长最快的项,去除系数1/2
    • 得到T(n) = O(n)

案例2

冒泡排序(简单版)

    function run(){
        let arr = [3,4,1,2];
        for (let j = 0; j < arr.length - 1; j++) {
            // 这里要根据外层for循环的 j,逐渐减少内层 for循环的次数
            for (let i = 0; i < arr.length - 1 - j; i++) {
                if (arr[i] > arr[i + 1]) {
                    let temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                }
            }
        }
    }

分析:

  • 找出基本规模,确定规模n
    • 因为依赖于内层的循环,所以let temp = arr[i],arr[i] = arr[i + 1],arr[i + 1] = temp为基本操作
    • 循环条件arrlength,所以规模n就是arr.length
  • 确定执行次数f(n)
    • 外层必定循环n-1次,内层因为if的判断条件,分为两种情况
    • 最好的情况(全部正序):n - 1
    • 最坏的情况(全部倒序):\underbrace{(n-1)+(n-2)+\cdots+1}_{n-1}=n*(n+1)/2
  • 利用大O表示法,省略系数低阶常量
    • 因此,最好的情况是O(n),最坏的情况是O(n^2)
    • 利用加权平均值得平均时间复杂度O(n^2)(不懂的话可以看看概率论)

案例三

    function run(n){
        let i = 0,s = 0;
        while(s<n){
            ++i;
            s=s+i;
        }
    }
  • 找出基本规模,确定规模n
    • 因为依赖于内层的while循环,所以++i,s=s+i为基本操作
    • 循环条件s<n,所以规模n就是参数n
  • 确定执行次数f(n)
    • 循环的开始与结束与si有关,假设自增m次后循环结束,因此s_1=1s_2=1+2s_3=1+2+3,... ,s_m=1+2+...+m = m(m+1)/2,因此 n + K = m(m+1)
    • 最后,得到m = \sqrt{2n + 2K + 1/4} - 1/2,即f(n)= \sqrt{2n + 2K + 1/4} - 1/2
  • 利用大O表示法,省略系数低阶常量
    • 得到T(n) = O(\sqrt{n})

到了,到此,我相信各位同学已经知道了计算时间复杂度的方式了,各位同学如果还有兴趣的话,可以自己去把排序算法都计算一下,可以加深一下自己的认知。

参考文档

  • 《天勤数据结构》