Java时间复杂度和空间复杂度(详解)

简介: Java时间复杂度和空间复杂度(详解)



1.复杂度分析

当我们设计一个算法时,怎样衡量其好坏?

算法在编写为可执行程序后,运行时需要耗费时间资源和空间(内存)资源。因此,衡量一个算法的好坏,一般是从时间空间两个方面来衡量。

2.时间复杂度

一个算法执行所需要的时间,我们可以将代码跑一遍得到具体的时间,但是如果每一个算法都测试一遍的话,十分耗费时间和精力,而且,其测试的结果高度依赖于测试环境,测试环境中的硬件不同会影响测试结果,测试数据的规模也会影响测试结果。

如果我们假设每行代码执行的时间都相同,那么此时影响时间的因素为语句的执行次数,即时间与其语句的执行次数成正比例

时间复杂度:算法中的基本操作的执行次数

public static int factorial(int N){
        int ret = 1;
        for (int i = 2; i <= N; i++) {
            ret *= i;
        }
        return ret;
    }

上述代码用来求n的阶乘,其中,在for前的代码每行执行了一次,在for循环(i++)及其中的代码(ret *= i)每行执行了N-1次,那么该代码总执行了(1+2*(N-1))次,即(2*N-1)次,代码具体执行了多少次,与传入数据n相关。

当 N = 100 时,代码执行 199 次

当 N = 1000时,代码执行 1999次

当 N = 10000时,代码执行 19999次

……

N越大,常数 -1,系数2对执行次数的影响越小。

因此,我们在计算时间复杂度时,并不需要计算精确的执行次数,只需要计算出其大概执行的次数,我们用大O的渐进表示法来表示

大O的渐进表示法

大O符号(Big O notation):用于描述函数渐进行为的数学符号

推导大O阶方法:

1.用常数1取代运行时间中所有的加法常数

2.在修改后的运行次数函数中,只保留最高阶项

3.如果最高阶项存在且不是1,则去除与这个项目相乘的常数

例如,上述求阶乘的代码中,其执行次数为 2*N-1,用大O的渐进表示法,去掉对结果影响不大的项 -1,最高阶项去除与其相乘的常数,即为O(N)

在算法中,存在不同的执行情况,

例如在长度为N的数组中查找数据x时,可能执行一次就找到,也可能执行n次也找不到

我们将其分为最好、平均和最坏情况

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

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

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

而我们一般关注的是算法的最坏运行情况

我们通过一些例子来进一步熟悉大O的渐进表示法:

由于我们需要去掉对结果影响不大的项,因此我们可以不再分析对结果影响较小的语句。

例1:

public static int add(int a, int b){
        int ret = a+b;
        return ret;
    }

上述代码一共执行两次,用常数1取代其中的常数,即为 O(1)

例2:

int fun(int N,int M){
        int count = 0;
        for (int i = 0; i < N; i++) {
            count++;
        }
        for (int i = 0; i < M; i++) {
            count++;
        }
        return count;
    }

上述代码一共有两个for循环,其中对执行次数影响最大(即执行次数最多)的语句为count++,共共执行了 N+M 次,

当 N 远大于 M时,时间复杂度为 O(N)

当 M 远大于 N时,时间复杂度为 O(M)

当 M 与 N 差不多大时,时间复杂度为 O(M+N)

由于没有说明N与M的大小关系,时间复杂度为 O(N+M)

例3:

void fun(int N){
        int count = 0;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < i; j++) {
                count += j;
            }
        }
    }

上述代码中有一个嵌套循环,其中对执行次数影响最大的语句为 count += j,我们对其进行分析

当 i = 0 时,语句执行 0 次,

当 i = 1 时,语句执行 1 次,

当 i = 2 时,语句执行 2 次,

……

当 i = N-1时,语句执行 N-1次

则总执行次数为 0 + 1 + 2 + …… + N-1,利用等差数列求和公式,可得结果为 ,最高阶项为 ,去掉系数,时间复杂度为 O(N^2)

例4:

void fun(int M){
        int count = 0;
        for (int i = 0; i < 10; i++) {
            count++;
        }
    }

时间复杂度为O(1)

注意!count++ 语句一共执行了10次,与M没有关系

例5:

public static int binarySearch(int[] arr, int x){
        if(arr.length == 0){
            return -1;
        }
        int left = 0;
        int right = arr.length-1;
        while(left <= right){
           int mid = left + ((right-left)>>1);
            if(arr[mid] == x){
                return mid;
            }else if(arr[mid] > x){
                right = mid;
            }else{
                left = mid+1;
            }
        }
        return -1;
    }

上述代码为二分查找,考虑最坏的情况,即查找不到:

我们先分析二分查找是如何查找数据的:

二分查找的前提是被查找的数组arr有序,通过确定中间位置mid,将数组分为两个部分,若被查找值x < arr[mid],则排除右边部分值,在左边继续进行二分查找;若x > arr[mid],则排除左边部分值,在右边继续进行二分查找;若 x = arr[mid],则找到被查找值,返回即可。

过程如下图所示:

由于一次二分查找会排除数组一半的元素,即

N/2

N/4

N/8

……

1 当结果为1时循环结束

因此 1*2*2*……*2 = N,即 2^x = N,则执行次数x =

时间复杂度为

例6:

long factorial(int N){
        return N < 2? N: factorial(N-1)*N;
    }

上述递归的结束条件为 N < 2,当N >= 2时,会一直递归下去:

N

N-1

N-2

……

2

1 此时递归结束

递归的次数为N,因此时间复杂度为O(N)

例7:

