zoukankan      html  css  js  c++  java
  • 复杂度分析

      同一个问题可以使用不同的算法解决,那么不同的算法孰优孰劣如何区分呢?因此我们需要一个表示方法来代表每个程序的效率。

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

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

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

      不过我们虽然无法预估代码的绝对执行时间,但是我们可以预估代码基本的执行次数。

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

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

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

    时间复杂度

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

    常数时间 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来存储数据,存储数据自然要占用内存空间,而占用的空间大小完全取决于传入数组大小。

      我们之所以说第二种解法更优,其实是一种常规思想,因为现实中绝大部分情况,时间复杂度显然比空间复杂度更为重要,我们宁愿多分配一些存储空间作为代价,来提升程序的执行速度。

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

      最后:感谢阅读。

  • 相关阅读:
    什么是ORM
    ORM优缺点
    Azure 中快速搭建 FTPS 服务
    连接到 Azure 上的 SQL Server 虚拟机(经典部署)
    在 Azure 虚拟机中配置 Always On 可用性组(经典)
    SQL Server 2014 虚拟机的自动备份 (Resource Manager)
    Azure 虚拟机上的 SQL Server 常见问题
    排查在 Azure 中新建 Windows 虚拟机时遇到的经典部署问题
    上传通用化 VHD 并使用它在 Azure 中创建新 VM
    排查在 Azure 中新建 Windows VM 时遇到的部署问题
  • 原文地址:https://www.cnblogs.com/fengyumeng/p/10918037.html
Copyright © 2011-2022 走看看