算法复杂度

什么是算法复杂度

算法复杂度,即算法在编写写成可执行程序后,运行时所需要的资源,资源包括时间资源和内存资源

通俗的说,就是执行一段代码所需要的资源(主要是花费的时间以及占用的空间)。

大 O 复杂度表示法

我们记一个算法的复杂度为O(n),表示数据规模为n的情况下,算法使用的时间和空间资源。(也可以理解O(n)描述着,代码执行花费的时间和空间随着n增大变化的趋势)。
算法复杂度从时间上考虑是时间复杂度(快不快),从空间上考虑是空间复杂度(占的内存多不多)。

时间复杂度

1、O(1)

	public void method1(int n){
        int a = 5; // 执行1次
        int b = 6; // 执行1次
        int c =  a + b;// 执行1次
        System.out.println(c);// 执行1次
    }

现在有如上的一段代码,假定每一行执行的时间为“1”,那么上述代码用掉的时间为4。无论n是多大,使用的时间都是4,记为O(n) =4。**当n趋于无限大的时候,这个4可以近似的认为是1。**为什么呢,还记得我们为什么需要算法复杂度吗?我们需要通过时间复杂度来描述这个代码随着n的增大,所需要时间的变化趋势。而常数项对变化趋势影响很小,所以可以认为是O(1)。

2、O(n)

	public void method2(int n){ 
        for (int i = 1 ; i <= n; ++i) {
            System.out.println("haha"); // 第3行
        }
    }

同样假定每一行执行的时间为“1”,那么这个代码要花费多长时间呢。这个时候,我们只需要看第3行执行多少次即可。
这个很轻易的看出这是与n有关的。n=1,那么System.out.println("haha")只要执行1次;n=2,执行2次;n=3,执行3次。当等于n时,执行n次。所以这个时间的变化是成线性上升的,所以O(n)=n;

3、O( n 2 n^2 n2)

	public void method3(int n){
        for (int i = 1 ; i <= n; ++i) { // 第2行   第1层嵌套
            System.out.println("haha"); // 第3行
            for (int j = 1 ; j <= n; ++j) { // 第4行  第2层嵌套
                System.out.println("heihei");// 第5行
            }
        }
    }

老规矩,假定每一行执行的时间为“1”。
上面说过,常数项对变化趋势影响很小,所以可以认为是O(1)。那么同样, 当高次项与低次项同时存在时,比如 n 3 n^3 n3 + n 2 n^2 n2 + n n n,只有高次项对变化的趋势影响最大。所以我们只需要保留高次项,删除低次项即可。即: n 3 n^3 n3 + n 2 n^2 n2 + n n n = O( n 3 n^3 n3)(这个不是上述代码的复杂度,还没有开始计算呢,)

注意:1、具有多层嵌套的情况下,从最里层嵌套开始看。
在这里插入图片描述
首先看下2号嵌套,
对于System.out.println("heihei")来说,无论j取什么值,2号for循环的循环体都执行一次,j=2,System.out.println("heihei")执行一次,j=100还是执行一次。j的取值在1到n之间,所以对于整个2号for循环,一共执行了n次(n个1累加)。然后看1号for循环。
当i=1时,1号的循环体执行1+n次(System.out.println(“haha”)1次,2号for循环体n次);i=2时,1好的循环体执行1+n次(System.out.println(“haha”)1次,2号for循环体n次);i=3时,1号的循环体执行1+n次(System.out.println(“haha”)1次,2号for循环体n次)。那么i=n时,1号的循环体1+n次(System.out.println(“haha”)1次,2号for循环体n次)。累加起来就是: ∑ i = 1 n 1 + n \displaystyle\sum_{i=1}^{n} 1+n i=1n1+n = n + n 2 n^2 n2。只保留高次项,所以时间复杂度O( n 2 n^2 n2)。(从此也可以看出,我们其实只需要算出具有最多次嵌套中的代码执行的次数即可。于本例子中就是算出第5行执行多少次就ok了)

