大家先思考一个问题:当你面对一道编程题或者需要实现某个功能时,你需要设计解决问题的算法,然后编制代码,最后运行成功。但是你有没有想过你设计的算法,效率高还是低?你可能会觉得,管这个干什么,能实现就不错了。作为初学者,我们的确没有太大必要关心算法的效率,但作为工作者或者老手,我们就必须要设计效率高一点的代码了,因为低效率的代码会造成资源的浪费,影响整个项目的运行效率。同一个问题,好的算法2秒就搞定,而效率低的算法半天运行不出来甚至直接死机。
那怎样的代码才算是效率高的代码呢?
首先,效率高≠运行时间短。算法的运行时间取决于算法本身的策略、问题的规模(比如求100以内的素数还是1000以内的素数),所采用的编程语言,计算机的硬件配置等因素。为了便于估算一个算法的效率,先驱们引入了两个概念——时间复杂度与空间复杂度
时间复杂度
先来看定义:“一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数f(n),算法的时间量度记作 T(n) = O(f(n)) 它表示随问题规模n的增大,算法的执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称时间复杂度。 ”
由定义可知,O就是时间复杂度,它是一个关于问题规模n的一个函数,而这个函数又与算法中的基本操作有关,那啥是基本操作呢?反正肯定不是我们口中的“坐下,基本操作。”这里基本操作是指算法中最有代表性,执行次数最多的操作。比如一个n阶矩阵相乘,问题规模就是n,基本操作则是“乘”这个操作。再比如用冒泡排序给10个数排序,问题规模就是10,基本操作是“交换”。
举个简单的例子:假设有一个算法是根据正方形的面积大小输出多少次Hello World,正方形的边长为n:
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
printf("Hello World");
}
}
这是一个二重循环,内层外层循环次数都是n,因此,这个算法的问题规模为n,基本操作为打印“Hello World”。可以推出f(n) = n^2。所以这个算法的时间复杂度为 n^2。
总结一下,求时间复杂度,先要明确算法的问题规模n,也就是这个算法的工作量是多大,再找到算法中执行次数最多的操作,确定这两者之间的函数关系,这个关系就是O。
需要注意的是,同一个算法,也会由于数据的不同导致时间复杂度的不统一。比如在冒泡排序中,如果需要排序的一组数已经是按想要的顺序排好了的,那这个时候时间复杂度为0,因为“交换”操作一次也没有执行。或者这组数是按倒序排列而我们需要升序排列,这个时候复杂度又变成最大。因此,算法的复杂度又分为为最佳情况和最坏情况。一般情况下,是选择最坏情况作为参考。
空间复杂度
类似于时间复杂度,空间复杂度作为算法所需要存储空间的量度,记作:
S(n) = O(f(n))
n表示问题规模。同样,如果所占的空间依赖于特定的输入,也是需要分最佳情况和最坏情况,除特别指明外,均按最坏情况分析。