算法时间复杂度分析

前言

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

到底什么是大 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(关注公众号,回复:黑客帝国,获取彩蛋)

更多内容欢迎大家关注
算法时间复杂度分析_第1张图片

你可能感兴趣的:(算法时间复杂度分析)