算法分析

  • 科学方法:(一种科学家用来理解自然世界的方法)
  • 观察(根据一定的步骤,来研究将问题规模和运行时间的关系量化):
    • 举例(可以说是构造一个实验)
    • 工具(比如说一个计时器,来记录某个实验的过程数据)
    • 实验数据的分析:
      • 1-图像分析(画图猜想)
  • 数学模型(构造一个数学模型来描述程序的运行时间:执行每条语句的耗时和执行每条语句的频率)
    • 近似频率(忽略低次项)
    • 近似运行时间(对每块代码块分块估算时间)
    • 算法分析的意义(经典算法理论大部分发表于数十年前,但依旧适用于今天的计算机)
    • 成本模型(一个衡量算法性能的指标
    • 总结(得到其运行时间的数学模型所需步骤):
      • 确定输入模型,定义问题的规模
      • 识别内循环
      • 根据内循环中的操作确定成本模型
      • 对于给定的输入,判断这些操作的执行频率.这可能需要进行数学分析(学习具体算法的时候有例子)
tip:数学中的常见函数,包括(向下取整,向上取整,自然对数,以2为底的对数,以2为底的对数想下取整,调和级数??,阶乘)
数学中的常见近似函数,包括(调和级数求和??,等差数列求和,等比数列求和,斯特灵公式??,二项式系数,指数函数)
  • 增长数量级的分类:


    算法分析_第1张图片
    图片.png
  • 设计更快的算法(一般步骤):

    • 实现并分析该问题的一种简单算法,通常称为暴力算法
    • 考查算法的各种改进
    • 用实验证明新的算法更快
tip:对于实际问题来说,运行时间只是选择算法时所考虑的各种因素之一.
  • 倍率实验(简单有效的预测任意程序的性能,并判断它们的运行时间大致的增长数量级):
    • 开发一个输入生成器来产生实际情况下的各种 可能的输入
    • 开发一个程序,能够计算每次实验和上次实验的运行时间的比值
    • 反复运行,直到该比值趋近于极限 2^x (对于比值没有极限的算法无效)
    • 这个程序的运行时间的增长数量级约为 N^x
有一个疑问,假如增长数量级是对数级的呢?
  • 猜想①:logN级别的数量级对比N^x数量级,会小很多,可能会被忽略;
  • 猜想②:可以将第3)步的极限解释为有logN的可能性[ logN / log (N/2) = logN/(logN-1)],即比值趋近于 2^x * [logN/(logN-1)],对应的 增长数量级 约为 N^x * logN
书中的背后一页用倍率公式解释了我的上述疑问,即 如果 T(N) ~ a(N^b)(logN) ,那么T(2N)/T(N) ~ 2^b
一般来说,在数学模型中,对数项是不能忽略的,但在倍率假设中,它在预测性能的公式中,显得不是很重要
倍率实验的作用:
  • 评估算法解决大型问题的可行性
  • 评估使用更快的的计算机所产生的价值
解释一下表1.4.9:
  • 系数为2、系数为10 这两列以下的数据的意思指的是 当输入规模增长2\10倍时,算法运行时间增长了多少倍.(比如平方级别增长了8\1000倍)
  • 注意事项:
    • 大常数:忽略低次项的常数系数,有可能是错误的
    • 非决定性的内循环:成本模型不能只考虑内循环,内循环外也有大量指令需要考虑。成本模型可能需要改进。
    • 指令时间:每条指令的执行时间不一定相同。
    • 系统因素:系统能够大大的影响程序性能。
    • 两个不分伯仲的程序:这两个程序可能各有优劣,最佳实现不一定有必要找出来。
    • 对输入的强烈依赖,运行时间和输入不一定是无关的。
    • 多个问题参量:可能存在多个变量影响着程序的性能。
    • 综上所述:有效近似的估算出任何程序所需的运行时间,就足够了。
  • 处理对输入的依赖:
    • 对我们所要解决的问题的输入建模。让输入模型更加切合实际。***个人理解:输入不一定要很复杂,只要能够分析程序的性能,简单反而更好。
    • 对最坏情况下的性能的保证。从极度悲观的角度来估算算法的性能。
    • 随机化算法。对输入随机化。
    • 操作的顺序。算法的“输入”可能不只是数据,还有可能是一系列操作的顺序。
    • 均摊分析。所有操作的总成本除于操作总数将成本均摊。我们可以允许执行一些昂贵的操作,但是所有操作的平均成本较低,也是可以接受的。
  • 内存
tip:一般内存的使用都会被填充为8字节的倍数。
* 原始数据所需内存:

类型
字节(每个字节8位)


算法分析_第2张图片
图片.png
  • 对象:
    所有实例变量使用的内存+对象本身的开销(一般是16字节,包括一个指向对象的类的引用、垃圾收集信息、同步信息)
    例如:一个Integer对象会使用24字节(16字节的对象开销,4字节保存它的int值,4个填充字节)
  • 链表
    嵌套的非静态类(内部类)。在普通对象的基础上还需要一个指向外部类的引用(8字节)
  • 数组:
    • 原始类型的数组:一般需要24字节的头信息(16字节的对象开销,4字节用于保存长度、4填充字节)+保存值所需的内存。
      • 例如:一个有N个int值的数组需要使用(24+4N)字节(会被填充为8的倍数。
    • 对象的数组:其实就是一个对象引用的数组,所以我们应该在对象所需内存外加上引用所需的内存。
      • 例如:一个含有N个Integer对象的数组需要24字节(数组开销)+8N字节(所有引用)+32N字节(所有对象所需内存)=24+40N字节
      • 例如2:一个含有MN个int值的二维数组需要 24字节(二维数组的开销)加上8M字节(所有元素数组的引用)加上(24+4N)M(所有元素数组的 占用见上面第(1)点)
  • 字符串对象:
    一个指向字符数组的引用(8字节)、3个int值(共12字节)(第一个int是字符数组中的偏移量、第二个int是字符串的长度、第三个int是散列值)、对象本 身的消耗(16字节)以及4个填充字节;除此之外,还要加上字符数组所需的内存空间。
  • 子字符串(java实现)subString():
    其实只是修改了字符串对象中的三个int值。
  • 注意避免最常见的两大错误。
    • 过于关注程序的性能。我们的首要任务应该是写成清晰正确的代码。而且要考虑生产效率。如果算法本身就很快,或者调试一个新算法时间远远超于直 接运行一个稍慢的程序,又或者花了大量时间精力去实现一个理论上能改进程序的算法,然而没成功等等情况,那就没有必要去调优。
    • 完全忽略程序的性能。
    • 所以总结起来说就是,需要根据实际情况选择,要寻找一个平衡点。

(完)

你可能感兴趣的:(算法分析)