数据结构与算法学习笔记(第二章)

算法

上章学习了数据结构,了解了一些相关概念以及数据结构的定义,我们了解到数据结构通常与算法是分不开的,没有算法的数据结构犹如演双簧少了一个人,失去了他的乐趣,本章开始学习算法。(没错,要有难度了,做好准备!)
首先来看算法的定义:

算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。

我们为什么学习算法呢?假设我们让计算机计算1+2+3+4+…+99+100的值,大多数人首先想到的程序是用1+2=3,3+3=6,6+4=10…这种方法循环100次,这些循环,看似对于计算机来说微不足道,但如果要加到上亿呢?但应用等差数列的求和算法后,我们却可以更高效率的得出计算结果,就算加到上亿,也不过是瞬间的事,算法可以极大的提升程序运行的效率,更快,更高效的实现我们的目标。计算机是死的,但人是活的,对于给定的问题,我们要灵活选择合适的算法,以更高效的解决问题。
在算法定义中,提到了指令,指令能被人或计算机等计算装置代替,它可以是计算机指令,也可以是我们平时的语言文字。每条指令表示一组操作,每一个操作都完成特定的功能,这就是算法。

算法的特性

算法具有五个基本特性:输入、输出、有穷性、确定性和可行性。
输入输出:算法具有零个或多个输入,但至少有一个或多个输出,输出的形式可以是打印输出,也可以返回一个或多个值。
有穷性:,顾名思义,指算法在执行有限的步骤之后,自动结束,不会出现无限循环,并且每一个步骤在可接受的时间内完成。例如死循环代码以及那种运行二十年才结束的代码(假如真的有的话),都不满足有穷性。
确定性:算法的每一步骤都具有确定的含义,不会出现二义性,不能出现模棱两可的结果,输出必须是确定的
可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成,可行性意味着算法可以转换为程序上机运行,并得到正确的结果

算法设计的要求

算法不是唯一的,没有最好的,只有最适合的,但尽管算法不唯一,优秀的算法还是有些共同的特征,这就是算法设计的要求。
1、正确性:正确性是算法设计要求的前提,算法的正确性是指算法至少应具有输入、输出和加工处理无歧义性、能正确反映问题的需求、能够得到问题的正确答案。
算法的“正确”分为四个层次:

  • 算法没有语法错误。
  • 算法程序对于合法的输入数据能够产生满足要求的输出结果。
  • 算法程序对于非法的输入数据能够得出满足规格说明的结果。
  • 算法程序对于精心选择的,甚至刁难的测试数据都有满足要求的输出结果。
    一般情况下,把层次3作为一个算法是否正确的标准。
    2、可读性:算法设计的另一目的是为了便于阅读,理解和交流,同时也可便于调试和修改,因此可读性也是算法好坏的重要标准。
    3、健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果。(类似于正确性的第三层次)
    4、时间效率高和存储量低:通俗来讲,就是算得快+占用的存储空间少,好的算法就是用最小的代价,办最大的事。
    综上,好的算法,也就是算法设计的要求,应该具有正确性、可读性、健壮性、时间效率高和存储量低的特点。

算法效率的度量方法

设计算法要提高效率,所谓效率,大都指的是算法程序执行时间,那如何度量一个算法的执行时间呢?
事后统计法(×):这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。
但事后统计法存在很大缺陷,其一就是可能会在设计好程序后发现这是很糟糕的算法,从而浪费大量的精力和时间,因此不予采纳。
事前分析估算法(√):由于设计程序是最耗费时间的步骤,因此考虑最好在设计程序之前选出合适的算法,由此,产生了一种叫做事前分析估算的方法。
定义:在计算机程序编制前,依据统计方法对算法进行估算。
高级程序语言编写的程序所消耗的时间取决于下列因素:

  • 算法采用的策略、方法。
  • 编译产生的代码质量。
  • 问题的输入规模。
  • 机器执行指令的速度。
    第一条是算法好坏的根本,第二条由软件来支持,第四条看硬件性能,抛开与计算机软件、硬件有关的因素,一个程序的运行时间,依赖于算法的好坏和问题的输入规模。所谓问题输入规模是指输入量的多少。
    **分析一个算法的运行时间时,重要的是把基本操作的数量与输入规模关联起来,及基本操作的数量必须表示成输入规模的函数。(即设输入规模为n,构建关于n的函数)**一般来说,不同的算法,随着问题输入规模的增大,它们在时间效率上的差异也会越来越大。(所谓算法效率,就是输入规模与基本操作的函数,算法效率的比较,就是在同样输入规模的情况下,基本操作次数的比较

函数的渐进增长

**定义:①给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么,我们说f(n)的增长渐进快于g(n)。**所谓的“增长渐进”,我们可以理解为不同函数的斜率。函数的渐进增长,可以理解为函数的单调递增。
②在利用函数判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注主项(最高阶项)的阶数。简而言之,在输入规模大到一定程度时,算法的效率取决于该函数的最高次数
由①②可得推论:某个算法,随着n的增大,它会越来越优于另一算法,或者越来越差于另一算法。这就是事前估算方法的理论依据,通过
算法时间复杂度
来估算算法时间效率。

算法时间复杂度

定义:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n)=O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。人话就是,把T(n)记作算法的时间复杂度,也可以说是算法的时间量度,T(n)的增长率和f(n)的增长率相同,T(n)和f(n)成正比,T(n)=O(f(n))。
因此可知,一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法。(即f(n)渐进增长小,基本操作次数少,效率高)。
那么如何分析一个算法的时间复杂度呢?及如何推导大O阶呢?以下为推导方法(可借鉴结论②):
1、得出
基本操作执行次数
与输入规模的函数。
2、用常数1取代所有加法常数。
3、在修改后的运行次数函数中,只保留最高阶项。
4、如果最高阶项存在且不是1,则则去除与这个项相乘的常数。
得到的结果就是大O阶。
例:常数阶顺序结构(执行的次数与n的大小无关,执行时间恒定的算法),具有O(1)的时间复杂度。单纯的分支结构,其时间复杂度也是O(1)。
线性阶、对数阶、平方阶、nlogn阶、立方阶、指数阶

常见的时间复杂度

数据结构与算法学习笔记(第二章)_第1张图片
常用的时间复杂度从小到大的排序是:
在这里插入图片描述
从立方阶往后的时间复杂度都过大,因此一般不去讨论。
总之,计算时间复杂度关键是找出基本操作次数,再通过上面介绍的方法得出结果。

最坏情况与平均情况

最坏情况运行时间是一种保证,在应用中,这是一种最重要的需求,通常,我们提到的运行时间都是指最坏情况的运行时间。
平均运行时间是最有意义的,因为它是期望的运行时间。
对算法的分析,一种方法是计算所有情况的平均值,,这种时间复杂度的计算方法称为平均时间复杂度。另一种方法是计算最坏情况下的时间复杂度,这种方法称为最坏时间复杂度。一般情况下,都是指最坏时间复杂度。

算法空间复杂度

算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作:S(n)=O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。
算法的空间复杂度解释较少,通常来说,只要算法不涉及到动态分配的空间以及递归、栈所需的空间,空间复杂度通常为O(1)。

总结

第二章的学习终于结束了啊,总体来说还是一些关于定义概念的东西,通过本章的学习,可以对自己的算法水平有一个更清晰的认知,通过时间复杂度的估算确定自己算法的效率,从而做出更高效率的算法。学习好难,,继续加油!

你可能感兴趣的:(数据结构学习)