830 likes | 985 Views
第一章 绪论 ( P1 ). 第一章基本内容. 数据结构研究的主要内容 (1.1 节 ) 与 数据结构相关的基本概念 (1.2 、 1.3 节 ) 数据、数据元素、数据结构等 抽象数据类型 ADT —— 重点 算法的概念、描述及评价标准 (1.3 节、 1.4 节 ) 算法设计的基本要求 描述算法的类 C 语言 算法的 时间复杂度 —— 重点 编程原则. 本次课基本内容. 算法的概念、描述及评价标准 (1.3 节、 1.4 节 ) 算法设计的基本要求 描述算法的类 C 语言 算法的时间复杂度 —— 重点. 学习重点.
E N D
第一章基本内容 • 数据结构研究的主要内容(1.1节) • 与数据结构相关的基本概念(1.2、1.3节) • 数据、数据元素、数据结构等 • 抽象数据类型ADT——重点 • 算法的概念、描述及评价标准(1.3节、1.4节) • 算法设计的基本要求 • 描述算法的类C语言 • 算法的时间复杂度——重点 • 编程原则
本次课基本内容 • 算法的概念、描述及评价标准(1.3节、1.4节) • 算法设计的基本要求 • 描述算法的类C语言 • 算法的时间复杂度——重点
学习重点 • 描述算法的类C语言 • 算法的时间复杂度分析
学习难点 • 语句频度和算法时间复杂度的估算方法
主要内容 • 算法的定义、五准则、设计原则 算法的描述——类C描述语言 算法的评价 • 语句频度 • 时间复杂度 • 渐近时间复杂度 • 例
算法 • 概念 • 解决某个特定问题的一种方法或一个过程。 • 描述计算机解决给定问题的操作过程(解题方法),即为解决某一特定问题而由若干条指令组成的有穷序列。 • 一系列将输入转换为输出的计算步骤。
算法五个准则 • 有穷性 • 执行了有限条指令后一定要终止。 • 确定性(无二义性) • 算法的每一步操作都必须有确切定义,不得有任何歧义性。且在任何条件下,相同的输入只能得到相同的输出。 • 可行性 • 算法中描述的操作都可以通过已经实现的基本运算执行有限次实现。 • 输入 • 一个算法有n(n≥0)个初始数据的输入。 • 输出 • 一个算法有一个或多个与输入有某种关系的有效信息的输出。
例一 for(n=0; ;n++) ; 例 • 例二 • for(n=0;n<100;n++) • ; • printf(“n=%d\n”,n);
程序和算法 • 算法用抽象的语言描述解决特定问题的每一步的操作。程序是计算机能理解和执行的指令序列。 • 一个程序实现一个算法。 • 算法和程序的区别是算法的执行是有穷的,而程序的执行可以是无限的。例如操作系统程序,只要系统不受破坏,操作系统的运行将不会停止。
算法设计原则 • 正确性——要求算法能够正确地执行预先规定的功能,并达到所期望的性能要求。 • 不含语法错误。 • 对于n组输入数据能够得出满足规格说明要求的结果。 • 对于精心选择的典型、边界性的n组输入数据能得出满足规格说明要求的结果。 • 对于一切合适的输入数据都能得出满足规格说明要求的结果(穷举)。 • 可读性——易于理解、易于编码、易于调试。 • 健壮性——算法中拥有对输入数据、打开文件、读取文件记录、分配内存空间等操作的结果检测,并通过与用户对话的形式做出相应的处理选择。 • 高时间效率与高空间效率——将算法变换为程序后,该程序在计算机上运行时所花费的时间及所占据空间尽可能少。
算法的描述 • 可用自然语言、高级程序设计语言或类同于高级程序设计语言的伪码描述 • ——惟一的要求:必须精确地描述计算过程。
选择算法描述语言的准则 • 该语言应该具有描述数据结构和算法的基本功能; • 该语言应该尽可能地简捷,以便于掌握理解; • 使用该语言描述的算法应能较容易地转换成任何一种程序设计语言。 • 一般而言,描述算法最合适的语言是介于自然语言和程序语言之间的伪语言。 • 控制结构往往类似于Pascal、C等程序语言; • 可使用任何表达能力强的方法使算法表达更加清晰和简洁,而不至于陷入具体的程序语言的某些细节。
类C描述语言 • 本课的描述——采用“类C”描述语言。 • 通过对C语言进行精心筛选保留的一个核心子集 • 为了便于描述,又做了若干扩展修改,增强了语言的描述功能 • 算法易于上机验证
算法的评价 • 评价标准(设计原则) • 首先,正确 • 此外,主要考虑如下几点: • 执行算法所耗费的时间; • 执行算法所耗费的存储空间,其中主要考虑辅助存储空间; • 算法应易于理解,易于编码,易于调试等等。 • 评价方法: • 事后统计法 • 事前分析估算法
算法性能的选择 • 使用次数较少的程序简明易懂; • 反复多次使用的程序快速; • 数据量极大,机器的存储空间较小节省空间。
算法的时间性能分析 • 算法的执行时间 • 语句频度 • 问题规模 • 算法的时间复杂度 • 算法的渐进时间复杂度
算法执行时间 • 算法执行时间=算法中每条语句的执行时间之和 • 语句频度(Frequency Count) =语句的执行次数 • 每条语句的执行时间=语句频度×语句执行一次所需时间 • 决定算法执行时间因素 • 编程所用的语言、编译器的质量 • 执行算法机器的速度 • 算法所解问题的规模(Size)——算法求解问题的输入量, 一般用一个整数表示。 • ***若要独立于机器的软、硬件系统来分析算法的时间耗费,则可设每条语句执行一次所需时间均是单位时间,算法执行时间就是该算法中所有语句的频度之和,即是问题规模的函数。
算法执行时间 for (i=1;i<n;i++) { y=y+1; //原操作 for (j=0; j<=(2*n); j++) x++; //原操作 } • 算法:控制结构(顺序、分支和循环三种)+原操作(固有数据类型的操作,称为“基本操作” ) • 算法的执行时间≈∑(原操作的执行时间)。 • 取决于 “起主要作用”的原操作的语句频度。 • 多个循环语句时,算法时间复杂度取决于嵌套层数最多的最深循环内所包含的基本操作的语句频度。
时间复杂度、渐近时间复杂度 • 算法的时间复杂度(Time Complexity)T(n)——算法的时间耗费,是该算法所求解问题规模n的函数。 • 渐进时间复杂度——问题规模n 趋向无穷大时的时间复杂度T(n)的数量级(阶或增长率)。是评价算法时间性能的主要标准。(宏观评价) • 算法分析时往往使用算法的渐近时间复杂度。 • 大“O”表示法:T(n)=O(f(n)) 。 • 表示随问题规模n的增大算法执行时间的增长率和f(n)的增长率相同。其中的f(n)一般是算法中频度最大的语句频度。 • 即,只求出T(n)的最高阶,忽略其低阶项和常系数。 T(n)=3n2-5n+10000=O(n2)
“大O”表示法 • 数学符号“O”的严格的数学定义——“大O”表示法: • 若T(n)和f(n)是定义在正整数集合上的两个函数,则T(n) =O(f(n))表示存在正的常数C和n0 ,使得当问题规模n≥n0时都满足0≤T(n)≤Cf(n) 。即当n充分大时,算法复杂度不大于f(n)的一个常数倍。 • 采用“大O”表示法的基本思想: • 在分析算法的复杂度时,忽略零星变量的存储开销和循环外个别语句的执行时间,简化时间和空间代价的度量,主要关注复杂性的量级(算法的主要代价),而忽略量级的系数。
“大O”表示法常用计算规则 • 常用的计算规则: • 加法准则 • T(n) = T1(n) + T2(n) = O(f1(n)) + O(f2(n))= O(max(f1(n), f2(n))) • 乘法准则 • T(n) = T1(n)×T2(n) = O(f1(n)) ×O(f2(n))= O(f1(n)×f2(n))
例 • 两个算法A1和A2求解同一问题,时间复杂度分别是T1(n)=100n2 ,T2(n)=5n3,则渐近时间复杂度分别是O(n2)和O(n3),n为输入量。 • 两个算法的时间开销之比为5n3/100n2=n/20。 • (1)n<20,T1(n)>T2(n),算法A2花费时间较少。 • (2)n>20, 算法A1要有效得多。 • 即,渐近时间复杂度O(n2)和O(n3) 从宏观上评价了这两个算法在时间方面的质量。
最坏时间复杂度和平均时间复杂度 • 本书在分析算法的性质时将主要考虑它们在最坏情况下的代价,个别地方也涉及到其他情况。 • 最坏时间复杂度 • 一般讨论的时间复杂度均是最坏情况下的时间复杂度。 • 算法在任何输入实例上运行时间的上界。 • 平均时间复杂度 • 指所有可能的输入实例均以等概率出现的情况下,算法的期望运行时间。
例:分析程序段的时间复杂度 例1: for (i=1;i<n;i++) { y=y+1; for (j=0; j<=(2*n); j++) x++; } /* 1 * / /* 2 * / 分析: 语句的频度指的是该语句重复执行的次数。 一个算法中所有语句的频度之和构成了该算法的运行时间。 语句1的频度是:n-1 语句2的频度是: 则该程序段的时间复杂度:T(n)=
分析: 语句1的频度是:1 设语句2的频度是m,则有: 即 ,取最大值 则该程序段的时间复杂度为: 例:分析程序段的时间复杂度 /* 1 * / 例2: i=1; while (i<=n) i=i*2; /* 2 * /
例:分析程序段的时间复杂度 • 例3: x=1; for(int i=1;i<=n;i*=2) for(int j=1;j<=n;j++) x++; 分析: 设外循环的次数为m次,则有 • 指令执行次数为 • 则该程序段的时间复杂度为:
常见函数的时间复杂度按数量递增排列及增长率常见函数的时间复杂度按数量递增排列及增长率 • 常数阶O(1) • 对数阶O(log2n) • 线性阶O(n) • 线性对数阶O(nlog2n) • 平方阶O(n2) • 立方阶O(n3) • …… • k次方阶O(nk) • 指数阶O(2n)
算法分析 • 算法的空间效率 • 存储量——算法执行过程中所需的最大存储空间。包括: • 输入数据所占空间 • 程序本身所占空间 • 辅助变量所占空间——除算法代码本身和输入输出数据所占据的空间外,算法临时开辟的存储空间单元。 • 空间效率——算法的执行过程中所占据的辅助空间数量。 • 通常以算法的空间复杂度作为量度。 • 算法空间复杂度:S(n)=O(g(n))。 • 表示随着问题规模n的增大,算法运行所需辅助存储量的增长率与g(n)的增长率相同。
编程原则——命名 • 至始至终都要非常谨慎地为变量和函数命名,同时对变量和函数进行详尽地解释; • 函数、常量、全局变量和程序不同部分中使用的类型的命名应该尽量有意义,名称能清晰说明函数、变量的目的。 • 临时变量和局部变量的命名应该尽量简单。 • 使用公共前缀或后缀将有相同常用类型的名称关联起来。 • 避免使用故意的拼写错误或无意义的前缀来获得不同的名称。如index, indx, ndex, index2… • 避免使用对问题没有任何意义或意义很小的名称。 • 避免使用彼此间拼写相近或易于混淆的名称。 • 小心使用字母”l”、”O”和”o”,不要单独使用。
编程原则——文档及其格式 • 随着程序的编写而撰写文档。尽量保持文档既简洁、又能起说明作用。 • 在每个函数的开始部分放置序言,包括 • 标识符(程序员姓名、编程日期、版本号); • 陈述函数的目的及其使用方法的语句; • 函数所做的改变及其所使用的数据; • 更多的对程序外部文档的使用。 • 编写的每一个函数都应包含准确的初始条件和操作结果。 • 声明变量、常量或类型时,要解释它们的作用以及使用方法。最好能通过名称表达出这些信息。 • 使用简短的注释描述程序的各个重要部分(段落或函数),说明这些部分的目的或作用。 • 如果程序重要部分的结尾不明显,则对其进行标识。 • 避免出现只是模仿代码功能或其他无意义的令人费解的注释。 • 对使用了某个技巧或意义不太明确的语句进行注释。最好避免使用这样的语句。 • 代码本身应该能够说明程序的工作方式。文档应该能够说明程序的工作原理以及作用。 • 无论何时修改程序,一定要确保同时修改相应的文档。 • 使用空格、空行、缩进等使程序易于阅读。程序的阅读时间远远多于程序的编写时间,所以要尽量使得程序易于阅读。
编程原则——程序的细化和模块化 • 不要“只见树木,不见森林”。——自顶向下细分原则 • 主程序要将所有的工作都准确地划分到多个函数中。 • 每个函数都只完成一个任务,但一定要很好地完成。 • 每个函数都应该有所隐藏。 • 尽量保持连接简单。尽量避免使用全局变量,从而避免产生副作用。若必须使用全局变量作为函数的输入参数,则要使用文档进行详细记录。
编程原则 • 输入和输出 • 将输入输出函数划分为独立的函数,这样便于根据不同计算机系统对它们进行定制和修改。 • 测试程序 • 测试数据的质量远比其数量重要得多。 • 程序测试主要是用来发现bug的存在,而不是发现bug不存在。 • 在程序编写时将白盒方法应用于各个小模块,在程序完成之后用黑盒方法测试程序中的较大部分。 • 黑盒方法:根据说明选择测试数据,不考虑程序细节 • 简单值、典型的有实际意义的值、极限值、非法值 • 白盒方法:为各程序的逻辑结构每一个可能发生的选择设计可以引起该选择的测试数据。
小结 • 类C语言可用来很好地描述算法。 • 算法效率可用算法的时间复杂度和空间复杂度来衡量。
小结 • 算法——对问题求解的一种描述。 • 一完整算法应具有五要素——有穷性、确定性、可行性、有输入和有输出。 • 算法的时间复杂度是比较不同算法效率的一种准则,其估算基于算法中基本操作的重复执行次数,或处于最深层循环内的语句的频度。 • 算法空间复杂度可作为算法所需存储量的一种量度,它主要取决于算法的输入量和辅助变量所占空间,若算法的输入仅取决于问题本身而和算法无关,则算法空间复杂度的估算只需考察算法中所用辅助变量所占空间,若算法的空间复杂度为常量级,则称该算法为原地工作的算法。
第一章小结 • 数据结构研究的主要内容 • 基本概念 • 数据、数据元素、数据结构(定义、逻辑结构及分类、存储结构及分类)等 • 抽象数据类型ADT • 算法 • 算法设计的基本要求 • 描述算法的类C语言 • 算法的时间复杂度
习题 • 分析程序段的时间复杂度 • x=1; • for (i=1;i<=n;i++) • for (j=1;j<=i;j++) • for (k=1;k<=j;k++) • x++; • x=1; for(int i=1;i<=n;i*=2) for(int j=1;j<=i;j++) x++; • 按增长率由小至大的顺序排列下列各函数: • 2100,(2/3)n,(3/2)n,2n ,log2n,nlog2n,n3/2
习题一 • 简述下列术语: 数据、数据元素(结点)、逻辑结构、存储结构、数据结构、抽象数据结构和数据类型。 • 试根据以下信息: 校友姓名、性别、出生年月、毕业时间、所学专业、现在工作单位、职称、职务、电话等, 为校友录构造一种适当的数据结构(作图示意),并定义必要的运算和用文字叙述相应的算法思想。 • 什么是算法?算法设计的目标是什么? • 如何评价一个算法? • 按增长率由小至大的顺序排列下列各函数: 2100,(2/3)n,(3/2)n,2n ,log2n,nlog2n,n3/2
设三个函数f, g, h分别为 f(n)=100n3+n2+1000 g(n)=25n3+5000n2 h(n)=n1.5+5000nlog2n 请判断下列关系是否成立: (1) f(n)=O(g(n)) (2) g(n)=O(f(n)) (3) h(n)=O(n1.5) (4) h(n)=O(nlog2n) • 设n为正整数,利用大"O"记号,将下列程序段的执行时间表示为n的函数。 (1) i=1; k=0; while(i<n){ k=k+10*i; i++;} (2) i=0; k=0;do{ k=k+10*i; i++; }while(i<n); (3) i=1; j=0; while(i+j<=n) {if (i<j) i++;else j++;} (4) x=n; // n>1 while (x>=(y+1)*(y+1)) y++; (5) x=91; y=100; while(y>0) if(x>100){x=x-10;y--;} else x++;
类C描述语言 • 预定义常量和类型 #define TRUE 1 #define FALSE 0 #define OK 1 #define ERROR 0 #define MAXSIZE 200 • typedef int Status; • /*函数的类型, 其值是函数结果状态代码*/ • 数据结构的表示(存储结构)用类型定义(typedef)描述。数据元素类型约定为ElemType类型,用户根据具体情况自行定义该数据类型。
类C描述语言 • 基本操作的算法描述 函数类型 函数名(函数参数表){ //算法说明 语句序列 }//函数名 • ***除函数参数表中的参数需要说明数据类型外,函数中使用 的局部变量可以不做变量说明,必要时给出相应的注释。 • ***在书写算法时,应该养成对重点语句段落添加注解的良好 习惯。 • ***语句序列中仅包含C语言的三种基本结构:顺序结构、选 择结构和循环结构。
类C描述语言 • 赋值语句 简单赋值 变量名=表达式; 串联赋值 变量名1=变量名2=…=变量名k=表达式; 成组赋值(变量名1, …, 变量名k)=(表达式1, …,表达式k); 结构名=结构名; 结构名=(值1, …, 值k); 变量名[ ]=表达式; 变量名[起始下标. .终止下标]=变量名[起始下标. .终止下标]; 交换赋值 变量名变量名; 条件赋值 变量名=条件表达式?表达式T:表达式F;
类C描述语言 • 选择语句 条件语句1 if(表达式) 语句; 条件语句2 if(表达式) 语句; else 语句; 开关语句1 switch(表达式){ case 值1: 语句序列1; break; … case 值n: 语句序列n; break; default: 语句序列n+1; } 开关语句2 switch { case 条件1: 语句序列1; break; … case 条件n: 语句序列n; break; default: 语句序列n+1; }
类C描述语言 • 循环语句 for语句 for(赋初值表达式序列; 条件; 修改表达式序列) 语句; while语句 while(条件) 语句; do-while语句 do { 语句序列; }while(条件); • 结束语句 函数结束语句 return 表达式; return; case结束语句 break; 异常结束语句 exit(异常代码); • 输入输出语句 输入语句 scanf([格式串], 变量1, …, 变量n); 输出语句 printf([格式串], 表达式1, …, 表达式n);
类C描述语言 • 注释 单行注释// 文字序列 • 基本函数 求最大值max(表达式1, …, 表达式n); 求最小值min(表达式1, …, 表达式n); 求绝对值abs(表达式); 求不足整数值floor(表达式); 求进位整数值ceil(表达式); 判定文件结束eof(文件变量)或eof; 判定行结束eoln(文件变量)或eoln; • 逻辑运算约定 与运算&&对于A&&B,当A的值为0时,不再对B求值。 或运算| |对于A| |B,当A的值为非0时,不再对B求值。
作业:分析程序段的时间复杂度 /* 1 * / • x=1; • for (i=1;i<=n;i++) • for (j=1;j<=i;j++) • for (k=1;k<=j;k++) • x++; /* 2 * / 分析: 由于内循环的执行次数虽与规模n无直接关系,但与外循环的变量取值有关。因此从内层向外层循环分析执行次数。 语句2的频度是 • 则该程序段的时间复杂度为:
作业:分析程序段的时间复杂度 • x=1; for(int i=1;i<=n;i*=2) for(int j=1;j<=i;j++) x++; 分析: 指令执行次数为 • 则该程序段的时间复杂度为: