算法
算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
1.1、数据结构和算法的关系
打个比方:梁山伯与祝英台,不管是去掉梁山伯还是祝英台。这部经典就失去意义。数据结构与算法也一样
1.2、算法定义
为了解决某个或某类问题,需要把指令表示成一定的操作序列,操作序列包括一组操作,每一个操作都完成特定的功能,这就是算法了
1.3、算法特性
- 输入、输出、有穷性、确定性和可行性
1.4、算法设计要求
- 正确性:算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题的需求、能够得到问题的正确答案
- 但是算法的“正确”通常在用法上有很大的差别,大体分为以下四个层次。 1.算法程序没有语法错误。 2.算法程序对于合法的输入数据能够产生满足要求的输出结果。 3.算法程序对于非法的输入数据能够得出满足规格说明的结果。 4.算法程序对于精心选择的,甚至刁难的测试数据都有满足要求的输出结果。对于这四层含义,层次1要求最低,但是仅仅没有语法错误实在谈不上是好算法。这就如同仅仅解决温饱,不能算是生活幸福一样。而层次4是最困难的,我们几乎不可能逐一验证所有的输入都得到正确的结果。因此算法的正确性在大部分情况下都不可能用程序来证明,而是用数学方法证明的。证明一个复杂算法在所有层次上都是正确的,代价非常昂贵。所以一般情况下,我们把层次3作为一个算法是否正确的标准。
- 可读性:算法设计的另一目的是为了便于阅读、理解和交流。
- 健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果。
- 最后,好的算法还应该具备时间效率高和存储量低的特点
1.5、算法效率度量
这里效率大都指算法的执行时间
1.5.1、事后统计方法
事后统计方法:这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。
- 必须依据算法事先编制好程序
- 时间的比较依赖计算机硬件和软件等环境因素,有时会掩盖算法本身的优劣
- 算法的测试数据设计困难,并且程序的运行时间往往还与测试数据的规模有很大关系。比如10个数字的排序,不管用什么算法,差异几乎是零
1.5.2、事前分析估算方法
事前分析估算方法:在计算机程序编制前,依据统计方法对算法进行估算。
一个用高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素: 1.算法采用的策略、方法。 2.编译产生的代码质量。 3.问题的输入规模。 4.机器执行指令的速度。
1.5.3、算法时间复杂度
1.5.3.1、算法时间复杂度定义
T(n)=O(f(n)) T(n)=O(f(n)) T(n)=O(f(n))
它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。我们称之为大O记法
- n:问题规模
- T(n):总执行次数,是关于n的函数
- f(n)与T(n)增长率相同的渐进函数
- 计算时间复杂度等价于计算f(n)这个渐进函数
- O(1):常数阶
- O(n):线性阶
- O(n2n^2n2 ):平方阶
1.5.3.2、推导大O阶方法
1.用常数1取代运行时间中的所有加法常数。
2.在修改后的运行次数函数中,只保留最高阶项。
3.如果最高阶项存在且不是1,则去除与这个项相乘的常数。
-
常数阶
int sum = 0,n = 100; /* 执行一次 */ sum = (1 + n) * n / 2; /* 执行一次 */ printf("%d", sum); /* 执行一次 */
f(n)=3 ,不存在最高阶项 f(n)=1。T(n)=O(1)
-
线性阶
int i; for (i = 0; i < n; i++) { /* 时间复杂度为O(1)的程序步骤序列 */ }
f(n)=n,T(n)=O(f(n)),T(n)=O(n)
-
对数阶
int count = 1; while (count < n) { count = count * 2; /* 时间复杂度为O(1)的程序步骤序列 */ }
f(n)=log2n\log_2nlog2n,T(n)=O(f(n)),T(n)=O(lognlog_nlogn)
-
平方阶
int i, j; for (i = 0; i < m; i++) { for (j = 0; j < n; j++) { /* 时间复杂度为O(1)的程序步骤序列 */ } }
O(m×n)
int i, j; for (i = 0; i < n; i++) { /* 注意j = i 而不是0 */ for (j = i; j < n; j++) { /* 时间复杂度为O(1)的程序步骤序列 */ } }
循环次数:n + n-1+ n-2+…+1 = (n+1)*n/2 = (n^2+n)/2 。用我们推导大O阶的方法,第一条,没有加法常数不予考虑;第二条,只保留最高阶项,因此保留n2/2;第三条,去除这个项相乘的常数,也就是去除1/2,最终这段代码的时间复杂度为O($n^2 $)。
12 12 12
O(1) 常数阶
2n+3
2n+3
2n+3
O(n)线性阶
3n2+2n+1
3n^2+2n+1
3n2+2n+1
O( n2n^2n2 )平方阶
5log2n+20
5log_2n+20
5log2n+20
O(logn\log_nlogn)对数阶
2n+3nlog2n+19
2n+3n\log_2n+19
2n+3nlog2n+19
O(nlognn\log_nnlogn) nlognn\log_nnlogn阶
6n3+2n2+3n+4
6n^3+2n^2+3n+4
6n3+2n2+3n+4
O(n3n^3n3) 立方阶
2n
2^n
2n
O(2n2^n2n) 指数阶