算法的学习-基础篇

前言

在参加面试的时候,多多少少都会问到一些关于算法的知识。

这其实是有原因的:在多个人专业知识相同的情况下,公司为什么选择放弃他人而选择你,其中的一个因素就是看你的算法基础。

本文将详细介绍算法的基础概念,如果对算法不太理解的同学可以借鉴参考。

算法基础

一、概念

根据《算法导论第三版》中的描述:算法就是任何问题的解决过程,它接收一些值或集合,对这些值或集合进行加工,最后产生一些值或集合作为输出,算法指的就是将输入转换为输出这个过程中的一系列计算流程。

简而言之,我们可以说算法就是解决一个特定任务的一系列步骤。

二、特征

一个算法应具有以下五个重要特征:
- 有穷性:算法必须在执行有限个步骤后终止,如果你设计的算法永无休止地尝试解决问题,那么它是无用的;
- 确切性:算法的每一步指令都必须具有确切的意义,并且在任何场景下,每一步指令都应该都没歧义;
- 有效性(可行性):一个算法设计出来是用以解决某个问题的,如果你设计的算法不能解决问题,那么它也是无用的;
- 输入项:一个算法有0个或者多个输入,用来初始化算法,这主要看你要解决的问题需不需要输入参数;
- 输出项:一个算法有1个或者多个输出。没有输出项的算法是毫无意义的,输出项反映了数据加工后的结果。

三、评定

针对同一个问题,可以有多种解决方案。

具体用哪种就要由每个算法的效率来决定了,一个算法的优劣将会大大影响程序的执行效率。

那么如何分析一个算法的优劣呢?主要是从时间复杂度空间复杂度来考虑:

1. 时间复杂度

时间复杂度是指执行算法所需要的计算工作量,简单点说就是指执行算法所需要消耗的时间。

一个算法执行所耗费的时间,从理论上来讲是不能算出来的,必须通过计算机运行测试才能知道。

不过一个算法具体耗费多少时间我们并不关心,我们只需要知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。

这里我们做一个假设:一条语句执行消耗的时间量为s,A算法中有n条语句,那么执行A算法所需要的时间就是n*s;B算法中有n-2条语句,那么B算法所消耗的时间就是(n-2)*s。在其他因素全都相同的情况下,我们就可以得出,B算法是优于A算法的,因为执行B算法所消耗的时间更多。

简单总结,一个算法花费的时间与算法中语句的执行次数成正比例。哪个算法中语句执行次数多,它花费的时间就多。

此时我们引入时间频度的概念,记为T(n)。

n指的是问题的规模,当n不断变大时,时间频度 T(n)也会不断变大。

我们想要呈现出它的变化规律,为此,我们引入了时间复杂度的概念。

一般来说,假设这个算法是问题规模为n的函数f(n),那么时间复杂度就记做:

T(n) = O(f(n))

O在表达式中代表着最坏的情况,这里最坏的情况就是n = 无穷大。

简单来说,上述公式表达的含义就是在n趋于无穷大的时候,T(n) = f(n)。

f(n)指的是具体的函数,虽然对f(n)没有规定,但是我们一般尽可能地去简化f(n)。

例如:O(n2+n+1)=O(3n2-n+5)=O(9n2+5)=O(n2)

注意O中是携带一个常数C的,所以f(n)一般不加系数。

为什么可以这样换算,为什么最后都可以换算为O(n2)?

这主要是因为当n趋于无限大时, n2 是远远大于 Cn的,也就是高次方项远远大于低次方项,导致低次方项完全可以忽略不计。

这就像是什么,我们把n2+3n+1想象为一棵树的话,n2就是主干部分,3n+1就是一些细枝末节,是完全可以忽略的。

下面我们就来举几个示例,计算它们的空间复杂度

第一题:

int x = 0;  //执行1次
int y = 5;  //执行1次
x = 10;     //执行1次
int sum = x+y;  //执行1次

以上四条语句的频度均为1,该程序的执行时间是一个与n完全无关的常数。算法的时间复杂度就是常数阶,记做:T(n) = O(1)

假设这个算法有上千条语句,因为它的执行时间不会随着n的增长而变化,其执行时间也不过是一个较大的常数。

此类与n毫无关联的算法的空间复杂度均可以记为:T(n) = O(1)。

