复杂度分析

  同一个问题可使用不一样的算法解决,那么不一样的算法孰优孰劣如何区分呢?所以咱们须要一个表示方法来表明每一个程序的效率。算法

 

  衡量一个程序好坏的标准,通常是运行时间与占用内存两个指标。数组

  不过咱们在写代码的时候确定没法去估量程序的执行时间,由于真实的执行时间受到多方面因素的影响,好比一样一段程序,放在高配服务器上跑和放在低配服务器上跑彻底是两个表现效果,好比遍历一个数组的函数,执行时间彻底取决于调用函数传入的数组大小。服务器

 

  如何在不运行程序的状况下,判断出代码的执行时间呢?显然是不可能的。函数

 

  不过咱们虽然没法预估代码的绝对执行时间,可是咱们能够预估代码基本的执行次数。优化

  一段代码的执行时间若是有变化,则必定是受到外部输入的数据所影响,咱们将代码中全部不变的因素,表示为大O,将变化的因素做为基数n,表示为:O(n),大O的意思是忽略重要项之外的内容,咱们常以这种大O表示法来判断比较各类算法的执行效率。spa

 

  接下来我会介绍几种经常使用的复杂度表示方法。code

 

  PS:专业的解释必然全篇都是数学证实,未免太过于复杂,让学者更加迷茫,我这里写的并非教材,而是最直白的理解。blog

 

时间复杂度

  本节中例举的各类时间复杂度以好到差依次排序。排序

常数时间 O(1)

  先看下这个函数:内存

    private static void test(int n)
    {
        int a = 2;
        int b = 3;
        int c = a+b;
        System.out.println(c);
    }

  一共4行代码,CPU要将a的值写入内存,b的值写入内存,a和b进行计算,将计算结果写入c,最后将c输出到控制台。

  尽管计算机内部要作这么多事情,这段代码的时间复杂度依然是O(1),缘由是这几行代码所作的操做是固定的,是不变的因素。

 

  再看下这个函数:

    private static void test(int n)
    {
        for (int i=0;i<100000;i++){
            System.out.println(i);
        }
    }

  循环10W次,可能你以为功耗可能有点大,不过它的时间复杂度仍然是O(1)。

 

  咱们能够这么固定的认为:不管接收的参数怎么变化,只要代码执行次数是无变化的,则用1来表示。 凡是O(1)复杂度的代码,一般表明着它是效率最优的方案。

 

对数时间 O(log n)

  广泛性的说法是复杂度减半,就像纸张对折。

  示例代码:

    private static void test(int n)
    {
        for (int j=1;j<=n;j=j*2){
            System.out.println(j);
        }
    }

  这段代码的执行效果并不是是一次折半,它是次次折半,以2为底,不断的进行幂运算,实际上只要有幂指数关系的,无论你的底数是几,只要可以对原复杂度进行求幂逆运算咱们均可以称之为O(log n)

  好比:

    private static void test(int n)
    {
        for (int j=1;j<=n;j=j*3){
            System.out.println(j);
        }
    }

  在忽略系数、常数、底数以后,最后均可以表示为O(log n),只不过咱们遇到的算法几乎不会出现一些极端例外状况,对数时间的所在地常见以二分查找为表明。

 

线性时间 O(n)

  咱们将test方法稍稍修改一下:

    private static void test(int n)
    {
        for (int i=0;i<n;i++){
            System.out.println(i);
        }
    }

  修改以后此次不是执行10W次,而是执行n次,n是由参数传入的一个未知值,在没有真实运行的时候咱们没法判断这个n究竟是多少?由于它能够是任意int型数字,你能够这么认为:在理想的状况下,它的复杂度是O(1),在恶劣的状况下,它的复杂度是无限大。彻底取决于方法调用方。

  直白的说,for循环就是循环n次,所以这段代码的时间复杂度为O(n),这种复杂度经常表现为线性查找。

 

线性对数时间 O(n log n)

  线性对数时间也就是线性时间嵌套对数时间:

    private static void t(int n){
        for (int i=0;i<n;i++){
            test(n);
        }
    }
    private static void test(int n)
    {
        for (int j=1;j<=n;j=j*2){
            System.out.println(j);
        }
    }

  t这个方法的时间复杂度就是O(n log n)

 

平方时间 O(n^2)

  平方时间就是执行程序须要的步骤数是输入参数的平方,最多见的是嵌套循环:

    private static void test(int n)
    {
        for (int i=0;i<n;i++){
            for (int j=n;j>0;j--){
                System.out.println(j);
            }
        }
    }

 

其余时间

  比O(n^2)还要慢的天然有立方级O(n^3)

  比O(n^3)更慢慢的还有指数级O(2^n)

  慢到运行一次程序要绕地球三百圈的有O(n!)

 

  正常状况下咱们不会接触到这些类型的算法。

 

空间复杂度

  所谓空间,就是程序运行占用的内存空间,空间复杂度指的就是执行算法的空间成本。

 

  这里咱们抛一道题来作例子:在一个数组中找出有重复的值,如数组[3,8,13,7,15,8,6,6] 找出8和6

 

  解法:

    private static void test(int[] arr)
    {
        for (int i=0;i<arr.length;i++){
            for(int j=0;j<i;j++){
                if(arr[j] == arr[i]){
                    System.out.println("找到了:"+arr[i]);
                }
            }
        }
    }

  很显然:时间复杂度为O(n^2)。

 

  那咱们还可使用一种更优的解法:

    private static void test(int[] arr)
    {
        HashSet hashSet = new HashSet();
        for (int i=0;i<arr.length;i++){
            if(hashSet.contains(arr[i])){
                System.out.println("找到了:"+arr[i]);
            }
            hashSet.add(arr[i]);
        }
    }

  也许你会惊讶的发现,时间复杂度被优化成了O(n)。

 

  虽然时间复杂度下降成了O(n),可是付出的代价是空间复杂度变成了O(n),由于新的解法使用了一个HashSet来存储数据,存储数据天然要占用内存空间,而占用的空间大小彻底取决于传入数组大小。

  咱们之因此说第二种解法更优,实际上是一种常规思想,由于现实中绝大部分状况,时间复杂度显然比空间复杂度更为重要,咱们宁愿多分配一些存储空间做为代价,来提高程序的执行速度。

 

  总而言之,比较两个算法优劣的指标有两个,时间复杂度与空间复杂度,优先比较时间复杂度,时间复杂度相同的状况下比较空间复杂度。

 

  最后:感谢阅读。

相关文章
相关标签/搜索