1.算法定义
算法:算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或者多个操作。算法定义中提到了指令,指令是能够被人或者机器等计算装置执行的指令。它可以是计算机指令,也可以是我们平时的语言文字。为了解决某个问题,需要把指令表示成一定的操作序列,操作序列包括一组操作,每一个操作都完成特定的功能,这就是算法。
2.算法的特性
算法具有五个基本特性:
1.输入输出
输入输出:算法具有零个或者多个输入,至少有一个或者多个输出。对于绝大多数算法来说,输入参数是有必要的。但是比如说打印HelloWorld
这样的代码,不需要输入参数,算法可以具有零个输入。算法至少有一个或者多个输出,如果一个算法没有输出,那么该算法是没有意义的。
2.有穷性
有穷性:指算法在执行有限的步骤后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。
3.确定性
确定性:算法的每一步骤都具有确定的含义,不会有二义性。算法在一定条件下,只有一条执行路径,相同的输入只能有唯一的输出结果。算法的每个步骤都被精确定义而无二义性。
4.可行性
可行性:算法的每一步必须是可行的,也就是说每一步都能够通过有限的次数完成。可行性意味着算法可以转化为程序上机运行,并得到正确的结果。
3.算法设计的要求
算法设计有以下几个要求:
1.正确性:
正确性:算法的正确性是指算法至少具有输入,输出和加工处理无二义性,能正确反映问题的需求,能够得到问题的正确答案。
2.可读性:
可读性:算法设计的另一个目的是为了便于阅读,交流,理解。
3.健壮性:
健壮性:当输入数据不合法时,算法也能做出相应处理,而不是产生异常或者莫名其妙的结果。
4.时间效率高和存储量低
4.算法效率的度量方法
刚刚提到设计算法要提高效率,这里效率大都指算法的执行时间效率。算法效率的度量有以下方法:
1.事后统计方法
事后统计方法:这种方法主要是通过设计好的测试程序和数据,利用计算机计时程序对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。但是这个方法有很多缺陷:
- 必须依据算法事先绘制好程序,通常耗费大量的时间和精力。
- 执行时间的比较依赖计算机软件和硬件等环境因素。
- 算法的测试数据设计困难,并且程序的运行时间往往还与测试数据的规模有很大关系,效率高的算法在小的测试数据面前得不到体现。基于这些缺陷我们一般不予采纳。
2.事前分析估计法
- 事前分析估计法:在计算机程序绘制前,依据统计方法对算法进行估算。一个用高级语言编写的程序在计算机上运行时所消耗的时间取决于如下因素:其中第一条当然是算法好坏的根本,第二条要有软件来支持,第四条要看硬件性能。抛开与计算机软件和硬件相关的因素,一个程序的运行时间,依赖于算法的好坏和问题的输入规模。所谓问题的输入规模是指输入量的多少。
- 算法采用的策略和方法
- 编译产生的代码质量
- 问题的输入规模
- 机器执行指令的速度
- 示例:如下是求和的两种算法,显然第一种算法总的执行次数为2n+3次,第二种算法总的执行次数为3次。事实上两个算法第一条和最后一条的开销是一样的,我们所关注的代码其实是中间那部分,我们把循环看作一个整体,忽略头尾循环判断的开销,那么这两个循环其实就是n次和一次的差距。第一种算法中基本操作数量与输入规模n的关系为f(n)=n,第二种算法中基本操作数量与输入规模n的关系为f(n)=1。显然算法二比算法一的效率更高效。测定运行时间最可靠的方法就是估算对运行时间有消耗的基本操作的执行次数。运行时间和这个执行次数成正比。在分析程序的运行时间时,最重要的是把程序看成是独立于程序语言的算法和一系列步骤。
//算法一:
int sum = 0 , n = 100; //执行一次
for(int i = 1 ; i <= n ; i ++) //执行n+1次
{
sum += i; //执行n次
}
printf("sum:%d",sum); //执行一次
//算法二:
int sum = 0 , n = 100; //执行一次
sum = (n+1) * n / 2; //执行一次
printf("sum:%d",sum); //执行一次
- 基于事前分析估计法度量算法的效率,有两种方法分别是时间复杂度和空间复杂度
5.时间复杂度和空间复杂度
1.时间复杂度
- 算法时间复杂度:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度记作:
T(n) = O(f(n))
。它表示随着问题规模的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度简称为时间复杂度 。其中f(n)是关于问题规模n的某个函数。这种大写O()来体现时间复杂度的计法,我们称作为大O计法。一般情况下,随着n的增大,T(n)增长最慢的算法称为最优算法。 - 最坏时间复杂度:计算最坏情况下的时间复杂度。一般没有特别情况说明都是指最坏时间复杂度。
- 平均时间复杂度:平均运行时间是所有情况中最有意义的,因为它是期望的运行时间。计算所有情况的平均值,这种时间复杂度的计算方法称为平均时间复杂度。
- 常见的时间复杂度:O(1) < O(logn) < O(n) < O(n^2) <O(2^n) < O(n!)
2.空间复杂度
空间复杂度:算法空间复杂度通过计算算法所需的存储空间得到,算法空间复杂度的计算公式为S(n) = O(f(n)),其中n为问题规模,f(n)为语句关于n所占存储空间的函数。当不限定词使用“复杂度”时,通常都是指时间复杂度。
3.推导大O阶方法
根据以下三个步骤推导算法的时间复杂度以及空间复杂度,
- 用常数1取代运行时间中所有的加法常数
- 在修改后的运行次数函数中,只保留最高阶项。
- 如果最高阶项存在且不是一,则去除与这个项相乘的常数
6.实例
- 对于n阶汉诺塔问题,其时间复杂度和空间复杂度分别是多少?
/**
* @param n 圆盘的数量
* @param source 源柱子
* @param assist 辅助柱子
* @param dest 目的柱子
*/
void han(int n, string source, string assist, string dest) {
// 递归出口
if (n == 1) {
cout << source << "-->" << dest << endl;
} else {
han(n - 1, source, dest, assist);
cout << source << "-->" << dest << endl;
han(n - 1, assist, source, dest);
}
}
int main() {
// 3阶汉诺塔
han(3, "A", "B", "C");
return 0;
}
- 确定汉诺塔问题的递归关系式如下,然后计算t(n)与n的关系
t(n) = 2t(n-1) + 1 n>=1
1 n = 1
- 最终计算其时间复杂度为O(2^n),空间复杂度即为递归深度,为O(n)。
7.总结
掌握算法时间复杂度的计算,深究自己写的代码是否高效,如何优化自己写的算法让计算机更高效执行程序是十分必要的。