Java学习 - 数据结构与算法 - 时间&空间复杂度

前言

文章以算法为起点,阐述了算法是用于操作数据和解决程序问题的方法集合。它强调了即使面对同一问题,不同的算法也能实现相同结果,但它们在时间与资源消耗上存在显著差异。

文章首先定义了时间复杂度和空间复杂度,这两个概念是评估算法效率的两个主要维度。时间复杂度关注算法执行所需时间与输入数据规模之间的关系,而空间复杂度则关注算法执行过程中对存储空间的需求。

文章对常见的排序算法进行了总结,包括它们的时间复杂度和空间复杂度。排序算法是算法设计中的一个经典问题,通过比较不同排序算法的效率,可以更直观地展示时间复杂度和空间复杂度的实际应用。

时间复杂度

定义

若存在函数 f ( n ) f(n) f(n),使得当 n n n 趋向无穷大时, T ( n ) / f ( n ) T(n) / f(n) T(n)/f(n) 的极限值为不等于 0 的常数,则称 f ( n ) f(n) f(n) T ( n ) T(n) T(n) 的同数量级函数,记作 T ( n ) = O ( f ( n ) ) T(n)=O(f(n)) T(n)=O(f(n)),称 O ( f ( n ) ) O(f(n)) O(f(n)) 为算法的 渐进时间复杂度,简称 时间复杂度,用大 O 来表示,称为 大 O 表示法

推导时间复杂度的原则

  1. 若运行时间是常数量级,则用常数 1 表示
  2. 只保留时间函数中最高阶项,如 O ( n 2 + 4 n ) O(n^2 + 4n) O(n2+4n),保留最高阶项后,成为 O ( n 2 ) O(n^2) O(n2)
  3. 若最高阶项存在,则省去最高阶项前的系数,如 O ( 4 n 2 ) O(4n^2) O(4n2),省去最高阶项的系数后,成为 O ( n 2 ) O(n^2) O(n2)

分析时间复杂度的方法

总结起来,对于如何分析一段代码的时间复杂度,主要有如下 3 个实用方法:

  1. 只关注循环执行次数最多的一行代码;
  2. 加法原则:总复杂度等于量度最大的那段代码的复杂度;
  3. 乘法原则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

常见的时间复杂度曲线

在这里插入图片描述

常见时间复杂度

O ( 1 ) O(1) O(1)

即无论执行多少行,都不会影响到其他区域,此时代码的复杂度就是 O ( 1 ) O(1) O(1),如下面的代码中,假设执行每行代码时间都相同切为 t t t,则 2,3 行各需 1 个执行时间,即为 t + t = 2 t t + t = 2t t+t=2t。此时执行时间复杂度为常数。

void sayHello(String name){
    System.out.prinln("Hello, " + String);
    System.out.prinln("欢迎关注我的公众号:【村雨遥】");
}
O ( l o g n ) O(log n) O(logn)

如下列二分查找代码中,通过 while 循环,能够成倍的缩减搜索范围,假设需要 x 次才能跳出循环,则有 num * 2 * 2 * ... = n ,其中 num 是常数,有 n 个 2 相乘,则有 n u m ∗ 2 x = n num * 2 ^x = n num2x=n,从而推出 x = l o g 2 ( n / n u m ) x = log_2(n/num) x=log2(n/num) ,因此时间复杂度用大 O 表示法表示为 O ( l o g n ) O(log n) O(logn)

int binarySearch(int[] arr, int target){
    int left = 0;
    int right = arr.length - 1;
    while(left <= right){
        int middle = left + (left - right) / 2;
        if(arr[middle] == target){
            return middle;
        }else if(arr[middle] > target){
            right = middle - 1;
        }else {
            left = middle + 1;
        }
    }

    return -1;
}
O ( n ) O(n) O(n)

如下面这段代码中,for 循环中的代码被执行了 arr.length 次,因此所需要的时间和数组长度成正比的,因此可以用 O ( n ) O(n) O(n) 来表示它的时间复杂度。利用上述推到原则和分析的方法,可以知道下面代码中循环次数最多的是 4,5 行,总的执行时间是 O ( 2 n ) O(2n) O(2n),抛去系数后,得到最终时间复杂度 O ( n ) O(n) O(n).

int sum(int[] arr){
    int total = 0;

    for(int i = 0; i < arr.length; i++){
        total += arr[i];
    }

    return total;
}
O ( n l o g n ) O(n log n) O(nlogn)

如果我们将一个复杂度为 O ( l o g n ) O(logn) O(logn) 的代码重复执行 n n n 次,那么此时代码的复杂度不就变成 O ( n l o g n ) O(nlogn) O(nlogn) 了吗。

