简约理解算法复杂度

目录

时间复杂度和空间复杂度

大O表示法

如何快速求解出复杂度呢?

复杂度的本质

常量

常见大O表示法

降低复杂度


人类为了能够加深对于事物的理解,通常会引用认为定义的概念作为衡量的标准,对所要研究的事物进行分析判断。物理学里,为了能够更好地描述一定距离下,所花费的时间,引入了速度的概念。同样,对于程序运行的性能而言,引入了复杂度用以描述程序运行的快慢问题。


时间复杂度和空间复杂度

复杂度分为时间复杂度和空间复杂度。时间复杂度,就是一段程序中,n个元素需要执行的操作次数。空间复杂度,可以理解为程序运行时,需要计算机分配多大的内存空间执行程序。栈便是一个很好的理解空间复杂度例子。


大O表示法

为了便于表示复杂度,计算机科学使用大O表示法来符号化复杂度,大O表示法为O(n)。O就是英文单词Operation的首字母,其含义为操作。n则表示操作次数。

以循环遍历数组为例,解释复杂度是如何表示的。为了便于理解,借助伪代码加以说明。

class Example {
    public void print(int[] nums) {
        for(int i=0;i<nums.length();i++){
            System.out.print(nums[i]);
        }
    }
}

假设数组nums的元素个数为n。一次循环遍历nums数组,每个元素输出打印一次,则可以理解包含元素个数为n的数组nums,一次循环遍历,需要执行n此操作,每一个元素执行一次输出操作,故时间复杂度为O(n)。数组的一次循环遍历,程序中数组的长度,就是算法的空间复杂度,即空间复杂度为O(n)。

对于空间复杂度而言,只需要把握两个原则:

1、数组的长度

一旦程序中有数组,则数组的长度,就是算法的空间复杂度

2、递归的深度

如果使用递归函数,递归最深的深度,就是算法的空间复杂度

如果程序中,既有数组,又是递归,那么取两者中的最高空间复杂度


如何快速求解出复杂度呢?

实际上,非常简单,一个函数或者一段代码,其内部针对操作的元素,执行了多少次操作,那么,执行的操作次数就是其程序的时间复杂度。空间复杂度,也即是这段程序所占用的空间深度。

这里需要提醒的是,当程序中包含多个复杂度时,以复杂度最高的运算为准。

class Example {
    public void print(int[] nums) {
        for(int i=0;i<nums.length();i++){
            System.out.print(nums[i]);
        }
        for(int i=0;i<nums.length();i++){
            for(int j=i;j<nums.length();j++){
                System.out.print(nums[j]);
            }
        }
    }
}

以上程序,包含数组一层循环遍历,也包含数组双重循环遍历,一层循环遍历的时间复杂度为O(n),双重循环遍历的时间复杂度为O(n^2),此时,函数的时间复杂度以最高的为准,即O(n^2)。


复杂度的本质

复杂度的本质,实际上,就是指出算法的运行有多快,并且其算法的运行时间如何随着输入元素的增加如何变化

文字上的叙述,有点难以理解,通过函数的图形化便能够很好理解了。

对于二分查找法,其时间复杂度为O(log n),这里先不解释,直接使用其概念即可。而对于以上数组的一次遍历,可以理解为普通算法,其时间复杂度为O(n)。以下列出二分查找法和普通算法之间运行时间上的比较,这里假设1次操作,花费1秒时间,实际上没有这么慢,只不过为了理解,便这样设定。

元素个数O(n)操作数O(log n)操作数O(n)运行时间O(log n)运行时间
1616416秒4秒
2562568256秒8秒
10241024101024秒10秒

通过表格,能够清晰地认识到,随着元素个数的增加,普通算法的操作数正比例增加,而对于二分查找法的对数函数而言,操作数增长较少。由此可见,复杂度不仅仅表示的是算法运行的快慢问题,而且还表示了随着输入元素个数的增长,算法运行时间是如何变化的。

看到这里,是不是有种似曾相识的感觉,复杂度很像速度和加速度的概念混合,既表示了算法的快慢,又表示了算法的快慢是如何随着数量增减而变化的。


常量

对于复杂度而言,实际上其大O表示法的表达式应为O(c * n + d),c和d表示算法的常量,可以为任意的数字。

当操作数的表达式不同时,常量的影响程度就非常小了。如果不理解,可以以二分查找法和普通算法的对比作为参考,即使常量不同,也无法改变两者在算法复杂度上的差距。假设二分查找法的常量为10,普通查找的常量为2,10log n和10n表达式,当n=2时,虽然元素个数很少,但普通算法依然比二分查找法要花费更多的运行时间。那么,当n很大时,差距就更加明显了。

当针对相同的表示式时,常量不同,算法运行的时间肯定不同。常量大的,操作次数也会很大。通常而言,不会拿相同的时间复杂度进行。

因此,为了简化表达,常量就可以直接忽略。大O表示法就以文章开头所示的方式O(n)使用即可。


常见大O表示法

以下列出软件实际开发中,常见的复杂度及其实例,便于加深复杂度的理解。

大O表示法名称算法
O(1)常量复杂度(Constant Complexity)数组的查找、链表的插入和删除
O(log n)对数复杂度(Logarithmic Complexity)二分查找
O(n)线性复杂度(Linear Complexity)简单查找(或叫普通查找)
O(n^2)平方复杂度(N Square Complexity)一种速度较慢的选择排序,双层循环
O(n^3)立方复杂度(N Cubic Complexity)三层循环
O(2^n)指数(Exponential Growth)斐波那契数列
O(n*log n)一种速度较快的快速排序
O(n!)阶乘(Factorial)一种非常慢的旅行商问题的解决方案

通过观看列表,无法直观感受复杂度的优劣,下面借助复杂度图表,展示各个复杂度曲线的走向,以及复杂度的优劣,帮助读者加深对于复杂度的理解。纵坐标表示操作数,横坐标表示元素个数,图表中的红色表示算法糟糕,土黄色表示算法较差,黄色表示算法一般,浅绿色表示算法较好,绿色表示算法极好。


降低复杂度

复杂度是一个很好的衡量指标,当我们进行代码的设计、编写、测试、调优时,都应该以复杂度作为判断标准,去比较和找出最优的算法,降低损耗,减少异常,提高性能。也就是说,分析程序的时间复杂度和空间复杂度,能够降低复杂度,进而降低计算机资源的损耗,有利于程序高性能运转,这是顶尖开发者的必备素质。

以中学时代曾经学过的前n项求和的问题为例,解释降低复杂度的好处。

前n项求和,1 + 2 + ... + n = ?

方法一,通过依次循环求和各个元素,最终得出前n项元素的总和,其时间复杂度为O(n)

public int sum(int n){
    int sum = 0;
    for(int i=1;i<=n;i++){
        sum += i;
    }
    return sum;
}

方法二,通过高斯定理,前n项求和公式1+2+...+n=\tfrac{n(n + 1)}{2},故时间复杂度为O(1)

public int sum(int n){
    return (n + 1) * n / 2;
}

依据上面的示例,算法的实现方式不同,复杂度不同,进而程序运行的性能有着极大的不同,这就是为什么要尽量找出最优的算法,降低复杂度,提高算法的运行性能的原因了。

实际上,复杂度也能够给我们人生很好的启发。当我们去规划人生,实现梦想的时候,是不是也应该有一个标准,给予我们作为参考,让我们能够判断这样的人生算法是否高效,是否合理,进而为我们更好地,更加快速地成长提供帮助。

由于作者个人水平有限,文章之中的不当之处,还请谅解和指正。最后,感谢你阅读本篇文章。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值