第二题:

int number = 0;     //执行一次
for(int i =0;i<n;i++){  //执行n+1次,注意为什么是n+1,因为当条件不满足时,也执行了一次判断
    number++;   //执行n次
}

语句一的频度为1。

语句二的频度为n+1。

语句三的频度为n。

所以上述代码的时间复杂度为:T(n) =1 + n+1 + n = 2n+2。

接着当n趋于无限大时,我们只需保留最高的次方项,即:T(n) = O(2n),接着我们将系数省略到O中,即T(n)=O(n)。

第三题:

int number = 0;         //执行1次
for(int i =0;i<n;i++){      //执行n+1次
    for(int j =0;j<n;j++){  //执行(n+1)*n次
        number++;   // 执行n*n次
    }

}

第三题可能有些难度,是一个嵌套的for循环,我们来一句一句分析。

语句一是一个频度为1的语句,和n无关,记为1。

语句二是一个外层for循环,执行次数为n+1,1代表的是当条件不满足时,结束循环的那次操作。

语句三是嵌套的for循环,自身的执行次数也是n+1,但是配合上外层的for循环,将会执行n次嵌套for循环,所以语句三的频度为:(n+1)*n。

语句四是嵌套for循环中的语句,一次嵌套for循环中的语句需要执行n次,而嵌套for循环也需要执行n次,所以语句四的频度为:n*n。

那么整个程序的时间复杂度即为:T(n) = 1+ n+1 + (n+1) * n + n * n = 2n2 +2n + 2。

当n趋于无限大时,只保留最高次方项,并且省略系数,即:T(n) = O(n2);

通过上面三题的练习,各位看官对时间复杂度的计算过程应该已经了解了。

其实时间复杂度的真正计算,还是比较简单的,难在时间复杂度概念理解。

接下来我们来看一下常见的时间复杂度函数有哪些:
时间复杂度函数

从图中可以看出,随着n的变化,时间复杂度各个函数的变化规律。

这些常见的时间复杂度大小排序如下:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)

我们应该尽可能使用时间复杂度小的算法,从而提升程序的运行效率。

2. 空间复杂度

算法的空间复杂度指的是一个算法所消耗的存储空间。

一个算法在计算机所占用的空间,由三方面决定:
- 存储算法本身所占用的空间;
- 算法输入输出的数据所占用的空间;
- 算法在运行过程中临时占用的空间。

存储算法本身所占用的空间与算法书写的长短成正比,要压缩这方面的存储空间,就必须编写出较短的算法。

算法输入输出数据所占用的空间是由这个算法要解决的问题决定的,针对同一问题,它占用的空间不会随着算法的变化而改变,但它确实占用着空间。

算法在运行过程中临时占用的空间会根据算法的不同而产生差异,也是我们最需要着重关心的一点。

有些算法只需要占用很少的临时工作单元,并且不会随问题规模的变大而变大,我们称这种算法是“就地”进行的,是节省存储的算法。

有些算法占用的临时工作单元数与解决的问题规模n有关,占用空间随着n的增大而增大,当n较大时,将占用较多的存储单元,一些常见的排序算法就属于这种情况,比如快速排序、归并排序,属于占用较多存储空间的算法。

当一个算法的空间复杂度不会随着问题规模的变化而变化时,可表示为O(1)。

当一个算法的空间复杂度与问题规模n呈线性比例关系时,可表示为O(n)。

同理,其他变化关系也可以推导出来。同时间复杂度相比,空间复杂度的分析还是比较简单的。

3. 其他

除了上述两条评定规则外,评价一个算法的质量还有其他几个因素:
- 正确性:算法的正确性是评价一个算法优劣最重要的标准!这个算法的结果是错误的,那么这个算法毫无意义。
- 可读性:可读性指的是人们理解这个算法的难易程度,当时间复杂度空间复杂度相同的情况下,当然要选择易于人们理解的算法。
- 健壮性:健壮性是指一个算法对不合理数据输入的反应能力和处理能力,也称为容错性。

总结

本文是算法最基本的一些知识,看完本文,需要你完全理解下图的含义:
排序算法复杂度

后续还会针对算法,做一些更深入的分析,感谢各位看官的浏览。

鞠躬。

感谢

百度百科

算法的时间复杂度和空间复杂度-总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值