【数据结构-JAVA】时间复杂度和空间复杂度

目录

1. 如何衡量一个算法的好坏

    1.1 何为算法

    1.2 算法效率

2. 时间复杂度

    2.1 时间复杂度的概念

    2.2 大O的渐进表示法

    2.3 计算时间复杂度的练习

        2.3.1 练习1

        2.3.2 练习2

        2.3.3 练习3

        2.3.4 练习4

        2.3.5 练习5

        2.3.6 练习6

        2.3.7 练习7 

        2.3.8 练习8

3. 空间复杂度


1. 如何衡量一个算法的好坏

1.1 何为算法

算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。

1.2 算法效率

算法效率分析分为两种:第一种是时间效率,第二种是空间效率。时间效率被称为时间复杂度,而空间效率被称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间, 在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

2. 时间复杂度

2.1 时间复杂度的概念

时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个数学函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但这里边存在一个问题:同一个算法,不同的电脑去运行,由于硬件上存在的差异,会得到不同的运行时间。因此这样得到的时间,并不能作为该算法运行时间普适性的参考。所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比,算法中的基本操作的执行次数,为算法的时间复杂度。

2.2 大O的渐进表示法

实际中我们计算时间复杂度时,我们并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。 大O符号(Big O notation):是用于描述函数渐进行为的数学符号。

在使用大O的渐进表示法之前,得先把算法中的语句执行次数精确计算好,之后再利用下面的内容对精确的执行次数进行修改:

大O的渐进表示法的具体内容:

1. 常数部分(意味着每次运行,执行次数都是相同的),以 1 来替换

2. 执行次数为 n 的函数时(意味着运行次数随着输入 n 的变化而变化),保留最高阶

3. 如果最高阶的系数不为 1 ,那么除去这个常数,得到的结果就是大O阶。

2.3 计算时间复杂度的练习

2.3.1 练习1

// 请计算一下func1基本操作执行了多少次?
    void func1(int N){
        int count = 0;
        for (int i = 0; i < N ; i++) {
            for (int j = 0; j < N ; j++) {
                count++;
            }
        }
        for (int k = 0; k < 2 * N ; k++) {
            count++;
        }
        int M = 10;
        while ((M--) > 0) {
            count++;
        }
        System.out.println(count);
    }

 func1 的基本执行次数为:

N * N + 2 * N + 10

 使用大O的渐进表示法以后,Func1的时间复杂度为:

 O(N²)

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。

另外有些算法的时间复杂度存在最好、平均和最坏情况:

最坏情况:任意输入规模的最大运行次数(上界)

平均情况:任意输入规模的期望运行次数

最好情况:任意输入规模的最小运行次数(下界)

例如:在一个长度为N数组中搜索一个数据x

最好情况:1次找到

最坏情况:N次找到

平均情况:N/2次找到(查找每个数据所需的次数和,再除以长度N:

(1 + 2 + 3 + ...... + N) / N = (1 + N) / 2        )

在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N) 。

2.3.2 练习2

// 计算func2的时间复杂度?
    void func2(int N) {
        int count = 0;
        for (int k = 0; k < 2 * N ; k++) {
            count++;
        }
        int M = 10;
        while ((M--) > 0) {
            count++;
        }
        System.out.println(count);
    }

 func2 的基本运行次数为:

2 * N + 10

 大O的渐进表示法:

O(N)

2.3.3 练习3

// 计算func3的时间复杂度?
    void func3(int N, int M) {
        int count = 0;
        for (int k = 0; k < M; k++) {
            count++;
        }
        for (int k = 0; k < N ; k++) {
            count++;
        }
        System.out.println(count);
    }

func3 的运行次数为:

M + N

 大O渐进表示法:

O(M+N)

2.3.4 练习4

    // 计算func4的时间复杂度?
    void func4(int N) {
        int count = 0;
        for (int k = 0; k < 100; k++) {
            count++;
        }
        System.out.println(count);
    }

func4 运行次数为: 

100

大O渐进表示法:

 O(1) 

2.3.5 练习5

    // 计算bubbleSort的时间复杂度?
    void bubbleSort(int[] array) {
        for (int end = array.length; end > 0; end--) {
            boolean sorted = true;
            for (int i = 1; i < end; i++) {
                if (array[i - 1] > array[i]) {
                    Swap(array, i - 1, i);
                    sorted = false;
                }
            }
            if (sorted == true) {
                break;
            }
        }
    }

 func5 运行次数为: 

(n-1)+......+1 = n(n-1)/2

大O的渐进表达式:

 O(N²)

2.3.6 练习6

    // 计算binarySearch的时间复杂度?
    int binarySearch(int[] array, int value) {
        int begin = 0;
        int end = array.length - 1;
        while (begin <= end) {
            int mid = begin + ((end-begin) / 2);
            if (array[mid] < value)
                begin = mid + 1;
            else if (array[mid] > value)
                end = mid - 1;
            else
                return mid;
        }
        return -1;
    }

  二分查找的查找次数:

(log2N) + 1

 因此二分查找的时间复杂度为:

(log2N)

2.3.7 练习7 

    // 计算阶乘递归factorial的时间复杂度?
    long factorial(int N) {
        return N < 2 ? N : factorial(N-1) * N;
    }

递归执行的总次数 = 递归的次数 * 每次递归后代码执行的次数 

 factorial 的时间复杂度为:

O(N)

2.3.8 练习8

    // 计算斐波那契递归fibonacci的时间复杂度?
    int fibonacci(int N) {
        return N < 2 ? N : fibonacci(N-1)+fibonacci(N-2);
    }

 Fibonacci 的时间复杂度为:

O(2^N)

【数据结构-JAVA】时间复杂度和空间复杂度_第1张图片

 由于每个斐波那契数都依赖前两项,那么在第一个斐波那契数之前,都会按照上图递归。虽然只有最左边一条分支一直到第 N 行才递归到 F(1),而其余的都在第 N 行以前就递归到 F(1),但我们算的是时间复杂度,可以是一个大概的值。如果以后有更好的更精确的算法,再继续补充至下文。

1 + 2 + 2^{2} + 2^{3} + ...... + 2^{N-1} = 2^{N} - 1

3. 空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少 bytes 的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。

示例1:

    // 计算bubbleSort的空间复杂度?
    void bubbleSort(int[] array) {
        for (int end = array.length; end > 0; end--) {
            boolean sorted = true;
            for (int i = 1; i < end; i++) {
                if (array[i - 1] > array[i]) {
                    Swap(array, i - 1, i);
                    sorted = false;
                }
            }
            if (sorted == true) {
                break;
            }
        }
    }

这里,临时占用存储空间的只有一个 sorted 变量,因此空间复杂度为 :

O(1)

 示例2:

    // 计算fibonacci的空间复杂度?
    int[] fibonacci(int n) {
        long[] fibArray = new long[n + 1];
        fibArray[0] = 0;
        fibArray[1] = 1;
        for (int i = 2; i <= n ; i++) {
            fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
        }
        return fibArray;
    }

空间复杂度为:

O(N)

示例3:

    // 计算阶乘递归Factorial的空间复杂度?
    long factorial(int N) {
        return N < 2 ? N : factorial(N-1)*N;
    }

 空间复杂度为:

O(N)

示例4:

    // 计算斐波那契递归fibonacci的空间复杂度?
    int fibonacci(int N) {
        return N < 2 ? N : fibonacci(N-1)+fibonacci(N-2);
    }

 空间复杂度为:

O(N)

你可能感兴趣的:(Java,java,开发语言)