4、O( n 2 n^2 n2)

	public void method4(int n){
        for (int i = 1 ; i <= n; ++i) {
            System.out.println("haha");
            for (int j = 1 ; j <= i; ++j) {
                System.out.println("heihei");
            }
        }

    }

在这里插入图片描述
仔细看看,这个和3还是有区别的,2号for里面的n变成i了。
同样先看下2号嵌套,
j=1时,执行1次,
j=2时,执行1次,
当 j=i时,还是执行1次,所以2号for循环执行i次(i个1累加)。
对于1号for循环,
当i=1时,执行1次(因为最终计算复杂度时,会忽略低次项,所以此次直接忽略System.out.println(“haha”)的执行次数,并且上面已经得出2号for循环的执行次数是i,此时i=2,也意味着2号for循环的执行次数是2),
当i=2时,执行2次
当i=3时,执行3次,
当i=n时,执行n次,
所以时间复杂度: ∑ i = 1 n i = \displaystyle\sum_{i=1}^{n} i = i=1ni= n ∗ ( n + 1 ) 2 \frac{n*(n+1)}{2} 2n(n+1) = n 2 + n 2 \frac{n^2+n}{2} 2n2+n ,忽略低次项以及系数后,为O( n 2 n^2 n2)

5、O( n 2 n^2 n2)

	public void method5(int n){
        for (int i = 1 ; i <= n; ++i) {
            System.out.println("haha");
            for (int j = i ; j <= n; ++j) {
                System.out.println("heihei");
            }
        }
    }

在这里插入图片描述
仔细看看,这个和3还有4相比,又变化了
同样先看下2号嵌套,
j=i时,执行1次,
j=i+1时,执行1次,
当 j=n时,还是执行1次,所以2号for循环执行n-i+1次(n-i+1个1累加)。
对于1号for循环,
当i=1时,执行n次(因为最终计算复杂度时,会忽略低次项,所以此次直接忽略System.out.println(“haha”)的执行次数,并且上面已经得出2号for循环的执行次数是n-i+1,此时i=1,也意味着2号for循环的执行次数是n),
当i=2时,执行n-1次,
当i=3时,执行n-2次,
当i=n时,执行1次,
所以时间复杂度: ∑ i = 1 n i = \displaystyle\sum_{i=1}^{n} i = i=1ni= n ∗ ( n + 1 ) 2 \frac{n*(n+1)}{2} 2n(n+1) = n 2 + n 2 \frac{n^2+n}{2} 2n2+n ,忽略低次项以及系数后,为O( n 2 n^2 n2)

6、O( n 3 n^3 n3)

	public void method6(int n){
        for (int i = 1 ; i <= n; ++i) {
            System.out.println("haha");
            for (int j = 1 ; j <= i; ++j) {
                System.out.println("heihei");
                for (int k = 1 ; k <= j; ++k) {
                    System.out.println("hehe");
                }
            }

        }

    }

在这里插入图片描述
这次来个3层循环的。
老规矩,3号for的执行次数为j,
那么2号for的循环次数就是 n 2 + n 2 \frac{n^2+n}{2} 2n2+n
对于1号for来说,
i=1,执行 1 2 + 1 2 \frac{1^2+1}{2} 212+1
i=2,执行 2 2 + 2 2 \frac{2^2+2}{2} 222+2
i=3,执行 3 2 + 3 2 \frac{3^2+3}{2} 232+3
i=n,执行 n 2 + n 2 \frac{n^2+n}{2} 2n2+n

所以时间复杂度: ∑ i = 1 n i 2 + i 2 = \displaystyle\sum_{i=1}^{n} \frac{i^2+i}{2}= i=1n2i2+i= O( n 3 n^3 n3)

7、O( l o g n log n logn)

	public void method10(int n){
        for (int i = 1 ; i <= n; i*=2) {
            System.out.println("heihei");
        }
    }

