教学课件:《数据结构》陈越.ppt
- 文档编号:30779877
- 上传时间:2023-08-30
- 格式:PPT
- 页数:300
- 大小:14.58MB
教学课件:《数据结构》陈越.ppt
《教学课件:《数据结构》陈越.ppt》由会员分享,可在线阅读,更多相关《教学课件:《数据结构》陈越.ppt(300页珍藏版)》请在冰豆网上搜索。
第1章概论,【定义】“数据结构是计算机中存储、组织数据的方式。
精心选择的数据结构可以带来最优效率的算法。
”,1/25,1引子,例1.1该如何摆放书,才能让读者很方便地找到你手里这本数据结构?
第1章概论,【分析】,2/25,1引子,方法1随便放任何时候有新书进来,哪里有空就把书插到哪里。
方法2按照书名的拼音字母顺序排放。
方法3把书架划分成几块区域,每块区域指定摆放某种类别的图书;在每种类别内,按照书名的拼音字母顺序排放。
查找效率极低!
有时插入新书很困难!
可能造成空间的浪费!
第1章概论,1引子,例1.2写程序实现一个函数PrintN,使得传入一个正整数为N的参数后,能顺序打印从1到N的全部正整数。
voidPrintN(intN)inti;for(i=1;i=N;i+)printf(“%dn”,i);return;,voidPrintN(intN)if(!
N)return;PrintN(N1);printf(“%dn”,N);return;,3/25,第1章概论,1引子,例1.3多项式的标准表达式可以写为:
f(x)=a0+a1x+a2x2+anxn现给定一个多项式的阶数n,并将全体系数存放在数组a里。
请写程序计算这个多项式在给定点x处的值。
方法1计算多项式函数值的直接法,doublef(intn,doublea,doublex)/*计算阶数为n,系数为a0.an的多项式在x点的值*/inti;doublep=a0;for(i=1;i=n;i+)p+=ai*pow(x,i);returnp;,方法2秦九韶法f(x)=a0+x(a1+x(a2+x(an)doublef(intn,doublea,doublex)/*计算阶数为n,系数为a0.an的多项式在x点的值*/inti;doublep=an;for(i=n;i0;i-)p=ai-1+x*p;returnp;,4/25,秦九韶算法的计算速度明显比直接法快了一个数量级!
为什么会这样?
第1章概论,1引子,5/25,#include#includeclock_tstart,stop;/*clock_t是clock()函数返回的变量类型*/doubleduration;/*记录被测函数运行时间,以秒为单位*/intmain()/*不在测试范围内的准备工作写在clock()调用之前*/start=clock();/*开始计时*/function();/*把被测函数加在这里*/stop=clock();/*停止计时*/duration=(double)(stop-start)/CLK_TCK;/*计算运行时间*/*其他不在测试范围的处理写在后面,例如输出duration的值*/return0;,代码1.6测试函数function()的运行时间,即使解决一个非常简单的问题,往往也有多种方法,且不同方法之间的效率可能相差甚远解决问题方法的效率跟数据的组织方式有关(如例1.1)跟空间的利用效率有关(如例1.2)跟算法的巧妙程度有关(如例1.3),第1章概论,1引子,6/25,第1章概论,数据对象:
计算机要处理的事物,如例1.1中“图书”。
2.1术语定义,操作:
处理事物的动作集合,如例1.1中的“查找”和“插入”,例1.2的函数“求值”等。
算法:
操作的实现方法,如例1.1的按字母序排放的“查找”和“插入”、例1.2的“直接法”和例1.3的“秦九韶法”等。
通常一个算法用一个函数来实现。
逻辑结构:
数据对象的逻辑组织关系。
分为“线性”、“树”和“图”。
例1.1中按方法1来处理,就是把图书集看成是线性的结构;按方法3来处理,就是把图书集看成是树形的结构。
物理结构:
数据对象信息在计算机内存中的存储组织关系。
一般分为“顺序存储”和“链式存储”。
7/25,第1章概论,数据类型:
数据对象的类型确定了其“操作集”和“数据定义域”。
2.2抽象数据类型,抽象数据类型:
“抽象”的意思,是指我们描述数据类型的方法是不依赖于具体实现的,即数据对象集和操作集的描述与存放数据的机器无关、与数据存储的物理结构无关、与实现操作的算法和编程语言均无关。
简而言之,抽象数据类型只描述数据对象集和相关操作集“是什么”,并不涉及“如何做到”的问题。
8/25,第1章概论,例1.4“矩阵”的抽象数据类型定义,2.2抽象数据类型,类型名称:
Matrix数据对象集:
一个MN的矩阵A。
操作集:
对于任意矩阵A、B、CMatrix,以及正整数i、j、M、N,以下仅列出几项有代表性的操作。
1)MatrixCreate(intM,intN):
返回一个MN的空矩阵;2)intGetMaxRow(MatrixA):
返回矩阵A的总行数;3)intGetMaxCol(MatrixA):
返回矩阵A的总列数;4)ElementTypeGetEntry(MatrixA,inti,intj):
返回矩阵A的第i行、第j列的元素;5)MatrixAdd(MatrixA,MatrixB):
如果A和B的行、列数一致,则返回矩阵C=A+B,否则返回错误标志;6)MatrixMultiply(MatrixA,MatrixB):
如果A的列数等于B的行数,则返回矩阵C=AB,否则返回错误标志;7),9/25,【定义】一个算法是解决某一类问题的步骤的描述。
一般而言,算法应该符合以下五项要求:
(1)输入:
它接受一些输入(有些情况下不需要输入);
(2)输出:
至少产生一个输出;(3)确定性:
算法的每一步必须有充分明确的含义,不可以有歧义;(4)有限性:
算法是一个有限指令集,并一定在有限步骤之后终止;(5)可行性:
算法的每一步必须在计算机能处理的范围之内。
第1章概论,3.1算法定义,另外,算法的描述可以不依赖于任何一种计算机语言以及具体的实现手段。
可以用自然语言、流程图等方法来描述。
但是,用某一种计算机语言进行伪码描述往往使算法容易被理解,本书即采用C语言的部分语法作为描述算法的工具。
10/25,例选择法排序:
把n个整数从小到大排序。
思想:
从余下的未排序的部分整数中,挑选最小整数放在前面已排序部分的后面.,voidSelectionSort(intList,intN)/*将N个整数List0.ListN-1进行非递减排序*/for(i=0;iN;i+)MinPosition=ScanForMin(List,i,N1);/*从Listi到ListN1中找最小元,并将其位置赋给MinPosition*/Swap(Listi,ListMinPosition);/*将未排序部分的最小元换到有序部分的最后位置*/,选择排序=找最小整数+交换至合适位置.,第1章概论,3.1算法例子,11/25,具体衡量、比较算法优劣的指标主要有两个:
空间复杂度S(n)根据算法写成的程序在执行时占用存储单元的长度。
这个长度往往与输入数据的规模有关。
空间复杂度过高的算法可能导致使用的内存超限,造成程序非正常中断。
第1章概论,3.2算法复杂度,时间复杂度T(n)根据算法写成的程序在执行时耗费时间的长度。
这个长度往往也与输入数据的规模有关。
时间复杂度过高的低效算法可能导致我们在有生之年都等不到运行结果。
什么是“好”的算法?
例1.2的实现函数PrintN的递归算法S(n)太大。
例1.3的秦九韶算法的T(n)比较小。
12/25,第1章概论,3.2算法复杂度,例1.2的实现函数PrintN的递归算法的S(n)太大:
S(n)=cn其中:
n是需要打印的整数的个数,是变量;c是1个单位的内存空间占用存储单元的长度,为固定常数。
例1.3的秦九韶算法的T(n)比较小:
T1(n)=cn其中:
n是多项式的阶数,是变量;c是执行1次加法和乘法需要的时间,为固定常数。
而简单直接算法的T(n)比较大:
T2(n)=c1n2+c2n,其中:
n是多项式的阶数,是变量;c1是执行1/2次乘法需要的时间;c2是执行1次加法和1/2次乘法需要的时间,都是固定常数。
13/25,第1章概论,3.2算法复杂度,我们经常关注下面两种复杂度:
最坏情况复杂度:
Tworst(n),平均复杂度:
Tavg(n),显然:
Tavg(n)Tworst(n)。
对Tworst(n)的分析往往比对Tavg(n)的分析容易。
14/25,如果:
程序A执行了(3n+4)步,程序B执行了(2n+2)步,A一定比B慢吗?
No!
Why?
第1章概论,3.3复杂度的渐进表示法,如何来“度量”一个算法的时间复杂度呢?
首先,它应该与运行该算法的机器和编译器无关;,其次,它应该与要解决的问题的规模n有关;(有时,描述一个问题的规模需要多个参数),再次,它应该与算法的“1步”执行需要的工作量无关!
所以,在描述算法的时间性能时,人们只考虑宏观渐近性质,即当输入问题规模n“充分大”时,观察算法复杂度随着n的“增长趋势”:
当变量n不断增加时,解决问题所需要的时间的增长关系。
比如:
线性增长T(n)=cn即问题规模n增长到2倍、3倍时,解决问题所需要的时间T(n)也是增长到2倍、3倍(与c无关),平方增长:
T(n)=cn2即问题规模n增长到2倍、3倍时,解决问题所需要的时间T(n)增长到4倍、9倍(与c无关),15/25,第1章概论,引入下面几种数学符号:
定义1.1T(n)=O(f(n)表示存在常数c0,n00,使得当nn0时有T(n)cf(n),例1.3中秦九韶算法的时间复杂度是O(n),而简单直接法的时间复杂度是O(n2)。
定义1.2T(n)=(g(n)表示存在常数c0,n00,使得当nn0时有T(n)cg(n),3.3复杂度的渐进表示法,定义1.3T(n)=(h(n)表示T(n)=O(h(n)同时T(n)=(h(n),16/25,第1章概论,常用函数增长表,3.3复杂度的渐进表示法,17/25,第1章概论,3.3复杂度的渐进表示法,18/25,n,第1章概论,3.3复杂度的渐进表示法,19/25,第1章概论,对给定的算法做渐进分析时,有几个小窍门:
(1)若两段算法分别有复杂度T1(n)=O(f1(n)和T2(n)=O(f2(n),那么两段算法串联在一起的复杂度:
T1(n)+T2(n)=max(O(f1(n),O(f2(n),那么两段算法嵌套在一起的复杂度:
T1(n)T2(n)=O(f1(n)f2(n),3.3复杂度的渐进表示法,
(2)若T(n)是关于n的k阶多项式,那么T(n)=(nk),(3)一个循环的时间复杂度等于循环次数乘以循环体代码的复杂度。
例如下面循环的复杂度是O(N):
for(i=0;iN;i+)x=y*x+z;k+;,
(1)若干层嵌套循环的时间复杂度等于各层循环次数的乘积再乘以循环体代码的复杂度。
例如下列2层嵌套循环的复杂度是O(N2):
for(i=0;iN;i+)for(j=0;jN;j+)x=y*x+z;k+;,
(2)if-else结构的复杂度取决于if的条件判断复杂度和两个分枝部分的复杂度,总体复杂度取三者中最大。
即对结构:
if(P1)/*P1的复杂度为O(f1)*/P2;/*P2的复杂度为O(f2)*/elseP3;/*P3的复杂度为O(f3)*/总复杂度为max(O(f1),O(f2),O(f3)。
20/25,问题给定n个整数(可以是负数)的序列a1,a2,an,求函数f(i,j)=max(0,)的最大值。
若全部整数都是负数,则最大子列和为0.,算法1,intMaxSubsequenceSum(constintA,intN)intThisSum,MaxSum,i,j,k;/*1*/MaxSum=0;/*初始化最大子列和*/*2*/for(i=0;iMaxSum)/*如果刚得到的这个子列和更大*/*8*/MaxSum=ThisSum;/*则更新结果*/*i,j循环结束*/*9*/returnMaxSum;,T(N)=O(N3),教材p.15有分析.,第1章概论,4应用实例:
最大子列和问题,21/25,算法2,intMaxSubsequenceSum(constintA,intN)intThisSum,MaxSum,i,j;/*1*/MaxSum=0;/*初始化最大子列和*/*2*/for(i=0;iMaxSum)/*如果刚得到的这个子列和更大*/*7*/MaxSum=ThisSum;/*则更新结果*/*j循环结束*/*i循环结束*/*8*/returnMaxSum;,T(N)=O(N2),第1章概论,4应用实例:
最大子列和问题,22/25,算法3,分治法,治,分,4,5,6,2,6,8,11,T(N/2),T(N/2),O(N),T(N)=2T(N/2)+cN,T
(1)=O
(1),=22T(N/22)+cN/2+cN,=2kO
(1)+ckN此处N/2k=1,=O(NlogN),结论对N2k同样正确,程序在教材p.16-17,第1章概论,4应用实例:
最大子列和问题,23/25,该算法的核心思想是基于下面的事实:
如果整数序列a1,a2,an的最大和子列是ai,ai+1,aj,那么必定有对任意ilj都成立。
因此,一旦发现当前子列和为负,则可以重新开始考察一个新的子列。
算法4,“在线”算法,intMaxSubsequenceSum(constintA,intN)intThisSum,MaxSum,j;/*1*/ThisSum=MaxSum=0;/*2*/for(j=0;jMaxSum)/*5*/MaxSum=ThisSum;/*6*/elseif(ThisSum0)/*7*/ThisSum=0;/*endfor-j*/*8*/returnMaxSum;,T(N)=O(N),序列A仅需扫描一遍!
1,3246,任何时刻,“在线”算法都可以对已经读入的数据序列给出正确的最大子列和答案,第1章概论,4应用实例:
最大子列和问题,24/25,注:
不包括输入子列的时间.NANotAcceptable,不可接受的时间,第1章概论,4应用实例:
最大子列和问题,25/25,第2章实现基础,2.1引子,还是为每个具体应用都编一个程序?
类型名称:
数据集合的基本统计数据对象集:
集合S=,操作集:
ElementTypeAverage(S):
求S中元素的平均值;ElementTypeMax(S):
求S中元素的最大值;ElementTypeMin(S):
求S中元素的最小值;ElementTypeMedian(S):
求S中元素的中位数。
从不同的应用中抽象出共性的数据组织与操作方法?
例2.1在日常数据处理中经常碰到的问题是需要对一组数据进行基本的统计分析。
比如,分析一个课程班学生的平均成绩、最高成绩、最低成绩、中位数、标准差等。
同样的统计要求也可能发生在其他领域。
1/25,如何利用程序设计语言实现上述抽象类型?
第2章实现基础,2.1引子,1.数据存储,C语言(包括其他高级语言)提供了数组、结构、链表等。
数据结构的存储实现跟所需要的操作密切相关。
在数据结构里,是利用数组和链表方式来实现的,包括很复杂的数据结构,如图、树等。
2.操作实现,流程控制语句,即分支控制语句(如if-else、switch语句)、循环控制语句(如for、while、do-while语句)。
此外,还有模块化的程序设计方法函数,2/25,方法2基于问题分解,相近的另一个问题是:
求集合中的第K大整数。
当K=N/2时,集合的第K大整数就是中位数。
第2章实现基础,2.1引子,求中位数Median(S),方法1基于排序。
首先将集合S从大到小排序,第N/2(大于等于N/2的最小整数)个元素就是中位数。
求解集合第K大整数问题的一种递归思路是:
元素=e,元素e,当K=N1时,第K大整数就是e。
当KN1时,第K大整数是在S1中的第K大整数。
当KN1时,第K大整数是在S2中的第(K-N1)大整数。
比较慢!
3/25,例2.2求集合659821734的中位数。
【分析】由于该集合有9个元素,所以中位数应该是集合从大到小排序后的第9/2=5个元素。
首先,选取集合的第一个元素6,根据这个元素从集合中分解出S1=6,9,8,7,S2=5,2,1,3,4。
由于|S1|=45,所以该中位数应该在集合S2中,且是S2中第(54=1)大整数。
继续选取S2中的第一个整数5,将S2分解出两个集合S1=5,S2=2,1,3,4。
由于|S1|=1,所以5就是S2集合的第1大整数,也就是集合659821734的中位数。
第2章实现基础,1引子,4/25,第2章实现基础,2数据存储基础,变量是数据存储的基本单位。
变量的类型决定了存储和操作。
几种基本的数据类型:
整型、实型(浮点型)、字符型等。
提供了构造数据类型:
数组、结构、指针等。
数组数组是最基本的构造类型,它是一组相同类型数据的有序集合。
数组中的元素在内存中连续存放,用数组名和下标可以唯一地确定数组元素。
例2.3求集合元素的最大值。
集合元素存放在数组A中,数组大小为N。
5/25,指针,第2章实现基础,2数据存储基础,指针是C语言中一个非常重要的概念。
使用指针可以对复杂数据进行处理,能对计算机的内存进行分配控制,在函数调用中使用指针还可以返回多个值。
指针与数组,数组名是数组中第1个元素(下标为0)的地址,可以看作是常量指针,不能改变指针常量(数组名)的值。
用指针实现内存动态分配,分配函数void*malloc(unsignedsize)。
释放函数voidfree(void*ptr)。
6/25,结构,结构类型定义的一般形式为:
struct结构名类型名结构成员名1;类型名结构成员名2;类型名结构成员名n;,第2章实现基础,2数据存储基础,【定义】结构类型把一些可以是不同类型的数据分量聚合成一个整体。
同时,结构又是一个变量的集合,可以单独使用其变量成员。
结构变量的使用,使用结构变量就是对其成员进行操作。
格式为:
结构变量名.结构成员名。
此外,结构变量不仅可以作为函数参数,也可以作为函数的返回值。
结构数组:
结构与数组的结合,结构指针:
指向结构的指针,
(1)用*方式访问,形式:
(*结构指针变量名).结构成员名
(2)用指向运算符“-”访问指针指向的结构成员,形式:
结构指针变量名-结构成员名,对结构数组元素成员的引用是通过使用数组下标与结构成员操作符“.”相结合的方式来完成的,其一般格式为:
结构数组名下标.结构成员名,7/25,共用体【定义】共用体类型是指将不同的数据项组织成一个整体,它们在内存中占用同一段存储单元。
第2章实现基础,2数据存储基础,共用体类型定义的一般形式为:
union共用体名类型名成员名1;类型名成员名2;类型名成员名n;,各个成员变量在内存中都使用同一段存储空间,因此共用体变量的长度等于最长的成员的长度。
共用体的访问方式同结构体类似。
u.k=258的二进制表示:
u.ch0=2,u.ch1=1,8/25,链表,链表是一种重要的基础数据结构,也是实现复杂数据结构的重要手段。
它不按照线性的顺序存储数据,而是由若干个同一结构类型的“结点”依次串接而成的,即每一个结点里保存着下一个结点的地址(指针)。
链表又分单向链表,双向链表以及循环链表等。
单向链表的结构,使用结构的嵌套来定义单向链表结点的数据类型。
如:
structNodeElementTypeData;structNode*Next;,第2章实现基础,2数据存储基础,structNode*p;p=(structNode*)malloc(sizeof(structNode);,9/25,
(1)插入结点(p之后插入新结点t),单向链表的常见操作,
(2)删除结点,第2章实现基础,2数据存储基础,p-Next=t;,p-Next=t-next;,10/25,(3)单向链表的遍历,p=head;while(p!
=NULL)处理p所指的结点信息;p=p-Next;,(4)链表的建立,有两种常见的插入结点方式:
(1)在链表的头上不断插入新结点;
(2)在链表的尾部不断插入新结点。
如果是后者,一般需要有一个临时的结点指针一直指向当前链表的最后一个结点,以方便新结点的插入。
第2章实现基础,2数据存储基础,11/25,双向链表,如果将双向链表最后一个单元的Next指针指向链表的第一个单元,而第一个单元的Previous指针指向链表的最后一个单元,这样构成的链表称为双向循环链表。
第2章实现基础,2数据存储基础,structNodeElementTypeData;structNode*Next;structNode*Previous;,12/25,双向链表的插入、删除和遍历基本思路与单向链表相同,但需要同时考虑前后两个指针。
第2章实现基础,2数据存储基础,structDNodeElementTypeData;structDNode*Next;structDNode*Previous;*p,*t;指针操作顺序:
t-Previous=p;t-Next=p-Next;p-Next-Previous=t;p-Next=t;,13/25,例2.4给定一个单链表L,请设计函数Reverse将链表L就地逆转,即不需要申请新的结点,将链表的第一个元素转为最后一个元素,第二个元素转为倒数第二个元素,【分析】基本思路是:
利用循环,从链表头开始逐个处理。
如何把握住循环不变式。
(循环不变式表示一种在循环过程进行时不变的性质,不依赖于前面所执行过程的重复次数的断言。
)在每轮循环开始前我们都面临两个序列,其中p是一个待逆转的序列,而q是一个已经逆转好的序列,如下图。
每轮循环的目的是把p中的第一个元素插入到q的头上,使这轮循环执行好后,p和q还是分别指向新的待逆转序列和已经逆转好的序列。
第2章实现基础,2数据存储基础,p-Next=q;q=p;,t=p-Next;p-Next=q;q=p;p=t;,14/25,类型定义typedef,第2章实现基础,2数据存储基础,除了使用C语言提供的标准类型和自己定义的一些结构体、枚举等类型外,还可以用typedef语句来建立已经定义好的数据类型的别名。
typedef原有类型名新类型名,typedefstructNode*NodePtr;,这样,Reverse函数头就可以写成:
NodePtrReverse(NodePtrL),15/25,第2章实现基础,3流程控制基础,顺序结构是一种自然的控制结构,通过安排语句或模块的顺序就能实现。
C语言为分支控制提供了if-else和switch两类语句,为循环控制提供了for、while和do-while三类语句。
三种基本的控制结构是顺序、分支和循环。
函数定义函数调用函数递归,语句级控制,单位级控制,16/25,例2.5求100到200之间的所有素数。
分析可以设定两重循环:
大循环(外层循环)控制整数i在100到200之间变化(用for语句),而小循环(内层循环)则用来判别i是否是素数(用while语句)。
第2章实现基础,3流程控制基础,17/25,函数与递归,比如:
C语言提供了实数和整数的加法运算符号“+”来完成运算;但是“+”不能对复数做加法运算;可以写一个函数来实现这个功能。
第2章实现基础,3流程控制基础,【定义】函数是一个完成特定工作的独立程序模块。
只需定义一次,就可以多次调用。
函数包括库函数和自定义函数两种。
例如,scanf、printf等库函数由C语言系统提供定义,编程时只要直接调用即可。
在程序设计中,往往根据模块
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构 教学 课件 陈越