int fibonacci(int N){
        return N < 2? N: fibonacci(N-1)+ fibonacci(N-2);
    }

斐波那契递归,可将其看成一个二叉树,将其递归时开辟的栈帧看作一个节点,每一个节点就是一次函数调用,则递归的时间复杂度为二叉树结点个数,具体递归过程为:

其中最后一层可能不满,由于计算时间复杂度只需要计算出其大概执行的次数,最后一层的空缺对计算的影响可以忽略不计

则递归的次数为Fib(N) = 2^0 + 2^1 + ……+ 2^(N-1)

利用等比数列求和公式,可得2^n - 1,则时间复杂度为O(2^N)

3.空间复杂度

空间复杂度是对一共算法在运行过程中临时占用存储空间大小的量度。空间复杂度计算的是变量的个数,其计算规则基本与时间复杂度类似,也使用大O的渐进表示法

例1:

void fun(int N){
        int count = 0;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < i; j++) {
                count += j;
            }
        }
    }

其中开辟了常数个额外的变量(count、i、j),因此空间复杂度为O(1)

例2:

int[] fun(int N){
        int[] arr = new int[N];
        for (int i = 0; i < N; i++) {
            arr[i] = i;
        }
        return arr;
    }

开辟了一共大小为N的整形数组和常数个额外的变量,因此空间复杂度为O(N)

例3:

long factorial(int N){
        return N < 2? N: factorial(N-1)*N;
    }

上述代码递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间,因此空间复杂度为O(N)

目录
相关文章
|
2月前
|
机器学习/深度学习 算法 搜索推荐
让星星⭐月亮告诉你,Java冒泡排序及其时间复杂度计算
冒泡排序是一种简单的排序算法,通过多次遍历数组,每次比较相邻元素并交换位置,将较小的元素逐步移至数组前端。第一轮结束后,最小值会位于首位;第二轮则将次小值置于第二位,依此类推。经过 (n-1) 轮遍历后,数组完成排序。冒泡排序的时间复杂度为 O(n²),在最优情况下(已排序数组)时间复杂度为 O(n)。示例代码展示了如何实现冒泡排序。
53 1
|
7月前
|
Java
java如何实现一个高效的二叉搜索树(BST)?请给出时间复杂度分析。
这段内容介绍了Java中使用AVL树实现高效二叉搜索树的方法。AVL树是一种自平衡树,节点子树高度差不超过1,确保操作时间复杂度为O(log n)。代码包括了`Node`类和`AVLTree`类,实现了节点、插入、删除、查找和平衡旋转等方法。通过旋转操作,维持树的平衡,保证了搜索效率。
44 6
|
7月前
|
存储 缓存 Java
java如何实现一个LRU(最近最少使用)缓存? 要求:设计一个LRU缓存,支持get和put操作。当缓存满时,需要淘汰最近最少使用的元素。要求使用双向链表+哈希表的数据结构来实现,并保证get和put操作的时间复杂度为O(1)。
java如何实现一个LRU(最近最少使用)缓存? 要求:设计一个LRU缓存,支持get和put操作。当缓存满时,需要淘汰最近最少使用的元素。要求使用双向链表+哈希表的数据结构来实现,并保证get和put操作的时间复杂度为O(1)。
67 1
|
7月前
|
算法 Java
给定一个字符串数组,如何找到其中最长的回文子串? 要求:编写一个Java函数,输入一个字符串数组,输出其中最长的回文子串。要求时间复杂度为O(n^2)。可以考虑使用动态规划或中心扩展的方法来优化算法。
给定一个字符串数组,如何找到其中最长的回文子串? 要求:编写一个Java函数,输入一个字符串数组,输出其中最长的回文子串。要求时间复杂度为O(n^2)。可以考虑使用动态规划或中心扩展的方法来优化算法。
88 1
|
算法 Java
Java实现-数据结构 2.时间和空间复杂度
Java实现-数据结构 2.时间和空间复杂度
74 0
|
算法 Java
Java时间复杂度与空间复杂度
Java时间复杂度与空间复杂度
150 0
|
机器学习/深度学习 算法 Java
【Java】如何提高算法效率——时间复杂度和空间复杂度(一)
当我们学习编程语言到达一定程度之后,就会开始注重代码的效率,这时候就会开始研究算法这么个东西,算法顾名思义就是计算方法,就好比你做一道数学题,有简单的办法也有麻烦的办法,但是简单的办法不好理解,在代码里这个叫做可读性差,而麻烦的办法虽然麻烦,但是方便理解,可读性好。在算法里也有两个很重要的因素,时间复杂度和空间复杂度,不同的算法有不同的特点,根据需求应用合适的算法,才是真正提高代码效率的真谛,请往下看
【Java】如何提高算法效率——时间复杂度和空间复杂度(一)
|
算法 Java
【Java】如何提高算法效率——时间复杂度和空间复杂度(二)
当我们学习编程语言到达一定程度之后,就会开始注重代码的效率,这时候就会开始研究算法这么个东西,算法顾名思义就是计算方法,就好比你做一道数学题,有简单的办法也有麻烦的办法,但是简单的办法不好理解,在代码里这个叫做可读性差,而麻烦的办法虽然麻烦,但是方便理解,可读性好。在算法里也有两个很重要的因素,时间复杂度和空间复杂度,不同的算法有不同的特点,根据需求应用合适的算法,才是真正提高代码效率的真谛,请往下看
|
机器学习/深度学习 算法 Java
【Java】如何提高算法效率——时间复杂度和空间复杂度
【Java】如何提高算法效率——时间复杂度和空间复杂度
【Java】如何提高算法效率——时间复杂度和空间复杂度
|
14天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####