大话数据结构笔记-第二章 算法
一、笔记
2.1 算法
算法是描述解决问题的方法。
算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
指令能被人或者机器等计算装置执行。可以是计算机指令,也可以是我们平时的语言文字。
为了解决某个或者某类问题,需要把指令表示成一定的操作序列,操作序列包括一组操作,每一个操作都完成特定 的功能,这就是算法。
- 数据结构与算法关系
学习数据结构中,谈到算法,也是为了帮助理解好数据结构。
2.2 算法的特性
算法的5个特性:输入、输出、有穷性、确定性和可行性。
算法的输入可以是零个。但算法至少有一个或多个输出。输出形式可以是打印输出或返回一个或多个值等。
有穷性:指算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。
确定性:算法的每一步骤都具有确定的含义,不会出现二义性。
可行性:算法的每一步都必须是可行的,也就是说,每一步都能通过执行有限次数完成。意味着算法可以转换为程序上机运行,并得到正确的结果。
2.3 算法设计的要求
算法设计的要求:正确性、可读性、健壮性、时间效率高和存储量低。
- 正确性:算法的正确性是指算法至少应该具有输入、输出和加工处理五歧义性、能正确反映问题的需求、能够得到问题的正确答案。
正确性大体分为以下四个层次:
①算法程序没有语法错误。
②算法程序对于合法的输入数据能够产生满足要求的输出结果。
③算法程序对于非法的输入数据能够得出满足规格说明的结果。
④算法程序对于精心选择的,甚至***难的测试数据都有满足要求的输出结果。
证明一个复杂的算法在所有层次上都正确代价非常昂贵,所以一般情况下,把层次3作为一个算法是否正确的标准。
- 可读性:算法设计的另一个目的是为了便于阅读、理解和交流。
可读性高有助于人们理解算法,晦涩难懂的算法往往隐含错误,不易被发现,并且难于调试和修改。
- 健壮性:当输入数据不合时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果。
- 时间效率,指算法的执行时间。执行时间短的算法效率高,执行时间长的效率低。
- 存储量需求指的是算法在执行过程中需要的最大存储空间,主要指算法程序运行时所占用的内存或外部硬盘存储空间。
设计算法应该尽量满足时间效率高和存储量低的需求。
2.4 算法效率的度量方法
- 事后统计方法:主要是通过设计好的测试程序和数据,利用计算机计时器对不用算法编制的程序的运行时间进行比较,从而确定算法效率的高低。
缺陷:
①必须依据算法实现编号程序,花费大量的时间和精力。
②依赖计算机硬件和软件等环境因素。
③算法测试数据设计困难。测试数据规模。
一般不予采纳事后统计方法。
- 事前分析估算方法:在计算机程序编制前,依据统计方法对算法进行估算。
运行时间所消耗的时间取决于:
①算法采用的策略、方法;(根本)
②编译产生的代码质量。(软件支持)
③问题的输入规模。(输入量的多少)
④机器执行指令的速度。(硬件性能)
所以,除去软硬件因素,一个程序的运行时间,依赖于算法的好坏和问题的输入规模。
测试运行时间最可靠的方法就是计算对运行时间有消耗基本操作的执行次数。运行时间与这个技术成正比。
最终,在分析程序的运行时间时,最重要的是把程序看成是独立于程序设计语言的算法或一系列步骤。
把基本操作的数量与输入规模关联起来,即基本操作的数量必须表示成输入规模的函数。
2.5 函数的渐进增长
- 函数的渐进增长:给两个函数 f(n) 和 g(n),如果存在一个整数N,使得对于所有的n>N, f(n) 总是比 g(n)大,那么,我们说 f(n) 的增长渐进快于 g(n)。
判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注主项(最高阶项)的阶数。
某个算法,随着 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阶:
①用常数 1 取代运行时间中的所有加法常数。
②在修改后的运行次数函数中,只保留最高阶项。
③如果最高阶项存在且不是 1,则去除与这个项相乘的常数。
得到的结果就是大O阶。
常数阶:不管常数是多少,都记作 O(1),而不能是 O(3)、 O(12)等其他任何数字。所以在单纯的分支结构(不包含在循环结构中),其时间复杂度也是 O(1)。
线性阶:需要确定某个特定语句或某个语句集运行的次数。分析算法的复杂度,关键就是要分析循环结构的运行情况。
理解大O推导不算难,难的是对数列的一些相关运算,这更多的是考察你的数学知识和能力。
执行次数函数 |
阶 |
非正式术语 |
12 |
O(1) |
常数阶 |
2n+3 |
O(n) |
线性阶 |
3n2+2n+1 |
O(n2) |
平方阶 |
5log2n+20 |
O(logn) |
对数阶 |
2n++3nlog2n+19 |
O(nlogn) |
nlogn阶 |
6n3+5n2+2n+12 |
O(n3) |
立方阶 |
2n |
O(2n) |
指数阶 |
常用的时间复杂度所耗费的时间从小到大依次是:
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
- 最坏情况与平均情况
最坏情况运行时间是计算最坏情况下的时间复杂度,是一种保证,这是应用中一种最重要的需求。除非特别指定,提到的运行时间都是最坏情况的运行时间。
平均运行时间是计算所有情况的平均值,是所有情况中最有意义的,因为它是期望的运行时间。现实中,平均运行时间很难通过分析得到,一般都是通过运行一定数量的实验数据后估算出来的。
2.6 算法空间复杂度
通过一笔空间上的开销来换取计算时间。
算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作:S(n) = O( f(n) ),其中,n 为问题的规模, f(n) 为语句关于 n 所占用存储空间的函数。
一般情况下,一个程序在机器上执行时,除了需要存储程序本身的指令、常数、变量和输入数据外,还需要存储对数据操作的存储单元。只需要分析该算法在实现时所需的辅助单元即可。
若算法执行时所需的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工作。空间复杂度为 O( 1 )。
使用”时间复杂度“来指运行时间的需求,使用”空间复杂度“指空间需求。当不限定词地使用”复杂度“时,通常都是指 时间复杂度。
二、总结
- 数据结构与算法的关系是相互依赖不可分割的。
- 算法的定义:是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
- 算法的特性:输入、输出、有穷性、确定性和可行性。
- 算法设计的要求:正确性、可读性、健壮性、时间效率高和存储量低。
- 算法特性与算法设计容易混淆,需要对比记忆。
- 算法效率的度量方法:事后统计方法(不科学、不准确)、事前分析估算方法。
- 函数的渐进增长:给两个函数 f(n) 和 g(n),如果存在一个整数N,使得对于所有的n>N, f(n) 总是比 g(n)大,那么,我们说 f(n) 的增长渐进快于 g(n)。
某个算法,随着 n 的增大,它会越来越优于另一算法,或者越来越差于另一算法。通过算法时间复杂度来估算算法时间效率。
- 推导大O阶:
①用常数 1 取代运行时间中的所有加法常数。
②在修改后的运行次数函数中,只保留最高阶项。
③如果最高阶项存在且不是 1,则去除与这个项相乘的常数。
得到的结果就是大O阶。
- 常用的时间复杂度所耗费的时间从小到大依次是:
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
三、知识点习题
1、设n为正整数。试确定下列各程序段中前置以记号@的语句的频度:
(1)for (i = 1;i <= n; i++) { for (j = 1; j <= i; j++) { for (k = 1; k <= j; k++) { @ x += delta; } } } (2)x = 91; y = 100; while (y > 0) { @ if (x > 100) { x -= 10; y--; } else x++; } 答案:(1)n(n+1)(n+2)/6 (2)1100
2、假设n为2的乘幂,并且n>2,试求下列算法的试卷复杂度及变量count的值(以n的函数形式表示)。
int Time (int n) { count = 0; x = 2; while (x < n/2) { x *= 2; count++; } return (count); }//Time 答案: O(log2n) count=log2n−2
3、已知有实现同一功能的两个算法,其时间复杂度分别为 O(2n )和 O(n10),假设现实计算机可连续运算的时间为107秒(100 多天),又每秒可执行基本操作(根据这些操作来估算算法时间复杂度 )105次。 试问在此条件下,这两个算法可解问题的规模(即n值的范围)各为多少?哪个算法更适宜?请说明理由 。
解: 2n =1012 n=40
n10 = 1012 n=16
则对于同样的循环次数 n,在这个规模下,第二种算法所花费的代价要大得多。故在这个规模下,第 一种算法更适宜。