美文网首页
算法时间复杂度分析

算法时间复杂度分析

作者: yuandatou | 来源:发表于2018-09-27 22:44 被阅读52次

    前言

    虽然写完程序在计算机上跑一遍。就知道代码执行的效率,但这都属于事后统计法,不同配置的平台处理效率各不相同,局限性太大,要想编写出能高效运行的程序,我们就需要考虑到算法的效率,分析算法的复杂度。算法复杂度包括时间复杂度,和空间复杂度,下面我们重点看下时间复杂度。

    到底什么是大 O ?

    T(n)=O(f(n))

    T(n):代码执行时间(估计)
    f(n): 代码执行的总次数
    T(n)=O(f(n)) 表示代码执行的时间和代码执行的总次数成正比。
    下面来看一个例子:

    int print(int n) {
        for(int i = 0; i<n; i++) {         // 需要执行 (n + 1) 次
           System.out.println("Hello, Algorithm!");      // 需要执行 n 次
        }
        return 0;       // 需要执行 1 次
    }
    

    上面方法中代码执行总次数f(n) = (n+1+n+1),执行总时间T(n)=O(2n+2),这就是大 O 时间复杂度表示法。大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模n增长的变化趋势。

    时间复杂度分析

    大 O 这种复杂度表示方法只是表示一种变化趋势。我们通常会忽略掉公式中的常量、低阶、系数,只需要记录一个最大阶的量级就可以了。所以下面看看常见时间复杂度的分析方法。

    1.加法法则:总复杂度等于量级最大的那段代码的复杂度

    如果 T1(n)=O(f(n)),T2(n)=O(g(n)),那么 T(n)=T1(n)+T2(n)=max(O(f(n)), O(g(n))) =O(max(f(n), g(n))).

    void print(int n) {
        // 第一部分时间复杂度为 O(n^2)
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                 System.out.println("Hello, Algorithm!");
            }
        }
        // 第二部分时间复杂度为 O(n)
        for(int j = 0; j < n; j++) {
             System.out.println("Hello, Algorithm!");
        }
    }
    

    此例时间复杂度为 max(O(n^2), O(n)),即 O(n^2)。

    2. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

    如果 T1(n)=O(f(n)),T2(n)=O(g(n));那么T(n)=T1(n)*T2(n)=O(f(n))*O(g(n))=O(f(n)*g(n))

    void print(int n) {
      for(int i = 0; i < n; i++) {  //时间复杂度为 O(n)
             f(i)
        }
     } 
     
     void f(int n) {
       for(int i = 0; i < n; i++) { //时间复杂度为 O(n)
             System.out.println("Hello, Algorithm!");
        }
     }
    
    

    我们单独看 print() 函数。假设 f() 只是一个普通的操作,那print()函数的时间复杂度就是,T1(n) = O(n)。但 f() 函数本身不是一个简单的操作,它的时间复杂度是 T2(n) = O(n),所以,整个 print() 函数的时间复杂度就是,T(n) = T1(n) * T2(n) = O(n*n) = O(n^2)

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

    我们在分析一个算法、一段代码的时间复杂度的时候,也只关注循环执行次数最多的那一段代码就可以了。这段核心代码执行次数的 n 的量级,就是整段要分析代码的时间复杂度。

    void print(int n) {
        if (n >= 0) {
            // 第一条路径时间复杂度为 O(n^2)
            for(int i = 0; i < n; i++) {
                for(int j = 0; j < n; j++) {
                 System.out.println("输入数据大于等于零\n");
                }
            }
        } else {
            // 第二条路径时间复杂度为 O(n)
            for(int j = 0; j < n; j++) {
                System.out.println("输入数据小于零\n");
            }
        }
    }
    
    

    此时时间复杂度为 max(O(n^2), O(n)),即 O(n^2)

    时间复杂度分析的基本策略是:从内向外分析,从最深层开始分析。如果遇到函数调用,要深入函数进行分析。

    常见时间复杂度案例分析

    需要注意的点:
    看到是双层循环,不一定都是O(n^2)级别的算法。

    public class Main {
    
        // O(1)
        /*下面这个方法是交换一个数组中的两个元素,是一个常数阶算法,
          因为没有涉及到数据规模的变化*/
        private static void swap(Object[] arr, int i, int j){
    
            if(i < 0 || i >= arr.length)
                throw new IllegalArgumentException("i is out of bound.");
    
            if(j < 0 || j >= arr.length)
                throw new IllegalArgumentException("j is out of bound.");
    
            Object temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    
        // O(n)
        /*下面这个方法是一个典型的O(n)级别的算法,
        O(n)级别的算法特征是包含一个循环,且循环的次数是和n相关的,
        代码执行次数可以表示为cn次,其中c是一个常数。*/
        private static int sum(int n){
    
            if(n < 0)
                throw new IllegalArgumentException("n should be greater or equal to zero.");
    
            int ret = 0;
            for(int i = 0 ; i <= n ; i ++)
                ret += i;
            return ret;
        }
        //下面这个方法是将以一个字符串数组翻转过来,
       /*就是将第一个元素和最后一个元素交换位置,
         第二个元素和倒数第二个元素交换位置,依次类推。只需执行n/2次。
        所以也是O(n)级别的算法*/
        private static void reverse(Object[] arr){
    
            int n = arr.length;
            for(int i = 0 ; i < n / 2 ; i ++ )
                swap(arr, i, n - 1 - i);
        }
    
        // O(n^2) Time Complexity
       /*下面这个方法是选择排序,是一个O(n^2)级别的算法,
        O(n^2)级别的算法的特征是包含一个双重循环
        执行次数分析:(n-1)+(n-2)+(n-3)+(n-4)=1/2*n^2-1/2*n,
        结合前面的分析。得出这是一个O(n^2)级别的算法*/
        private static void selectionSort(Comparable[] arr, int n){
    
            for(int i = 0 ; i < n ; i ++){
                int minIndex = i;
                for(int j = i + 1 ; j < n ; j ++)
                    if(arr[j].compareTo(arr[minIndex]) < 0)
                        minIndex = j;
    
                swap(arr, i, minIndex);
            }
        }
    
        // O(n) Time Complexity
       /*但双重循环未必是O(n^2)级别的算法,如下面这个方法,
        内循环就是和n无关的,是一个固定次数的循环。
        总的执行次数为30n次,所以是O(n) 级别的算法*/
        private static void printInformation(int n){
    
            for( int i = 1 ; i <= n ; i ++ )
                for( int j = 1 ; j <= 30 ; j ++ )
                    System.out.println("Class " + i + " - " + "No. " + j);
        }
    
        // O(logn) Time Complexity
        /*二分查找法
        在有序数组中,我们要找到一个目标值,数组长度为n,
        我们首先在n个元素中查找,比较数组中间位置的那个元素,
        之后在n/2个元素中查找,再到n/4个元素中查找...每次扔掉一半的元素,
        最终在1个元素中查找。那么最多执行多少次呢,
        即就是n经过几次“除以2”操作等于1.由高中数学可知需要经过log2n次。
        所以时间复杂度O(log2n),即为O(logn)。
        注意:不管是以2为底、以3为底,还是以10为底,
        我们可以把所有对数阶的时间复杂度都记为 O(logn)。为什么呢?
        因为他们之间通过一个常数系数便可互相转化。
        而我们采用大O法标记时间复杂度时忽略系数。(ps:注释中的log2n,2为底数)*/
    
    
        private static int binarySearch(Comparable[] arr, int n, int target){
    
            int l = 0, r = n-1;
            while( l <= r ){
                int mid = l + (r-l)/2;
                if(arr[mid].compareTo(target) == 0) return mid;
                if(arr[mid].compareTo(target) > 0) r = mid - 1;
                else l = mid + 1;
            }
            return -1;
        }
    
        private static String intToString(int num){
    
            StringBuilder s = new StringBuilder("");
            String sign = "+";
            if(num < 0){
                num = -num;
                sign = "-";
            }
    
            while(num != 0){
                s.append(Character.getNumericValue('0') + num % 10);
                num /= 10;
            }
    
            if(s.length() == 0)
                s.append('0');
    
            s.reverse();
            if(sign == "-")
                return sign + s.toString();
            else
                return s.toString();
        }
    
    
        // O(nlogn)
        private static void hello(int n){
    
            for( int sz = 1 ; sz < n ; sz += sz )
                for( int i = 1 ; i < n ; i ++ )
                    System.out.println("Hello, Algorithm!");
        }
    
    
        // O(sqrt(n)) Time Complexity
        //经过sqrt(x)次操作等于num,所以时间复杂度是O(sqrt(n))
        private static boolean isPrime(int num){
    
            for(int x = 2 ; x*x <= num ; x ++)
                if( num % x == 0 )
                    return false;
            return true;
        }
    
        private static boolean isPrime2(int num){
    
            if( num <= 1 ) return false;
            if( num == 2 ) return true;
            if( num % 2 == 0 ) return false;
    
            for(int x = 3 ; x * x <= num ; x += 2)
                if( num%x == 0 )
                    return false;
    
            return true;
        }
    
        public static void main(String[] args) {
    
            System.out.println(intToString(123));
            System.out.println(intToString(0));
            System.out.println(intToString(-123));
    
            System.out.println();
    
            if(isPrime2(137)) System.out.println("137 is a prime.");
            else System.out.println("137 is not a prime.");
    
            if(isPrime2(121)) System.out.println("121 is a prime.");
            else System.out.println("121 is not a prime.");
        }
    }
    

    ps(扫描二维码,回复:黑客帝国,获取彩蛋)

    更多内容欢迎大家关注

    yuandatou

    相关文章

      网友评论

          本文标题:算法时间复杂度分析

          本文链接:https://www.haomeiwen.com/subject/ulgyoftx.html