void hello (int n){
    for( int i = 1 ; i < n ; i++){
        int m = 1;
        while( m < n ){
            m *= 2;
        }
    }
}
O ( n 2 ) O(n^2) O(n2)

假设我们将时间复杂度为 O ( n ) O(n) O(n) 的代码重复执行 n n n 次,那么此时的时间复杂度就是 n ∗ O ( n ) n*O(n) nO(n),即可表示为 O ( n 2 ) O(n^2) O(n2),表现出来就是双重循环的形式。

void selectionSort(int[] arr, int n){
    for(int i = 0; i < n; i++){
        int min = i;
        for(int j = i + 1; j < n; j++){
            if(arr[j] < arr[min]){
                int tmp = arr[i];
                arr[i] = arr[j];
                arr[j] = tmp;
            }
        }
    }
}
O ( n 3 ) O(n^3) O(n3)

O ( n 2 ) O(n^2) O(n2),类似,将时间复杂度为 O ( n 2 ) O(n^2) O(n2) 的代码嵌套循环一次,此时复杂度就变成了 O ( n 3 ) O(n^3) O(n3),表现出来就是三重循环嵌套的形式。

void demo(int n){
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            for(int k = 0; k < n; k++){
                System.out.print("Hello, World");
            }
            System.out.print("------");
        }
        System.out.print("******");
    }
}
O ( n ! ) O(n!) O(n!)

虽然理论上存在时间复杂度为 O ( n ! ) O(n!) O(n!) 的算法,但实践中基本遇不到,所以这里就不展开了。

空间复杂度

定义

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度(即除开原始序列大小的内存,在算法过程中用到的额外的存储空间),反映的对内存占用的趋势,而不是具体内存,也叫作 渐进空间复杂度表示算法的存储空间与数据规模间的增长关系,用 S ( n ) S(n) S(n) 来代替;

常用空间复杂度

O ( 1 ) O(1) O(1)

算法执行所需临时空间不随某一变量 n 的大小而变化,则该算法空间复杂度为一个常量,表示为 S ( n ) = O ( 1 ) S(n) = O(1) S(n)=O(1)

int num1 = 1;
int num2 = 2;
int total = num1 + num2;
O ( n ) O(n) O(n)

数组占用内存大小为 n,而且后续未分配新的空间,因此该算法空间复杂度为 S ( n ) = O ( n ) S(n) = O(n) S(n)=O(n)

int[] arr = new int[n];
O ( n 2 ) O(n^2) O(n2)

二维数组的情况;

int[][] arr = new int[n][n];

常见排序算法的时间复杂度和空间复杂度

对于面试中常见的的排序算法,以下总结给出了其时间复杂度以及空间复杂度,以及算法稳定性。

排序算法平均时间复杂度最好时间复杂度最坏时间复杂度空间复杂度稳定性
插入排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)稳定
希尔排序 O ( n 1.3 ) O(n^{1.3}) O(n1.3) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)不稳定
选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)不稳定
堆排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( 1 ) O(1) O(1)不稳定
冒泡排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)稳定
快速排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n 2 ) O(n^2) O(n2) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)不稳定
归并排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n ) O(n) O(n)稳定
计数排序 O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k)稳定
桶排序 O ( n + k ) O(n+k) O(n+k) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( n + k ) O(n+k) O(n+k)稳定
基数排序 O ( n ∗ k ) O(n*k) O(nk) O ( n ∗ k ) O(n*k) O(nk) O ( n ∗ k ) O(n*k) O(nk) O ( n + k ) O(n+k) O(n+k)稳定

总结

今天的文章首先对时间复杂度进行了定义,解释了它是如何描述算法执行时间随输入规模增长的变化趋势的。接着,文章介绍了推导时间复杂度的原则,帮助读者理解如何从算法的执行步骤中抽象出其时间复杂度。

文章还列举了常见的时间复杂度类别,如 (O(1))、(O(\log n))、(O(n))、(O(n \log n))、(O(n^2)) 等,并通过实例展示了它们在不同算法中的应用。

在空间复杂度方面,文章定义了空间复杂度,解释了它如何衡量算法在执行过程中对存储空间的需求,并介绍了一些常见的空间复杂度类别。

文章的最后部分对常见的排序算法进行了总结,包括它们的时间和空间复杂度,通过今天的学习,我们不仅了解了如何评估算法的效率,还学会了如何根据时间和空间复杂度来选择最适合问题的算法。希望这篇文章能够帮助你在算法设计和分析方面取得进步。如果你觉得有收获,别忘了点赞支持哦!

  • 15
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值