第二章 算法(上)
2.1 数据结构和算法之间的关系
在“数据结构”课程中,谈到算法是为了帮助理解好的数据结构,并不会详细谈算法的方方面面。
2.2两种算法的比较
求1+2+3+......+100结果的程序。
简单的C语言程序:
#include "stdio.h"
int i = 0, sum = 0, n = 100;
void main()
{
for (i;i <= n;i ++)//循环从0遍历直到100 0+1+2+3+....+100
{
sum += i; //累加求和
}
printf("%d", sum); //输出结果5050
}
这是最简单的计算机程序之一,它就是一种算法。以下是计算的一种更为简便快速的算法思维。
sum = 1 + 2 + 3 +...+ 98 + 99 +100,
sum = 100 + 99 + 98 +...+ 3 + 2 +1。
2*sum = (1+100)+(2+99)+(3+98)+...+(98+3)+(99+2)+(100+1)。
所以 2*sum = 101+101+101+...+101+101+101 = 100*101。
sum = 10100/2 = 5050。用程序来实现如下:
#include "stdio.h"
int sum = 0, n = 100;
void main()
{
sum = n * (n + 1) / 2;
printf("%d", sum); //输出结果为5050
}
此方法相当于求等差数列和的算法,不仅仅可以用于从1加到100,就是加到100,一万,一亿(只需改变int变量类型为long int,否则会溢出),也就是瞬间的事情,但如果用for循环遍历挨个加的程序,当累加到的数值过大,显然电脑算的不一定会有人脑快。
2.3 算法的定义
算法(Algorithm)是描述解决问题的方法。是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
2.4 算法的特性
算法具有五个基本特性:输入、输出、有穷性,确定性和可行性。
- 输入输出:算法具有零个或多个输入,至少有一个或多个输出。
- 有穷性:指算法在执行有限步骤后,自动结束而不会无限循环,并且每个步骤在可接受的时间内完成。
- 确定性:算法的每一个步骤都有其确定的含义,不会出现二义性
- 可行性:算法的每一步都必须是可行的,也就是说,每一个步骤都能通过执行有限次数完成。
2.5 算法设计的要求
2.5.1 正确性
算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性,能正确反应问题的需求,能够得到问题的正确答案。好的算法还应该具备容易理解的特性。
算法的“正确”通常在用法上有很大的差别,大体分为:
- 算法程序没有语法错误。
- 算法程序对于合法的输入数据能够产生满足要求的输出结果。
- 算法程序对于非法的输入数据能够得出满足规格说明的结果。
- 算法程序对于精心选择的,甚至刁难的测试数据都有满足要求的输出结果。
我们一般把第三点作为一个算法是否正确的标准。
2.5.2 可读性
算法设计的另一个目的就是为了便于阅读、理解和交流。
2.5.3 健壮性
当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果。
2.5.4 时间效率高和存储量低
设计算法时应该尽量满足时间效率高和存储量低的需求。
2.6算法的度量方法
设计算法要提高效率,而效率高大都指算法的执行时间。以下有两种方式估算算法执行时间,从而比较算法效率高低。
2.6.1 事后统计方法
这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。
缺陷:必须依据算法事先编制好程序,耗费大量时间和精力;计算机硬件和软件的不同有时会掩盖算法本身的优劣;算法测试数据很难合理设计,效率高的算法在很小的测试数据面前往往得不到体现。基于事后统计方法的种种缺陷,我们考虑不予采纳。
2.6.2 事前分析估算方法
在计算机编制程序之前,依据统计方法对算法进行估算。
一个用高级程序语言编写出来的程序在计算机上运行所消耗的时间主要取决于:
- 算法采用的策略、方法。(算法好坏的根本)
- 编译产生的代码质量。(软件支持)
- 问题的输入规模。
- 机器执行指令的速度。(硬件支持)
抛开计算机软硬件有关因素,也就是说,一个程序的运行时间,依赖算法的好坏和问题的输入量。
以上文求和算法为例:
第一种算法:
第二种算法:
第一种算法总共执行2n+3次,第二种算法总共执行了3次,除首尾相同部分代码,实际上就是n次与1次的差距。并且随着n越来越大,第一种算法运行次数也会越来越大,而第二种算法的运行次数都为1,算法的好坏显而易见。