当前位置:   article > 正文

数据结构-----算法复杂度(大O表示法)_o(n!)

o(n!)

为什么要先聊这个算法复杂度呢?

首先,我们先聊聊算法。“老衲”经常听别人说起算法,但还是不太明白算法是什么。

先拽一大段概念:

算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度时间复杂度来衡量。

是不是看不懂,不想看?让我用白话文给你道道:

算法,在日常生活中也是随处可见的。比如“老衲”要去上学,怎么去上学?

  1. 做飞机,机票贵(引申到程序上就是耗费空间),但是时间快呀
  2. 坐火车,火车票便宜,但老衲肯定要好久才能到学校,时间又耗费了很多

其实,上面的两种策略就是算法。也就是为了达到“去学校”这一目的,根据自身情况(比如我钱多,我就是要最快到;比如我钱紧张,时间还是很充裕)来选择不同的方式去上学。

同理,算法也有空间复杂度(引申到现实生活就是耗费的价格)和时间复杂度两个标准来衡量它们的好坏。

大O表示法

声明一下,由于个人能力有限,对于时间复杂度和空间复杂度的表示,掌握到了程序的大致表示,如果对复杂度的计算有浓厚的兴趣,可以去看看《c++数据结构与算法》第二章,讲得挺全面!

什么是大O表示法?

come on! 接着来聊,什么是大O表示法呢?

由于程序运行所需的时间往往会受到机器性能与其他因素影响,用绝对的时间单位衡量算法的效率并不合适。在讨论算法复杂度的时候,我们一般关注它的近似值(渐进趋势),即渐进复杂度,常用大O表示法表示。

O(1)常数时间复杂度
O(log n)对数时间复杂度
O(n)线性时间复杂度
O(n^2)平方
O(n^3)立方
O(2^n)指数
O(n!)阶乘

以上7种即为常见的时间复杂度/空间复杂度。
这些又有什么含义呢?先来看下面这张图:

可见,几种时间复杂度的增长速度顺序为:O(1) < O(log n) < O(n) < O(n ^ 2) < O(n ^ 3) < O(2 ^ n) < O(n!)

n是什么

n就是算法的输入规模,就是输入数字的数量。

明白了n是什么,就可以大概解释一下7个表达式的含义了。以O(n^2)为例,它表示当输入规模为n时,时间复杂度接近于n^2。

悟出门道了吗?"老衲"说没有。没关系,接下来我将结合代码来解释一下这几种常用时间复杂度的意思。( 如果对程序比较陌生,可以先点开超链接看看我对这块的理解  程序是什么?

O(1) 常数复杂度

  1. int n = 1000;
  2. System.out.println("Hey - your input is: " + n);

 上面这段代码的时间复杂度就是常数级别的,没有循环,一条语句。

  1. int n = 1000;
  2. System.out.println("Hey - your input is: " + n);
  3. System.out.println("Hmm.. I'm doing morestuff with: " + n);
  4. System.out.println("And more: " + n);

这段代码的时间复杂度也是O(1)。

O(log n) 对数复杂度

log n即为log₂n,这里省略了对数的底数2。“老衲”学过高数,对log n一定不会陌生了。

下面这段代码的时间复杂度即为O(log n)。

  1. for(int i = 1; i < n; i = i * 2)
  2. {
  3. System.out.println("Hey - I'm busy looking at: " + i);
  4. }

留给“老衲”的思考题:思考一下为什么是 i * 2? 

O(n) 线性复杂度

顾名思义,从上面那张图中可以看出函数图像为一条直线。

下面这段代码时间复杂度即为线性阶。单重循环遍历1~n并打印出来,共循环了n次。

  1. for(int i = 1; i <= n; i ++)
  2. {
  3. System.out.println("Hey - I'm busy looking at: " + i);
  4. }

O(n^2) 平方

明白了n次单重循环时间复杂度为O(n),就不难理解下面的双重循环嵌套,共运行n*n次,即时间复杂度为O(n^2)了吧。

  1. for (int i = 1; i <= n; i ++)
  2. {
  3. for (int j = 1; j <= n; j ++)
  4. {
  5. System.out.println("Hey - I'm busy looking at: " + i + " and " + j);
  6. }
  7. }

O(n^3) 立方

以此类推,聪明的“老衲”一定能猜到O(n^3)时间复杂度的程序该怎么写。

O(2^n) 指数

下面代码的时间复杂度就是O(2^n)。

  1. for (int i = 1; i <= Math.pow(2, n); i++){
  2. System.out.println("Hey - I'm busy looking at: " + i);
  3. }

O(n!) 阶乘

  1. public static int fibonacci(int n)
  2. { // 这个方法的功能是递归求第n个斐波那契数
  3. if(n == 1 || n == 2)
  4. return 1;
  5. else
  6. return fibonacci(n - 1) + fibonacci(n - 2);
  7. }
  8. public static void main(String[] args)
  9. {
  10. for(int i = 0; i < fibonacci(6); i ++) // 这里的for循环对时间复杂度影响不大
  11. System.out.println("Hey - I'm busy looking at: " + i);
  12. }

O(n!)复杂度常出现在递归的算法中。上面的代码功能为递归求解第n个斐波那契数,时间复杂度为O(n!)。

递归没有理解没关系,这里简单介绍一下,如果想要深入理解递归请点击超链接查看我的另一篇博客:(此处应有超链接)

先来看看上面代码中用到的公式:

fibonacci(n) = fibonacci(n-1) + fibonacci(n-2)    // 这是斐波那契的一种写法

代入一个数,比如6,画出递归树看一下这段代码是如何运行的:

不难看出来,运行这小小的一句代码时间成本巨大,只是一个小小的6,就让计算机运行了6!次,显然是不划算的。

在后面的算法文章里面,会细致地讲讲如何通过不同的方式来降低算法复杂度!

如何计算算法的渐进复杂度?

当我们得到算法的运行次数后,可以通过以下三步求出大O表示法表示的渐进时间复杂度:

  1. 用常数1取代运行时间中左右加法常数
  2. 在修改后的运行次数函数中只保留最高阶项
  3. 如果最高阶项存在且不是1,则去除与这个项相乘的常数

举几个例子:

执行次数

非正式术语

12

O(1)

常数阶

2n + 3

O(n)

线性阶

3n^2+2n+1

O(n^2)

平方阶

O(log n)

对数阶

O(n^3)

立方阶

2^n

O(2^n)

指数阶

递归O(n!)阶乘阶

结束语

明白了7个大O表示法,可以返回去看看折线图,它反映了当n为不同值时,不同渐进时间复杂度的增长趋势!

“老衲”, 你理解了吗?

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/970329
推荐阅读
相关标签
  

闽ICP备14008679号