算法,即解决一类问题的方法,一般情况下又要求该方法是计算机可实现的。
形象点描述,则算法好似说明书,照着说明书一步步来就可以解决问题。
形式化描述,算法是是一串有穷指令序列,其中该序列的每条指令属于某个有穷基本指令集,且指令集中的每条指令都是物理可实现的或人可以操作的。
算法具有一下特点:
1.输入输出——算法是用来解决某一类问题的方法,输入对应着对问题的描述,而输出则是求得的解。
2.正确性——这里的正确不是指对一次输入可以的得到正确的结果,而是对于所以合法的输入都可以得到符合预期的结果。
3.有穷性——本质上算法只能解决那些经过有穷运算之后停止的问题,对于无限过程只能采用近似逼近的方法。
4.可行性——物理可实现或是人可以操作的。.
以上是算法所必有的特性,此外特定环境下或有更多具体的要求(如软工中可能会要求可读性,健壮性等)。
解决某一类问题的算法往往不止一种,因而我们需要一些指标来评价解决同一问题算法的优劣。
需要指出的是在评价算法是我们总是认为资源是有限的(如果无限就没有必要比较优劣),因而我们认为所消耗资源少的算法为优。
在不考虑软工的情况下,评价算法的指标至少有两种——时间开销和空间开销。
1.空间开销
主要指的是算法在计算机运行时的内存开销。又可以细分为指令空间、数据空间、运行栈空间。
1.1 对于某一问题的算法所需的指令的空间往往不会随问题规模的增大而增大,故分析时往往不予考虑。
1.2 算法所需的数据空间有可以分为两种,一种是用来存储描述问题本身的数据,另一种是在求解的过程中所需的额外空间。
同样前者一般属于必须的(如存储待排序的数据所用的空间),所以分析时也往往不予考虑。
1.3运行栈空间对于非递归程序往往不会随着问题规模的增长而增长,但对于递归程序来说则是必须要考虑的。
2.时间开销
指的是解决问题所消耗的时间,往往也可以分为,CPU运算消耗的时间和IO消耗的时间,对于现在的计算机体系而言,IO操作
的速度一般远远慢于CPU的运算速度,因而提高时间效率的方法可以是减少IO(包括内存IO和外存IO)。
指标选定之后便需要度量的方法
一般而言,分析的方法可以分为:
事情分析——通过数学手段予以一般性的估算
事后分析——通过多组测试数据进行实验,对结果进行分析
两种方法各具特点,下面简述之。
事前分析:主要是通过算法运行过程中的所需执行的指令数量多少予以刻画,需要指出的 是这里指的是基本指令的度量,而不是在高级语言中简简单单的
看某条语句执行的频度,因为有些时候在高级语言中看似只有一条语句,其背后却执行了n条指令(如函数调用)。
通过统计指令执行的频度确实给出了一种事情分析算法的方法,但往往过于繁琐甚至于不可能,另一方面我们往往并不是需要预测算法执行的具体
时间,而只是需要一个大概的时间,因而对统计频度的方法进行适当的简化——只关心主要部分,而忽略次要部分,即算法的渐进性分析。所谓渐进
性分析其实是采用了极限的思想,即给出一个当n(这里看成是数据规模)充分大时的一个断言。如可以断言当n充分大时 1/100(n^2 + n) 会大于1000n
因而在渐进性分析会忽略低阶项和系数,只是划出了不同的数量等级
如 log(n)
n
nlog(n)
n^2
...(多项式)
2^n
...(指数级)
另外需要指出的是对于超过多项式复杂的问题又称为NP难问题
下面给出几个渐进分析中会用到的符号
O 渐进小于等于 f(n) = O(g(n)) <==> lim f(n) / g(n) = 0
Θ 渐进等于 类似第一种给出数学定义...
Ω 渐进大于 类似...
o 渐进小于 类似...
事前分析中另一个不得不注意的问题是,对于某一算法,输入数据的形式不同则算法实际执行的指令频度会差别很大(如排序问题中原始数据的有序程度不同往往对算法会用影响)因而又将渐进分析分为:
最好复杂度 —— 除了一些特殊的情况,最好复杂度的价值不大
平均复杂度 —— 运用概率分析的方法,往往难以得出
最坏复杂度—— 更具有实际应用价值
事后分析:事后分析是利用多组测试数据进行实验, 对得到的结果进行分析给出结论。这种方法简单易行,但往往不具有一般性。
实际中往往会将事前分析和事后分析两种方式结合起来使用,即先进行事前分析得出一般性的结论然后利用事后分析进行验证。
另外特别要指出的是以上都是在渐进的意义下才成立的,即只有当n充分大时才成立,这也意味着一个多项式时间复杂度的算法
并不总是比一个指数时间复杂的算法更具可行性。因为在渐进性分析中我们总是忽略了常数因子,而常数因子可能在当n较小时具有
决定性的影响。