算法和算法分析
- 逻辑结构—研究对象的特性及其相互之间的关系
- 存储结构—有效地组织计算机存储
- 算法–有效地实现对象之间 ”运算“关系
算法的定义
对特定问题求解方法和步骤的一种描述,他是指令的有限序列。其中每个指令表示一个或多个操作
简而言之,算法就是解决问题的方法和步骤
算法的描述
- 自然语言:英语、中文
- 流程图:传统流程图、NS流程图
- 伪代码:类语言
- 程序代码:python go
算法和程序
算法
算法是解决问题的一种方法或一个过程,考虑如何将输入转换成输出,一个问题可以有多种算法
程序
程序是某种程序设计语言对算法的具体实现
程序=数据结构+算法
数据结构通过算法实现操作
算法根据数据结构设计程序
算法的特性
一个算法必须具备以下五个重要特性
一个算法必须总是在执行有穷步之后结束,且每一步都在又穷时间内完成
算法中的每一条指令必须有确定的含义,没有二义性,在任何条件下。只有唯一的一条执行路径,及对于相同的输入只能得到相同的输出。
算法是可执行的,算法描述的操作可以通过已经实现的借本操作执行有限次来实现
一个算法有零个或多个输入
一个算法有一个或多个输出
算法设计的要求
-
正确性
正确性:算法满足问题要求,能正确解决问题
算法转化为程序后要注意:
- 程序中不含有语法错误
- 程序对于几组输入数据能偶得出满足要求的结果
- 程序对于精心选择的、典型、苛刻且带有刁难性的几组输入数据能够得出满足要求的结果
- 程序对于一切合法的输入数据都能得出满足要求的结果
通常一第三层意义上的正确性作为衡量一个算法是否合格的标准
-
可读性
可读性:
- 算法住哟啊是为了人的阅读和交流,其次才是计算机执行,因此算法应该抑郁人的理解;
- 另一方面,晦涩难读的算法易于隐藏较多错误而难以调试
健壮性:
- 指当输入非法数据时,算法恰当的做出反应或进行相应处理,而不是产生莫名其妙的输出结果
- 处理出错的方法,不应是中断程序执的行,二应该是返回一个表示错误或 错误性质的执行,而应是返回一个表示错误性质的值,以便在更高的抽象层次上进行处理
高效性:
- 要求花费尽量少的时间和尽量低的存储要求
、
算法效率
对于同一个问题,可以有许多不同的算法。究竟如何来评价这些算法的优劣程度呢?
一个好的算法首先要具备正确性,然后是健壮性,可读性,在几个方面都满足的情况下,主要考虑算法的效率,通过算法的效率高低来批判不同算法的优劣程度。
算法效率以下两个方面来考虑
时间效率:
指的是算法所消耗的时间
空间效率:
算法执行过程中所消耗的存储空间
时间效率和空间效率有时候是矛盾的,提升效率可能要消费时间,提升时间可能要消费空间,也就是我们常说的一种像以时间换空间,以空间换时间,
算法时间效率的度量
-
算法时间效率可以用依据该算法编制的程序在计算机上执行所消耗的时间来度量
-
两种度量方法
-
事后统计
- 将算法实现,测算起时间和空间开销。
- 缺点:编写程序实现算法将花费较多的时间和精力;所的实验结果依赖于计算机的软硬件的环境因素,掩盖算法本身的优劣所需的时间与算法中进行的简单操作次数的乘机
-
事前分析
一个算法的运行时间是指一个和算法在计算机上运行缩小哈窦娥时间大致等于计算机执行一种简单的操作(如赋值、比较、移动等)所需的时间与算法中进行的简单操作次数乘积
算法运行时间=一个简单操作所需的时间*简单操作次数
也即算法中每条语句的执行时间之和
算法运行时间= Σ每条语句的执行次数(语句频度)*该语句执行一次所需的时间
算法运行时间= Σ语句频度*该语句执行一次所需的时间
每条语句执行一次所需的时间,一般是随机器而异的。取决于机器的指令性能、速度以及编译的代码质量。是由机器本身软硬件环境决定的,他与算法无关。
所以,我们可 **假设执行每条语句所需的时间均为 单位时间 **。此时对算法的运行时间的讨论就可以转化为该算法中所有语句的执行次数,即频度之和了。
- 对算法所消耗资源的一种估算方法。
- 为了便于比较不同算法的时间效率,我们仅仅比较他们的数量合计
- 例如:两个不同的算法,时间消耗分别是:
- T1(n) = 10n^2 与 T2(n) = 5n^3
- 例如:两个不同的算法,时间消耗分别是:
- 若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(N) = O(f(n)),称O(f()n)为算法的渐进时间复杂夫(O=order O是数量级的符号),简称时间复杂度。
-
算法时间复杂度定义
算法中基本语句重复执行的次数是问题规模n的某个函数f(n),算法的时间度量记作T(n)=O(f(n))
基本语句
算法中重复执行次数和算法的执行语句时间成正比的语句
对算法运行时间贡献最大
执行次数最多
问题规模
- n越大算法的执行时间越长
- 排序:n为记录数
- 矩阵:n为矩阵的阶数
- 多项式:n为多项式的项数
- 集合:n为元素的个数
- 树:n为树的结点个数
- 图:n为图的订单或者边数
看例子
i = 1;
while(i<=n):
i = i * 2
若循环执行1次 i = 1*2
若循环执行2次 i = 2*2
若循环执行3次 i = 4*2
若循环执行4次 i = 8*2
....
若循环执行x次 i = 2^x
i<=n,所以 2^x<=n 所以 x<log2n
也可以不再区分以什么为底数 直接是log级别也就是 lgN
算法时间复杂度计算
请注意
:有的情况下,算法中基本操作执行的次数还随问题的 数据集不同而不同
顺序查找
顺序查找,在数组list1中查找值等于e的元素,返回其所在的位置。
for i in list1:
if i == e:
return i
- 最坏时间复杂度:旨在最坏的情况下,算法的时间复杂度。
- 平均时间复杂度:只在所有可能输入实例在等概率出现的情况下,算法的期望运行时间。
- 最好时间复杂度:只在最好的情况下,算法的时间复杂度
- 一般总是考虑在最坏的情况下的时间复杂度,以保证算法的运行时间不会比他更长
对于复杂的算法,可以将它分成几个容易估算的部分,然后利用大O加法法则和乘法法则,计算算法的时间复杂度:
a) 加法规则
T(n)=T1(n) + T2(n) = O(f(n)) + O(g(n)) = O(max(f(n),g(n)))
b)乘法规则
T(n)=T1(n) * T2(n) = O(f(n)) * O(g(n)) = O(max(f(n)*g(n)))
时间复杂度T(n)按数量及递增顺序为
复杂度低:常数阶(O(1))>对数阶(O(log2n))>线性对数阶(O(nlog2n))>平方阶(O(n2))>立方阶(O(n3))>K次方阶(O(nk))>指数阶O(2n) 复杂度高
渐进空间复杂度
空间复杂度:算法所需要存储空间的度量,
记作 S(n)=O(f(n))
其中n为问题的规模(或大小)
算法要占据的空间
- 算法本身要占据的空间。输入/输出,指令,常数,变量等
- 算法要使用的辅助空间
例:将一堆数组a中的n个数逆序存放在原数组中
算法一
package main
import "fmt"
//将一维数组a中的n个数逆序存放在原数组中
func main() {
a := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 89, 321, 32, 3, 1, 31, 23, 12, 312, 31, 241, 2}
fmt.Println(a)
n := 7
for i := 0; i < n/2; i++ {
t := a[i]
a[i] = a[n-i-1]
a[n-i-1] = t
}
fmt.Println(a)
}
[1 2 3 4 5 6 7 8 89 321 32 3 1 31 23 12 312 31 241 2]
[7 6 5 4 3 2 1 8 89 321 32 3 1 31 23 12 312 31 241 2]
S(n) = O(1)
原地工作 只需要t这一个变量
算法二
package main
import "fmt"
//将一维数组a中的n个数逆序存放在原数组中
func main() {
a := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 89, 321, 32, 3, 1, 31, 23, 12, 312, 31, 241, 2}
b := make([]int, len(a), len(a))
n := len(a)
fmt.Println(a)
for i := 0; i < n; i++ {
b[i] = a[n-i-1]
}
for i := 0; i < n; i++ {
a[i] = b[i]
}
fmt.Println(b)
fmt.Println(a)
}
[1 2 3 4 5 6 7 8 89 321 32 3 1 31 23 12 312 31 241 2]
[7 6 5 4 5 6 7 8 89 321 32 3 1 31 23 12 312 31 241 2]
S(n) = O(n)
需要创建一个等长切片 有n个元素就需要开辟n个辅助空间
设计好算法的过程
抽象数据类型=数据的逻辑结构+抽象运算(运算的功能描述)
数据的逻辑结构-----数据的结构存储1 … 数据的存储结构n