算法复杂度分析

数据结构和算法本身解决的是“快”和“省”的问题,而执行效率是算法一个非常重要的考量指标。算法的复杂度就是它的一个衡量标准,尽管不同硬件的实际执行效率有所不同。

算法的复杂度主要分为空间复杂度和时间复杂度。

空间复杂度:算法在计算机执行时所需要存储空间的度量。通常使用S(n) = O(f(n))表示。
主要包括三个部分:
1.算法程序所占的空间
2.输入的初始数据所占的空间
3.算法执行过程中所需要的额外空间

时间频度:算法中语句的执行次数,通常用T(n)来表示。
时间复杂度:定性的描述算法的运行时间,考察输入值大小趋近无穷的情况,通常使用O(f(n))来表示。

时间复杂度

1.时间复杂度分析

通常对于复杂度的分析,一般有以下三个常用的方法

1. 只关注循环执行次数最多的一段代码

private void method(int n){
        int a = 0;
        int b = 1;
        int c = 2;

        for (int i = 0; i < n ; i++) {
            a += i;
        }
    }

这个方法里面,前三次的声明只会执行1次,对于常数级不予考虑。对于for循环,这两行代码,将会执行n次,所以时间复杂度为O(n)。

2. 总复杂度等于量级最大的那段代码的复杂度

private void methodOne(int n){
        int a = 0;
        for (int i = 0; i < 1000 ; i++) {
            a += i;
        }

        int b = 0;//1
        for (int i = 0; i < n ; i++) { // n+1
            b += i; //n
        }

        int c = 0; //1
        for (int i = 0; i < n ; i++) { //n+1
            for (int j = 0; j < n ; j++) { //n * (n+1)
                c = c + i*j; //n * n
            }
        }
    }

对于第一个循环,执行了1000次,对于常数级我们仍不给予考虑。第二个循环执行了n次,时间复杂度为O(n)。对于第三个嵌套的for循环,代码执行了n²(并不是最准确的)次,时间复杂度为O(n²),所以这个方法的时间复杂度为O(n²)。

3. 嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

private void method(int n){
        int a = 0;
        for (int i = 0; i < n ; i++) {
            a += methodOne(i);
        }
    }

private int methodOne(int n){
        int a = 0;
        for (int i = 0; i < n ; i++) {
            a += i;
        } 
        return a;
    }

method方法的复杂度为O(n),但是在for循环中调用了methodOne,因为methodOne的复杂度也是O(n),所以整个方法的时间复杂度就是O(n²)。

2.常见的时间复杂度

复杂度量级
大致分为两类,多项式量级非多项式量级

图中波浪线的两种为非多项式量级。我们把时间复杂度为非多项式量级的算法问题叫作 NP(Non-Deterministic Polynomial,非确定多项式)问题。

当数据规模 n 越来越大时,非多项式量级算法的执行时间会急剧增加,求解问题的执行时间会无限增长。所以,非多项式时间复杂度的算法其实是非常低效的算法,我们应尽量避免这种算法。

3.空间复杂度

空间复杂度像比于时间复杂度,重要性和分析都没有那么重要和困难,但是对效率的影响还是存在滴。

private int[] methodTwo(int n){
     int a = 0;
     int[] array = new int[n];
        for (int i = 0; i < n ; i++) {
            a += i;
            array[i] = a;
        }
      return array;
    }

a变量只声明了一个空间,常量级的可以考虑不计,array变量声明了n个空间,剩下的没有占用更多的空间,因此空间复杂度是 O(n)。

常见的空间复杂度就是 O(1)、O(n)、O(n²),像 O(logn)、O(nlogn) 这样的对数阶复杂度平时都用不到。

4.最好、最坏情况时间复杂度

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

private int methodThree(int[] array, int x){
        int a = 0;
        for (int i = 0; i < array.length ; i++) {
            if (array[i] == x){
                a = i;
                return a;
            }
        }
        return -1;
    }

这个方法是找出和x相同值的下标,最好的情况是数组的第一个就是我们想要的结果,此时时间复杂度是O(1)。最坏的情况是最后一个是我们想要的结果或者是没有和x相同的下标,此时时间复杂度是O(n)。

5.平均时间复杂度

在大多数情况下,最好或是最坏的情况一般很少发生,为了更好的表示复杂度,引入平均时间复杂度。对于上面的案例,有n+1种情况,每种情况发生的概率是一样的,把每种情况下查找需要遍历的元素个数累加起来,然后再除以 n+1,就可以得到需要遍历的元素个数的平均值

(1+2+3+……+n+n)/(n+1)

结果是一个n量级的数,所以它的平均时间复杂度是O(n)。

6.均摊时间复杂度

我们把耗时多的那次操作均摊到耗时少的操作上,这就是均摊分析的大致思路。

static int index;
private int[] insert(int[] array, int x){
        if(array.length == index){
            int sum = 0;
            for (int i = 0; i < array.length ; i++) {
                sum += array[i];
            }
            array[0] = sum;
            index = 1;
        }
        array[index] = x;
        index++;
        return array;
    }

这个方法实现了插入方法,当数组有空间时,直接插入,当没有空间时,将数组里全部数求和后放到数组第一个位置。

最好的情况是数组中有空闲空间,只需要插入就行了,所以最好情况时间复杂度为 O(1)。

最坏的情况是数组中没有空闲空间了,需要先做一次数组的遍历求和,然后再将数据插入,所以最坏情况时间复杂度为 O(n)。

平均时间复杂度是每个位置都有空闲,加上没有空间需要求和,一共有n+1种情况,假设每种情况概率相同,前n种的每种概率为1/(n+1),加起来也就是n/(n+1),最后一种需要执行一次for循环,也就是n/n+1(严格意义来说的话是n+1/n+1),所以平均时间复杂度就为O(1)。

与上一个查找下标方法相比,寻找下标方法在最好的情况下才为O(1),而插入方法在最坏的情况下才为O(n)。这是因为在大多数情况下,插入方法的时间复杂度很低,只有个别情况下复杂度才很高,我们将它的效率进行了均摊。

对一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,这个时候,我们就可以将这一组操作放在一块儿分析,看是否能将较高时间复杂度那次操作的耗时,平摊到其他那些时间复杂度比较低的操作上。而且,在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好情况时间复杂度。

我们可以认为均摊时间复杂度是一种特殊的平均时间复杂度

我们要做的控制复杂度,而不是制造复杂度。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值