本文内容,参考自《大话数据结构》(程杰著) ,一部分自己修改,如:把C语言换成了Java语言。写作目的,意在加强记忆。
本文写作工具,使用 "幕布" ,从关注的公众号推送得知这一工具。但感觉不太适合程序员,还是Markdown好。
这个思维导图,"幕布" 可以自动生成,这个功能不错。
以下是正文:
- 算法定义
- 算法:是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
- 算法的特性
- 算法具有零个或多个输入。算法至少有一个或多个输出。
- 有穷性
- 有穷性:指算法在执行有限步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。
- 确定性
- 确定性:算法的每一步骤都具有确定的含义,不会出现歧义。算法在一定的条件下,只有一条执行路径,相同的输入只能有唯一的输出结果。算法的每个步骤被精确定义而无歧义。
- 可行性
- 可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成。
- 算法设计的要求
- 正确性
- 正确性:算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题的需求,能够得到问题的正确答案。
- 算法程序没有语法错误
- 算法程序对于合法的输入数据能够产生满足要求的输出结果
- 算法程序对于非法的输入数据能够得出满足规格说明的结果
- 算法程序对于精心选择的,刁难的测试数据都有满足要求的输出结果
- 正确性:算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题的需求,能够得到问题的正确答案。
- 可读性
- 可读性:算法设计的另一目的是为了便于阅读、理解和交流
- 健壮性
- 一个好的算法还应该能对输入数据不合法的情况做合适的处理。比如输入的时间或者距离不应该是负数等。健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果。
- 时间高效和存储量低
- 设计算法应该尽量满足时间效率高和存储量低的需求。
- 正确性
- 算法效率的度量方法
- 函数的渐近增长
- 函数的渐近增长:给定两个函数 f(n) 和 g(n) ,如果存在一个整数 N ,使得对于所有的 n > N ,f(n) 总是比 g(n) 大,那么,我们说 f(n) 的增长渐近快于 g(n) 。
- 判断一个算法的效率时,函数中的常数和其他要项常常可以忽略,而更应该关注主项(最高阶项)的阶数。
- 一个算法,随着 n 的增长,它会越来越优于另一算法,或者越来越差于另一算法。
- 函数的渐近增长:给定两个函数 f(n) 和 g(n) ,如果存在一个整数 N ,使得对于所有的 n > N ,f(n) 总是比 g(n) 大,那么,我们说 f(n) 的增长渐近快于 g(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)
- 推导大 O 阶方法步骤