《数据结构》——算法

248 阅读6分钟

本文内容,参考自《大话数据结构》(程杰著) ,一部分自己修改,如:把C语言换成了Java语言。写作目的,意在加强记忆。

本文写作工具,使用 "幕布" ,从关注的公众号推送得知这一工具。但感觉不太适合程序员,还是Markdown好。


这个思维导图,"幕布" 可以自动生成,这个功能不错。

以下是正文:

  • 算法定义
    • 算法:是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
  • 算法的特性
    • 算法具有零个或多个输入。算法至少有一个或多个输出。
    • 有穷性
      • 有穷性:指算法在执行有限步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。
    • 确定性
      • 确定性:算法的每一步骤都具有确定的含义,不会出现歧义。算法在一定的条件下,只有一条执行路径,相同的输入只能有唯一的输出结果。算法的每个步骤被精确定义而无歧义。
    • 可行性
      • 可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成。
  • 算法设计的要求
    • 正确性
      • 正确性:算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题的需求,能够得到问题的正确答案。
        • 算法程序没有语法错误
        • 算法程序对于合法的输入数据能够产生满足要求的输出结果
        • 算法程序对于非法的输入数据能够得出满足规格说明的结果
        • 算法程序对于精心选择的,刁难的测试数据都有满足要求的输出结果
    • 可读性
      • 可读性:算法设计的另一目的是为了便于阅读、理解和交流
    • 健壮性
      • 一个好的算法还应该能对输入数据不合法的情况做合适的处理。比如输入的时间或者距离不应该是负数等。健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果。
    • 时间高效和存储量低
      • 设计算法应该尽量满足时间效率高和存储量低的需求。
  • 算法效率的度量方法
    • 函数的渐近增长
      • 函数的渐近增长:给定两个函数 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 的一个函数。这样用大写 O( ) 来体现算法时间复杂度的记法,我们称之为大 O 记法。一般情况下,随着 n 的增大,T(n) 增长最慢的算法为最优算法。
    • 推导大 O 阶方法
      • 推导大 O 阶方法步骤
        • 用常数 1 取代运行时间中的所有加法常数
        • 在修改后的运行次数函数中,只保留最高阶项
        • 如果最高阶项存在且不是 1 ,则去除与这个项相乘的常数
      • 常数阶
        • 如上图代码,这个算法的运行次数函数是 f(n) = 4 。根据我们推导大 O 阶的方法,把常数项 4 改为 1 。没有最高阶项,所以这个算法的时间复杂度为 O(1)。另外对于分支结构而言,无论是真,还是假,执行的次数都是恒定的,不会随着 n 的变大而发生变化,所以单纯的分支结构(不包含在循环结构中),其时间复杂度也是 O(1)。
      • 线性阶
        • 线性阶的循环结构会复杂很多。要确定一个算法的阶次,我们常常需要确定特定语句或语句集运行的次数。因此,分析算法的复杂度,关键就是要分析循环结构的运行情况。
        • 如上图代码,这个算法的运行次数函数是 f(n) = 1+n+1+1。根据我们推导大 O 阶的方法,只保留最高阶项,所以这个算法的时间复杂度为 O(n)。
      • 对数阶
        • 由于每次 i 乘以 2 之后,就距离 n 更近一分。也就是说,有多少个 2 相乘后大于 n ,则会退出循环。由于 2^x = n 得到 x = ㏒₂n。所以,这个循环的时间复杂度为 O(㏒n)。
      • 平方阶
        • 外层循环,时间复杂度为 O(n) ;内层循环,时间复杂度也为 O(n)。所以这个算法的时间复杂度为 O(n²)
        • 如果外循环的循环次数改为了m,时间复杂度就变为O(m × n)
        • 上面这个循环,它的时间复杂度是多少呢?由于 i = 0 时,内循环执行了 n 次,当 i = 1 时,执行了 n-1 次,......当 i = n-1 时,执行了 1 次。所以总的执行次数为:n + (n+1) + (n-2) + ... + 1 = n(n+1)/2 = n²/2 + n/2 。用我们推导大 O 阶的方法,只保留最高阶项,因此保留 n²/2 ;去除这个项相乘的常数,也就是去掉 1/2 ,最终这个算法的时间复杂度为 O(n²) 。
      • 常见的时间复杂度
        • 12 —— O(1) ——常数阶
        • 2n+3—— O(n) ——线性阶
        • 3n²+2n+1—— O(n²) ——平方阶
        • 5㏒₂n+20—— O( ㏒n) ——对数阶
        • 2n+3n㏒₂n+19—— O(n㏒n) ——n㏒n阶
        • 6n³+2n²+3n+4—— O(n³) ——立方阶
        • 2^n —— O(2^n) —— 指数阶
        • O(1) ﹤ O(㏒n) ﹤ O(n) ﹤ O(n㏒n) ﹤ O(n²)﹤ O(n³)﹤ O(2^n)﹤ O(n!)﹤ O(n^n)