当i= l o g 2 n log_2^n log2n时,跳出循环。
i取值范围(( 2 0 2^0 20 2 1 2^1 21 2 2 2^2 22 2 3 2^3 23,…, 2 t 2^t 2t),(t = l o g 2 n log_2^n log2n)
所以这个循环一共走 l o g 2 n log_2^n log2n次,即O( l o g 2 n log_2^n log2n)

8、O(n l o g n logn logn)

 public void method8(int n){

        for (int i = 1 ; i <= n; i*=2) {
            System.out.println("haha");

            for (int j = 1 ; j <= n; ++j) {
                System.out.println("heihei");
            }

        }

    }

在这里插入图片描述
2号for的执行次数为n
1号for的i实际的取值范围( 2 0 2^0 20 2 1 2^1 21 2 2 2^2 22 2 3 2^3 23,…, 2 t 2^t 2t),(t = l o g 2 n log_2^n log2n)
i= 2 0 2^0 20 ,执行n 次,
i= 2 1 2^1 21,执行n 次 ,
i= 2 2 2^2 22,执行n 次 ,
i=n,执行n 次 ,
所以时间复杂度: ∑ x = 0 l o g 2 n n = \displaystyle\sum_{x=0}^{log_2^n} n= x=0log2nn= n* l o g 2 n log_2^n log2n 。此处直接忽略系数 O(n l o g n logn logn)

9、O(n)

	public void method9(int n){
        for (int i = 1 ; i <= n; i*=2) {
            System.out.println("haha");
            for (int j = 1 ; j <= i; ++j) {
                System.out.println("heihei");
            }
        }
    }

在这里插入图片描述
2号for的执行次数为i
1号for的i实际的取值范围( 2 0 2^0 20 2 1 2^1 21 2 2 2^2 22 2 3 2^3 23,…, 2 t 2^t 2t),(t = l o g 2 n log_2^n log2n)
i= 2 0 2^0 20 ,执行 2 0 2^0 20 次,
i= 2 1 2^1 21,执行 2 1 2^1 21 次 ,
i= 2 2 2^2 22,执行 2 2 2^2 22 次 ,
i=n,执行n 次 ,
所以时间复杂度: ∑ x = 0 l o g 2 n 2 x = \displaystyle\sum_{x=0}^{log_2^n} 2^x= x=0log2n2x= 2 0 ( 1 − 2 t ) 1 − 2 \frac{2^0(1-2^t)}{1-2} 1220(12t)
又因为t= l o g 2 n log_2^n log2n
所以 2 0 ( 1 − 2 t ) 1 − 2 \frac{2^0(1-2^t)}{1-2} 1220(12t) = (n-1)
因此:O(n)

总结:
 没有循环,时间复杂度:O(1)
 有循环:从最内层循环开始,
  1、确定变量取值范围
  2、确定变量每次取值的时间复杂度,记为f(n)
  3、循环相加f(n)
 重复1到3步,即可计算出嵌套循环的时间复杂度

各种时间复杂度比较

上面的例子中,演示了O(1)、O(n)、O( n 2 n^2 n2)、O( n 3 n^3 n3)、O( l o g n logn logn)、O( n ∗ l o g n n*logn nlogn)
下图是几个复杂度比较
在这里插入图片描述

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

最好时间复杂度:在最理想的情况下,执行这段代码的时间复杂度。就是代码最少执行多少次。
最坏时间复杂度:在最糟糕的情况下,执行这段代码的时间复杂度。就是代码最多------------------------------------执行多少次。

那么在最好与最坏的情况下一定会存在这其他的情况,累加 这些情况下的时间复杂度 乘以 每种情况出现的频率 即可求出平均时间复杂度。
平均时间复杂度: ∑ i = 1 n O ( i ) ∗ P ( i ) \displaystyle\sum_{i=1}^{n} O(i)*P(i) i=1nO(i)P(i).
O ( i ) O(i) O(i)代表i情况下花费的时间
P ( i ) P(i) P(i)代表出现i这种情况的概率。

1

    // 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;
    }

最好时间复杂度:不执行if语句,O(1)
最坏时间复杂度:执行if语句,if语句里面有个for循环,需要执行n次,所以O(n)
平均时间复杂度:对于数据规模n,即取值范围为1,2,3,…,n

  • 5
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值