分析一个算法指的是确定其执行时所需要使用的资源的量(如时间和内存)
算法大多被设计用来担任处理大量输入的工作,所以算法的效率和复杂度取决于时间和空间复杂度。
时间复杂度就是一个算法在一定输入量下的运行时间。相似的,空间复杂度是在程序运行期间是需要使用的计算机内存的大小。
总的来说,程序所需要的空间取决于以下两个部分:
- 固定部分:这个因程序的不同i而不同。它包括存储指令、常量、变量和结构化的变量(像数组)。
- 动态部分:它也是每个程序各不相同。它包括递归栈需要的空间、动态分配内存的结构化变量的空间…
无论如何,运行时需求更大。因此,我们讨论算法的运行时效率。
最坏情况、平均情况、最优情况
以数组排序为例
考虑这样一个数组
source: 1 2 3 4 5 6 7 8 9 10
我们要将其按逆序排序。应该的结果为:10 9 8 7 6 5 4 3 2 1
source数组与我们排序后的结果相比,是完全逆序的。在这一情况下,我们称之为最坏情况。最坏情况复杂度
而如果
source为:10 9 8 7 6 5 4 3 2 1
我们可以发现,这一数组已经完全排序好了,这称之为最优情况。最优情况复杂度
平均情况复杂度:是指在输入为随机的情况下的运行时间。
均摊情况复杂度:在代码执行的所有复杂度情况中绝大部分是低级别的复杂度,个别情况是高级别复杂度且发生具有时序关系时,可以将个别高级别复杂度均摊到低级别复杂度上。基本上均摊结果就等于低级别复杂度。
最好的算法毋庸置疑,是花费最少情况和最少内存的算法。但是,在很多情况下,我们设计不出这样的算法。不同的算法大多有不同的特点。有的花费更少的时间,有点花费更少的空间。
我们一般根据应用场景选择合适的算法,比如在可用内存有限的情况下,我们不得不优先考虑空间占有率低的。
而在内存充足的情况下,我们更关心时间,特别是现在。你可以想象一个软件,对一个表格排序就要花费你喝一杯咖啡的情况吗?
我们如何来评估时间复杂度呢。
我们用一个函数f(n)来表示,n是输入量的大小。
这样我们可以评估运行时间随着输入量的变化。
最常使用的就是O(Big O notation)
如果这个函数是线性的(没有循坏和递归),它的时间复杂度就是其输入量的大小。然而,当一个算法包含循环的时候,算法的效率会因为输入量的变化而发生很大的变化。我们设计算法的目的就是尽可能地减少这种变化。
- 线性循环
只用一个循环。首先,我们需要知道在循环中语句执行的次数。这是因为迭代的数量与其直接相关。
考虑如下的循环
for(i=0;i<100;i++)
statement block
我们可以知道这个循环迭代了100次。在这一情况下,f(n) = n
再考虑如下代码
for(i=0;i<100;i+=2)
statement block
这一循环仅迭代了50次。 f(n) = n/2
- 对数循环
循环控制变量要么被乘,要么被除。
考虑如下两个循环:
for(i=1;i<1000;i*=2)
statement block;
for(i=1000;i>=1;i/=2)
statement block;
在第一个里面,i每次被乘以2。这个循环仅执行了10次而不是1000次。
第二个也类似。
f(n) = log n
- 嵌套循环
- 线性对数循环
for(i=0;i<10;i++)
for(j=1; j<10;j*=2)
statement block
f(n) = nlog n
- 双重循环
for(i=0;i<10;i++)
for(j=0; j<10;j++)
statement block;
f(n) = n*n
- 依赖双重循环
for(i=0;i<10;i++)
for(j=0; j<=i;j++)
statement block
1+2+3+…+9+10 = 55
f(n) = n (n + 1)/2
未完…