分析并写出下列程序的运行结果_掌握这些数学函数,你会在算法效率的分析时经常用到...

46d492673cc96f75f991ce5d8d7e5ced.png

借助数学函数进行算法效率的对比分析

cce401115c9e68c14170a5ad6a3d5468.png
▲ 《程序员数学 从零开始》:如果没有必要的数学知识,几行简单的代码就会变成学习中的绊脚石

如何进行算法分析呢?

分别运行解决同一个问题的两个算法进行比较在很多时候并不理想,面对这样的困难迫使我们求助于数学工具,虽然我们不能对一个还没有完整实现的程序使用运行比较法,但却能通过数学分析了解程序性能的大致轮廓并预估改进版本的有效性。

大多数算法都有影响运行时间的主要参数

,这里的
是所解决问题的大小的抽象度量,比如对于一个排序算法来说,
是待排序元素的个数。我们的目标是尽可能地使用简单的数学公式,用
表达出程序的运行效率。

函数的增长

对于将要比较的两个算法,我们并不满足于简单地描述为“一个算法比另一个算法快”,而是希望能够通过数学函数直观地感受到二者的差异,具体来说,是希望知道“一个算法比另一个算法快多少”。

一些函数在算法分析中极为常见:

  1. 。如果程序中的大多数指令只运行
    次或几次,与问题的规模无关,我们就说程序运行的时间是常量的。小高斯的算法就是典型的常量时间。
  2. 。随着问题规模的增长,程序运行时间增长较慢,可以认为程序的运行时间小于一个大常数。虽然对数的底数会影响函数的值,但影响不大。鉴于计算机是
    进制的,所以通常取
    为底数,
    ,这与数学中略有差别(数学中
    )。当
    时,
    ;当
    增长了
    倍时,
    ,仅有略微的增长;只有当
    增长到
    时,
    才翻倍。如果一个算法是把一个大问题分解为若干个小问题,而每个小问题的运行时间是常数,那么我们认为这个算法的运行时间是
    ,二分查找就其中的典型。
  3. 。比
    稍大,当问题规模翻倍时,运行时间比翻倍少一点;当
    增长了
    倍时,程序运行时间增长
    倍。开销是 √N 时间的程序通常对程序的终止条件做了处理,比如,在判断一个数是否是素数时,边界值是这个数的平方根,而不是这个数本身。
  4. 。这就是通常所说的线性时间,如果问题规模增大了
    倍,程序运行时间也增大
    倍。
    的蛮力求和法就是线性时间,这类方法通常带有一个以问题规模为终点的循环。
  5. 。当问题规模翻倍时,如果运行时间比翻倍多一点,我们就简单地说程序运行的时间是
    。当
    时,
    ;如果 N=2048 ,
    都是把一个大问题分解为若干个能过在常数时间内运行的小问题,区别在于是否需要合并这些小问题,如果合并,就是
    ;如果不合并,就是
    。大多数归并问题的运行时间可以简单地看作
  6. 。如果问题规模翻倍,运行时间增长
    倍;问题规模增长
    倍,运行时间增长
    倍。
  7. 。如果问题规模翻倍,运行时间增长
    倍;问题规模增长
    倍,运行时间增长
    倍。
  8. 。真正要命的增长。如果
    翻倍后,
    。复杂问题的蛮力法通常具有这样的规模,这类算法通常不能应用于实际。

来看一下这些函数的增长曲线,如图 1 所示。

baf8f33668ae3eaf3147905f0986c076.png

以上函数能够帮助我们直观地理解算法的运行效率,让我们很容易区分出快速算法和慢速算法。大多数时候,我们都简单地把程序运行的时间称为“常数”、“线性”、“平方次”等。对于小规模的问题,算法的选择不那么重要,一旦问题达到一定规模,算法的优劣就会立马体现出来。代码 4-2 展示了当问题规模是

的增长规模。

▼ 代码 2: 函数的增长规模 C4_2.py

01 import math
02
03 fun_list = ['lgN', 'sqrt(N)', 'N', 'NlgN', '$N^2$', 'N^3'] # 函数列表
04 print(' ' * 10, end='')
05 for f in fun_list:
06     print('%-15s' % f, end='')
07 print('n', '-' * 100)
08
09 N_list = [10 ** n for n in range(7)] # 问题规模
10 for $N$ in N_list: # 函数在不同问题规模下的增长
11     print('%-8s%-2s' % (N, '|'), end='')
12     print('%-15d' % round(math.log2(N)), end='')
13     print('%-15d' % round(math.sqrt(N)), end='')
14     print('%-15d' % N, end='')
15     print('%-15d' % round(N * math.log2(N)), end='')
16     print('%-15d' % $N$ ** 2, end='')
17     print(N ** 3)

运行结果如图 4.2 所示。

22ce8fe9bbe20cf90255cd68bb050461.png

e9625495a188f085b7770f4a10571de4.png

图 2 告诉我们,在问题规模是

的时候,所有算法都同样有效,问题规模越大,不同复杂度的算法运行效率相差得越大。

增长的太过迅猛,作为一个另类单独列出。

▼ 代码 3:

的增长
01 for $N$ in range(10, 110, 10):
02 print('2**{0} = {1}'.format(N, 2 ** N))

运行结果如图 3 所示。

791d3ad3939998a7ef5720cb1970f4c3.png

这些运行结果告诉我们,有些时候,选择正确的算法是解决问题的唯一途径。对于函数的输出结果来说,如果把

看作
秒,那么
就是
秒,超过
分半。这意味着对于一个规模是
的问题来说,一个是
复杂度的算法可以立刻得出结果,
复杂度的算法耗时约
秒,
复杂度的算法耗时将超过
小时,
复杂度则需要
万多年!也许我们可以忍受一运行
秒或
小时的程序,但一定没法容忍有生之年看不到结果的程序。

上文 [遇见] 授权节选自北大出版社《程序员数学从零开始》,本书刚好参与京东每满100-50活动, 感兴趣的朋友可以关注下:

270余幅插图+90余段Python代码+20余个原理剖析,教你学会程序员必须掌握的数学及算法背后的数学原理。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值