1、西安文理学院精品课数据结构教 案计算机科学系韩利凯数据结构第一章 绪论教学目标 掌握数据结构的定义、内容、方法、描述、评价。重点、难点数据结构的研究范围,研究采用的方法,算法规则描述的工具,对算法作性能评价。教学方法 用多媒体课件( ppt )以及与生活实例相结合等方法讲授,这样便于描述相关概念及学生记笔记,加深他们的印象,使基础知识掌握地比较牢固。学习要点1. 熟悉各名词、术语的含义,掌握基本概念,特别是数据的逻辑结构和存储结构之间的关系。分清哪些是逻辑结构的性质,哪些是存储结构的性质。2. 了解抽象数据类型的定义、表示和实现方法。3理解算法五个要素的确切含义:动态有穷性(能执行结束) ;确
2、定性(对于相同的输入执行相同的路径) ;有输入;有输出;可行性(用以描述算法的操作都是足够基本的) 。 4掌握计算语句频度和估算算法时间复杂度的方法。1.1 什么是数据结构(定义)首先介绍数据结构的相关名词。1. 数据(Data)数据是描述客观事物的数值、字符以及能输入机器且能被处理的各种符号集合。 。2. 数据元素(Data Element)数据元素是组成数据的基本单位 ,是数据集合的个体,在计算机中通常作为一个整体进行考虑和处理。例如:学生登记表是数据,每一个学生的记录就是一个数据元素。3. 数据对象(Data Object)数据对象是性质相同的数据元素的集合,是数据的一个子集。 4. 数
3、据结构(DATA Structure)数据结构是指相互之间存在一种或多种特定关系的数据元素集合,是带有结构的数据元素的集合,它指的是数据元素之间的相互关系,即数据的组织形式。5. 数据类型(Data Type)数据类型是一组性质相同的值集合以及定义在这个值集合上的一组操作的总称。6. 数据抽象与抽象数据类型) 数据的抽象高级语言中提供整型、实型、字符、记录、文件、指针等多种数据类型,可以利用这些类型构造出象栈、队列、树、图等复杂的抽象数据类型。)抽象数据类型(Abstract Data Type)抽象数据类型(简称 ADT)是指基于一类逻辑关系的数据类型以及定义在这个类型之上的一组操作。抽象数
4、据类型是近年来计算机科学中提出的最重要的概念之一,它集中体现了程序设计中一些最基本的原则:分解、抽象和信息隐藏。一个抽象数据类型确定了一个模型,但将模型的实现细节隐藏起来;它定义了一组运算,但将运算的实现过程隐藏起来。用抽象数据类型的概念来指导问题求解的过程,可以用图 1.4 来表示:数学模型 抽象数据模型 数据结构非形式算法 伪语言程序 可执行程序图 1.4 求解过程数据结构是基础,抽象数据类型是中枢。1.2 数据结构的内容数据结构的内容可归纳为三个部分。逻辑结构、存储结构、运算集合。数据结构是一门主要研究怎样合理地组织数据、建立合适的数据结构、提高计算机执行程序所用的时空效率的学科。1.3
5、 算法1 算法(Algorithm)定义算法是规则的有限集合,是为解决特定问题而规定的一系列操作。2算法的特性 有限性 有限步骤之内正常结束,不能形成无穷循环。 确定性 算法中的每一个步骤必须有确定含义,无二义性得以实现。 输 入 有多个或 0 个输入 输 出 至少有一个或多个输出。 可行性 原则上能精确进行,操作可通过已实现基本运算执行有限次而完成。在算法的五大特性中,最基本的是有限性、确定性、可行性。3算法设计的要求一般应该具有以下几个基本特征:) 算法的正确性 2)可读性 3)健壮性 4)高效率和低存储量1.4 算法描述的工具1算法、语言、程序的关系先分析数据结构中算法、语言、程序的关系
6、: 算法:描述了数据对象的元素之间的关系(包括数据逻辑关系、存贮关系描述) 。 描述算法的工具:(自然语言、框图或高级程序设计语言) 程序是算法在计算机中的实现(与所用计算机及所用语言有关)2设计实现算法过程步骤: 找出与求解有关的数据元素之间的关系(建立结构关系) 确定在某一数据对象上所施加运算 考虑数据元素的存储表示 选择描述算法的语言 设计实现求解的算法,并用程序语言加以描述。3类描述算法的语言选择采用了标准 C 语言作为算法描述的工具。1.5 对算法作性能评价1性能评价对问题规模与该算法在运行时所占的空间 S 与所耗费的时间 T 给出一个数量关系的评价。2有关数量关系计算数量关系评价体
7、现在时间算法编程后在机器中所耗费时间。数量关系评价体现在空间算法编程后在机器中所占存储量。1)关于算法执行时间一个算法的执行时间大致上等于其所有语句执行时间的总和,对于语句的执行时间是指该条语句的执行次数和执行一次所需时间的乘积。2)语句频度语句频度是指该语句在一个算法中重复执行的次数。3)算法的时间复杂度:所谓算法的时间复杂度,即是算法的时间量度记做:T(n)=O(f(n)它表示随问题规模 n 的增大算法的执行时间的增长率和 f(n)的增长率相同,称作算法的渐进时间复杂度,简称时间复杂度。4)数据结构中常用的时间复杂度频率计数数据结构中常用的时间复杂度频率计数有 7 个:O(1) 常数型 O
8、(n)线性型 O(n 2)平方型 O(n 3)立方型O(2n)指数型 O(log2n)对数型 O(nlog2n)二维型5)最坏时间复杂度算法中基本操作重复执行的次数还随问题的输入数据集的不同而不同。6)算法的空间复杂度关于算法的存储空间需求,类似于算法的时间复杂度,我们采用空间复杂度作为算法所需存储空间的量度,记做:S(n)=O(f (n)1.6 关于学习数据结构1数据结构课程地位数据结构发展趋势包括两个方面:一是面向专门领域中特殊问题的数据结构的研究和发展,如图形数据结构、知识数据结构、空间数据结构,另一方面,从抽象数据类型的角度,用面向对象观点来讨论数据结构,已成为新的发展趋势。2数据结构
9、课程学习特点数据结构课程教学目标要求学生学会分析数据对象特征,掌握数据组织方法和计算机的表示方法,以便为应用所涉及数据选择适当的逻辑结构、存储结构及相应算法,初步掌握算法时间空间分析的技巧,培养良好的程序设计技能。第 2 章 线性表教学目标 线性结构是一种最基本的数据结构,熟练掌握其逻辑结构、存储结构各种运算。重点、难点链表结构,重点掌握单链表、循环链表、双向链表,初步掌握静态链表。教学方法 首先给出线性表的抽象数据类型定义,然后分别用顺序结构和链表结构实现线性表,最后给出一个应用实例。学习要点1. 了解线性表的逻辑结构特性是数据元素之间存在着线性关系,在计算机中表示这种关系的两类不同的存储结
10、构是顺序存储结构和链式存储结构。用前者表示的线性表简称为顺序表,用后者表示的线性表简称为链表。2. 熟练掌握这两类存储结构的描述方法,如一维数组中一个区域ij的上、下界和长度之间的变换公式(L=j-i+1, i=j-L+1, j=i+L-1),链表中指针 p 和结点*p 的对应关系 (结点*(p-next)是结点*p 的后继等) ,链表中的头结点、头指针和首元结点的区别及循环链表、双向链表的特点等。链表是本章的重点和难点。扎实的指针操作和内存动态分配的编程技术是学好本章的基本要求。3. 熟练掌握线性表在顺序存储结构上实现基本操作:查找、插入和删除的算法。4. 熟练掌握在各种链表结构中实现线性表
11、操作的基本方法,能在实际应用中选用适当的链表结构。5. 能够从时间和空间复杂度的角度综合比较线性表两种存储结构的不同特点及其适用场合。2.1 线性表的概念及运算2.1.1 线性表的逻辑结构线性表是 n 个类型相同的数据元素的有限序列,数据元素之间是一对一的关系,即每个数据元素最多有一个直接前驱和一个直接后继。2.1.2 线性表的抽象数据类型定义一个抽象数据类型定义了一个模型,但不涉及模型的具体实现问题。2.2 线性表的顺序存储2.2.1 线性表的顺序存储结构线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素,使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中。
12、采用顺序存储结构的线性表通常称为顺序表。2.2.2 线性表顺序存储结构上的基本运算1. 查找操作线性表有两种基本的查找运算。按序号查找 GetData(L,i): 要求查找线性表 L 中第 i 个数据元素,其结果是 L.elemi-1或L-elemi-1。按内容查找 Locate(L,e ): 要求查找线性表 L 中与给定值 e 相等的数据元素,其结果是:若在表 L 中找到与 e 相等的元素,则返回该元素在表中的序号;若找不到,则返回一个“空序号” ,如-1。2. 插入操作线性表的插入运算是指在表的第 i (1in+1)个位置,插入一个新元素 e,使长度为 n的线性表 (e1,e i-1,e
13、i,e n) 变成长度为 n+1 的线性表(e 1,,e i-1,e,e i,e n) 。3. 删除操作线性表的删除运算是指将表的第 i(1i n) 个元素删去,使长度为 n 的线性表 (e1, ,ei-1,e i,e i+1,e n),变成长度为 n-1 的线性表(e 1,,e i-1, ei+1,e n)。2.3 线性表的链式存储采用链式存储结构的线性表称为链表。2.3.1 单链表链表是用一组任意的存储单元来存放线性表的结点,在存储线性表的每个数据元素值的同时,存储指示其后继结点的地址(或位置)信息,这两部分信息组成的存储映象叫做结点(Node) ,如图 2.5 所示。图 2.5 单链表的
14、结点结构它包括两个域:数据域用来存储结点的值;指针域用来存储数据元素的直接后继的地址(或位置) 。由于链表的每个结点只有一个指针域,故将这种链表又称为单链表。应设一个头指针 H 指向第一个结点,由于表中最后一个结点没有直接后继,则指定线性表中最后一个结点的指针域为“空”(NULL)。例如:图 2.6 所示为线性表(A,B,C,D,E,F,G,H)的单链表存储结构,整个链表的存取需从头指针开始进行,依次顺着每个结点的指针域找到线性表的各个元素。图 2.6 单链表的示例图有时为了操作的方便,还可以在单链表的第一个结点之前附设一个头结点,头结点的数据域可以存储一些关于线性表的长度的附加信息,也可以什
15、么都不存;而头结点的指针域存储指向第一个结点的指针。此时带头结点单链表的头指针就不再指向表中第一个结点而是指向头结点。如果线性表为空表,则头结点的指针域为“空” ,如图 2.8 所示。 (a)带头结点的空单链表(b)带头结点的单链表图 2.8 带头结点单链表图示2.3.2 单链表上的基本运算1建立单链表动态建立单链表的常用方法有如下两种:1)头插法建表算法描述:从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头结点之后,直至读入结束标志为止。2)尾插法建表该方法是将新结点插到当前链表的表尾上。为此需增加一个尾指针 r,使之始终指向当前链
16、表的表尾。2查找1)按序号查找算法描述:设带头结点的单链表的长度为 n,要查找表中第 i 个结点,则需要从单链表的头指针 L 出发,从头结点(L-next)开始顺着链域扫描,用指针 p 指向当前扫描到的结点,初值指向头结点(pL-next) ,用 j 做记数器,累计当前扫描过的结点数(初值为 0) ,当 j = i 时,指针 p 所指的结点就是要找的第 i 个结点。2) 按值查找算法描述:按值查找是指在单链表中查找是否有结点值等于 e 的结点,若有的话,则返回首次找到的其值为 e 的结点的存储位置,否则返回 NULL。3单链表插入操作算法描述:要在带头结点的单链表 L 中第 i 个数据元素之前
17、插入一个数据元素 e,需要首先在单链表中找到第 i-1 个结点并由指针 pre 指示,然后申请一个新的结点并由指针 s 指示,其数据域的值为 e,并修改第 i-1 个结点的指针使其指向 s,然后使 s 结点的指针域指向第 i 个结点。4删除算法描述:欲在带头结点的单链表 L 中删除第 i 个结点,则首先要通过计数方式找到第 i-1个结点并使 p 指向第 i-1 个结点,而后删除第 i 个结点并释放结点空间。删除过程如图 2.12所示。2.3.3 循环链表循环链表:是一个首尾相接的链表。其特点是将单链表最后一个结点的指针域由 NULL改为指向头结点或线性表中的第一个结点,就得到了单链形式的循环链
18、表,并称为循环单链表。(a)带头结点的循环单链表的一般形式( b)带尾结点的循环单链表的一般形式2.3.4 双向链表链表中就有两条方向不同的链,我们称之为双 ( 向) 链表 (Double Linked List)。图 2.14 双链表的结点结构1、 双向链表的前插操作2、 双向链表的删除操作算法描述:欲删除双向链表中的第 i 个结点,则指针的变化情况如图所示:图 2.17 双向链表的删除操作2.4 一元多项式的表示及相加对于符号多项式的各种操作,实际上都可以利用线性表来处理。比较典型的是关于一元多项式的处理。在数学上,一个一元多项式 Pn(x)可按升幂的形式写成:Pn(x)=p0+p1x+p
19、2x2+p3x3+ +pnxn(2)通过键盘输入一组多项式的系数和指数,以输入系数 0 为结束标志,并约定建立多项式链表时,总是按指数从大到小的顺序排列。算法描述:从键盘接受输入的系数和指数;用尾插法建立一元多项式的链表。第 3 章 限定性线性表 栈和队列教学目标 栈和队列是两种限定性线性表,在编译程序、操作系统等各种软件系统中应用广泛。熟练掌握逻辑、存储结构。重点、难点要求重点掌握利用栈和队列解决实际问题的方法。教学方法用栈和队列的典型应用引出栈和队列的抽象数据类型定义、分别用顺序结构和单链表结构实现栈和队列,栈与递归的实现。学习要点1. 掌握栈和队列这两种抽象数据类型的特点,并能在应用问题
20、中正确选用它们。2. 熟练掌握栈类型的两种实现方法,即两种存储结构表示时的基本操作实现算法,特别应注意栈满和栈空的条件以及它们的描述方法。3. 熟练掌握循环队列和链队列的基本操作算法,特别注意队满和队空的描述方法。4. 理解递归算法执行过程中栈的状态变化过程。3.1 栈3.1.1 栈的定义栈又称为后进先出的线性表,简称为 LIFO 表。在日常生活中也可以见到很多“后进先出”的例子,如:手枪子弹夹中的子弹,子弹的装入与子弹弹出膛均在弹夹的最上端进行,先装入的子弹后发出,而后装入的子弹先发出。又如:铁路调度站,都是栈结构的实际应用。3.1.2 栈的表示和实现1顺序栈顺序栈是用顺序存储结构实现的栈,
21、即利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时由于栈的操作的特殊性,还必须附设一个位置指针 top(栈顶指针)来动态地指示栈顶元素在顺序栈中的位置。通常以 top=-1 表示空栈。 初始化。void InitStack(SeqStack *S)/*构造一个空栈 S*/S-top= -1; 判栈空。int IsEmpty(SeqStack *S) /*判栈 S 为空栈时返回值为真,反之为假*/return(S-top=-1?TRUE:FALSE); 判栈满。int IsFull(SeqStack *S)return(S-top = Stack_Size?TRUE:FALSE);
22、 进栈。int Push(SeqStack * S, StackElementType x)if(S-top= Stack_Size-1) return(FALSE); /*栈已满*/S-top+;S-elemS-top=x;return(TRUE); 出栈。int Pop(SeqStack * S, StackElementType *x) /* 将栈 S 的栈顶元素弹出,放到 x 所指的存储空间中 */if(S-top=-1) /*栈为空*/return(FALSE);else*x= S-elemS-top;S-top-; /* 修改栈顶指针 */return(TRUE); 取栈顶元素。i
23、nt GetTop(SeqStack *S, StackElementType *x) /* 将栈 S 的栈顶元素弹出,放到 x 所指的存储空间中,但栈顶指针保持不变 */if(S-top=-1) /*栈为空*/return(FALSE);else*x = S-elemS-top;return(TRUE); 思考题:说明读栈顶元素的算法与退栈顶元素的算法的区别,并请写出读栈顶算法。2链栈链栈即采用链表作为存储结构实现的栈。 进栈操作。int Push(LinkStack top, StackElementType x)/* 将数据元素 x 压入栈 top 中 */ LinkStackNode
24、* temp;temp=(LinkStackNode * )malloc(sizeof(LinkStackNode);if(temp=NULL) return(FALSE); /* 申请空间失败 */temp-data=x;temp-next=top-next;top-next=temp; /* 修改当前栈顶指针 */ return(TRUE); 出栈操作。int Pop(LinkStack top, StackElementType *x) /* 将栈 top 的栈顶元素弹出,放到 x 所指的存储空间中 */LinkStackNode * temp;temp=top-next;if(temp
25、=NULL) /*栈为空*/return(FALSE);top-next=temp-next;*x=temp-data;free(temp); /* 释放存储空间 */return(TRUE);思考题:将可利用空间组成链栈,常用的申请一个新结点(如 C 语言中的 mallock 函数)与归还一个无用结点(如 C 语言中的 free 函数)操作,对可利用空间的链栈来说,分别相当做什么操作?3.1.3 栈的应用举例栈应用的典型例子。1.括号匹配问题假设表达式中包含三种括号:圆括号、方括号和花括号,它们可互相嵌套,如 ( ( ) )或( ( ( ) ) )等均为正确的格式,而 ) 或 ( ) 或 (
26、 均为不正确的格式。在检验算法中可设置一个栈,每读入一个括号,若是左括号,则直接入栈,等待相匹配的同类右括号;若读入的是右括号,且与当前栈顶的左括号同类型,则二者匹配,将栈顶的左括号出栈,否则属于不合法的情况。另外,如果输入序列已读尽,而栈中仍有等待匹配的左括号,或者读入了一个右括号,而栈中已无等待匹配的左括号,均属不合法的情况。当输入序列和栈同时变为空时,说明所有括号完全匹配。2.算术表达式处理规则:一、规定优先级表二、设置两个栈:OVS(运算数栈)、OPTR( 运算符栈) 三、自左向右扫描,遇操作数进 OVS(运算数栈),操作符则与 OPTR(运算符栈)栈顶优先数比较:当前操作符OPTR
27、栈顶, 当前操作符进 OPTR 栈,前操作符OPTR 栈顶.图 3.7 A/BC+D*E 的运算过程时栈区变化情况栈区变化示意图3.2.1 队列的定义队列 (Queue) 是另一种限定性的线性表,它只允许在表的一端插入元素,而在另一端删除元素,所以队列具有先进先出 (Fist In Fist Out,缩写为 FIFO)的特性。在队列中,允许插入的一端叫做队尾(rear) ,允许删除的一端则称为队头(front)。3.2.2 队列的表示和实现1链队列用链表表示的队列简称为链队列。(1)初始化操作。int InitQueue(LinkQueue * Q) /* 将 Q 初始化为一个空的链队列 */
28、Q-front=(LinkQueueNode *)malloc(sizeof(LinkQueueNode);if(Q-front!=NULL)Q-rear=Q-front;Q-front-next=NULL;return(TRUE);else return(FALSE); /* 溢出!*/(2)入队操作。int EnterQueue(LinkQueue *Q, QueueElementType x) /* 将数据元素 x 插入到队列 Q 中 */LinkQueueNode * NewNode;NewNode=(LinkQueueNode * )malloc(sizeof(LinkQueueNo
29、de);if(NewNode!=NULL)NewNode-data=x;NewNode-next=NULL;Q-rear-next=NewNode;Q-rear=NewNode;return(TRUE);else return(FALSE); /* 溢出!*/(3)出队操作。int DeleteQueue(LinkQueue * Q, QueueElementType *x) /* 将队列 Q 的队头元素出队,并存放到 x 所指的存储空间中 */LinkQueueNode * p;if(Q-front=Q-rear)return(FALSE);p=Q-front-next;Q-front-ne
30、xt=p-next; /* 队头元素 p 出队 */if(Q-rear=p) /* 如果队中只有一个元素 p,则 p 出队后成为空队 */Q-rear=Q-front; *x=p-data;free(p); /* 释放存储空间 */return(TRUE); 2循环队列循环队列是队列的一种顺序表示和实现方法。由于只能在队尾入队,使得上述空单元无法使用。我们把这种现象称为假溢出,真正队满的条件是 rear - front=MAXSIZE 。为了解决假溢出现象并使得队列空间得到充分利用,是将顺序队列的数组看成一个环状的空间,即规定最后一个单元的后继为第一个单元,称之为循环队列。通过取模(求余)运算
31、来实现:rear=(rear+1)mod MAXSIZE,显然,当 rear+1=MAXSIZE 时,rear=0 ,同样可求得最后一个单元 QueueMAXSIZE-1的后继:Queue0 。队尾指针的变化是:rear=(rear+1)mod MAXSIZE ;而出队操作时,队头指针的变化是:front=(front+1)mod MAXSIZE。 图 3.15 给出了循环队列的几种情况。第 4 章 串教学目标字符串是最基本的非数值数据,在语言编译、信息检索、文字编辑等问题中,有广泛的应用。熟练掌握其逻辑结构、存储结构各种运算。重点、难点字符串的抽象数据类型定义,定长顺序串、堆串的存储结构和操
32、作实现。一般性了解块链串。教学方法采用应用实例任务驱动给出字符串的抽象数据类型定义,然后分别用顺序结构和链表结构实现字符串。学习要点1. 了解数组的两种存储表示方法,并掌握数组在以行为主的存储结构中的地址计算方法。2. 掌握对特殊矩阵进行压缩存储时的下标变换公式。3. 了解稀疏矩阵的两种压缩存储方法的特点和适用范围,领会以三元组表示稀疏矩阵时进行矩阵运算采用的处理方法。4. 掌握广义表的结构特点及其存储表示方法,学会对非空广义表进行分解的两种分析方法:即可将一个非空广义表分解为表头和表尾两部分或者分解为 n 个子表。4.1 串的定义 串(String)是零个或多个字符组成的有限序列。一般记为:
33、S=a1a2an (n0)其中 S 是串的名字,用单引号括起来的字符序列是串的值。n 是串中字符的个数,称为串的长度,n=0 时的串称为空串(Null String)。串中任意个连续的字符组成的子序列称为该串的子串。包含子串的串相应地称为主串。通常将字符在串中的序号称为该字符在串中的位置。当且仅当两个串的值相等时,称这两个串是相等的。由一个或多个称为空格的特殊字符组成的串,称为空格串(Blank string),其长度为串中空格字符的个数。请注意空串(Null String)和空格串(Blank string)的区别。串也是线性表的一种,因此串的逻辑结构和线性表极为相似,区别仅在于串的数据对象
34、限定为字符集。4.2 抽象数据类型串的实现常用的实现方法有:定长顺序串、堆串和块链串。4.2.1 定长顺序串定长顺序串是将串设计成一种结构类型,串的存储分配是在编译时完成的。用一组地址连续的存储单元存储串的字符序列。在进行串的插入时,插入位置 pos 将串分为两部分(假设为 A、B,长度为 LA、LB),及待插入部分(假设为 C, 长度为 LC),则串由插入前的 AB 变为 ACB,可能有三种情况:(1) 插入后串长 (LA+LC+LB)MAXLEN:则将 B 后移 LC 个元素位置,再将 C 插入。(2) 插入后串长MAXLEN 且 pos+LCMAXLEN 且 pos+LCMAXLEN:则
35、 B 的全部字符被舍弃(不需后移) ,并且 C 在插入时也有部分字符被舍弃。同上类似,在进行串的连接时(假设原来串为 A,长度为 LA, 待连接串为 B,长度为LB) ,也可能有三种情况:(1) 连接后串长MAXLEN:则直接将 B 加在 A 的后面。(2) 连接后串长MAXLEN 且 LAMAXLEN 且 LA=MAXLEN:则 B 的全部字符被舍弃(不需连接) 。置换时的情况较为复杂,假设为原串为 A、长度为 LA,被置换串为 B、长度为 LB,置换串为 C、长度为 LC,每次置换位置为 pos,则每次置换有三种可能:(1) LB=LC:将 C 复制到 A 中 pos 起共 LC 个字符处
36、。(2) LBLC:将 A 中 B 后的所有字符前移 LB-LC 个字符位置,然后将 C 复制到 A中 pos 起共 LC 个字符。(3) LBj 时,有 aij=0,则此矩阵称为上三角矩阵;若矩阵中的所有元素均满足 aij=aji,则称此矩阵为对称矩阵。下面以 nn 下三角矩阵(图 5.6)为例来讨论三角矩阵的压缩存储。对于下三角矩阵的压缩存储,只存储下三角的非零元素,对于零元素则不存。下三角矩阵中元素 aij(ij),在一维数组 A 中的位置为:Loc i ,j=Loc1,1+前 i-1 行非零元素个数+第 i 行中 aij前非零元素个数LOC i ,j= LOC1,1+ i (i -1)
37、/2+ j-15.3.2 带状矩阵所谓的带状矩阵即:在矩阵 A 中,所有的非零元素都集中在以主对角线为中心的带状区域中。其中最常见的是三对角带状矩阵。对于三对角带状矩阵的压缩存储,以行序为主序进行存储,并且只存储非零元素。具体压缩存储方法如下:一、确定存储该矩阵所需的一维向量空间的大小二、确定非零元素在一维数组空间中的位置。5.3.3 稀疏矩阵所谓的稀疏矩阵,是指矩阵中大多数元素为零的矩阵。一般地,当非零元素个数只占矩阵元素总数的 25%30%,或低于这个百分数时,我们称这样的矩阵为 稀疏矩阵。一、稀疏矩阵的三元组表表示法:把三元组按“行序为主序”用一维数组进行存放,即将 j 矩阵的任何一行的
38、全部非零元素的三元组按列号递增存放。由此得到矩阵 M,N 的三元组表.稀疏矩阵的三元组表示法虽然节约了存储空间,但比起矩阵正常的存储方式来讲,其实现相同操作要耗费较多的时间,同时也增加了算法的难度。即以耗费更多时间为代价来换取空间的节省。二、稀疏矩阵的链式存储结构-十字链表在十字链表中,同一行的非零元素通过 right 域链接成一个单链表。同一列的非零元素通过 down 域链接成一个单链表。这样,矩阵中任一非零元素 Mij 所对应的结点既处在第i 行的行链表上,又处在第 j 列的列链表上,这好像是处在一个十字交叉路口上,所以称其为十字链表。同时我们再附设一个存放所有行链表的头指针的的一维数组,
39、和一个存放所有列链表的头指针的的一维数组。广义表,顾名思义,它也是线性表的一种推广。它被广泛的应用于人工智能等领域的表处理语言 LISP 语言中。在 LISP 语言中,广义表是一种最基本的数据结构,就连 LISP 语言的程序也表示为一系列的广义表。由于广义表 GL=(d 1,d 2,d 3,d n)中的数据元素既可以是单个元素,也可以是子表,因此对于广义表,我们难以用顺序存储结构来表示它,通常我们用链式存储结构来表示。表中的每个元素可用一个结点来表示。广义表中有两类结点,一类是单个元素结点,一类是子表结点。从上节得知,任何一个非空的广义表都可以将其分解成表头和表尾两部分,反之,一对确定的表头和
40、表尾可以唯一地确定一个广义表。由此,一个表结点可由三个域构成:标志域,指向表头的指针域,指向表尾的指针域。而元素结点置需要两个域:标志域和值域。第六章 树教学目标 树是一种层次结构,在文件系统、数据库系统、编译系统等方面有重要应用。熟练掌握树与二叉树的抽象数据类型定义和实现,二叉树的遍历与线索二叉树,树、森林与二叉树的关系,哈父曼树及其应用。重点、难点二叉树、树、森林与二叉树的相互转换。教学方法提出树、二叉树和的森林问题,在教师组织和指导下,通过学生比较独立的探究和研究活动,探求问题的答案。学习要点1. 熟练掌握二叉树的结构特性,了解相应的证明方法。2. 熟悉二叉树的各种存储结构的特点及适用范
41、围。3. 遍历二叉树是二叉树各种操作的基础。实现二叉树遍历的具体算法与所采用的存储结构有关。不仅要熟练掌握各种遍历策略的递归和非递归算法,了解遍历过程中“栈”的作用和状态,而且能灵活运用遍历算法实现二叉树的其它操作。层次遍历是按另一种搜索策略进行的遍历。4. 理解二叉树线索化的实质是建立结点与其在相应序列中的前驱或后继之间的直接联系,熟练掌握二叉树的线索化过程以及在中序线索化树上找给定结点的前驱和后继的方法。二叉树的线索化过程是基于对二叉树进行遍历,而线索二叉树上的线索又为相应的遍历提供了方便。5. 熟悉树的各种存储结构及其特点,掌握树和森林与二叉树的转换方法。建立存储结构是进行其它操作的前提
42、,因此读者应掌握 1 至 2 种建立二叉树和树的存储结构的方法。6. 学会编写实现树的各种操作的算法。7. 了解最优树的特性,掌握建立最优树和哈夫曼编码的方法。6.1 树的概念与定义树是 n(n0)个结点的有限集合 T。当 n=0 时,称为空树;当 n0 时,该集合满足如下条件:其中必有一个称为根(root)的特定结点,它没有直接前驱,但有零个或多个直接后继。其余 n-1 个结点可以划分成 m(m0)个互不相交的有限集T1,T2,T3 , ,Tm,其中 Ti 又是一棵树,称为根 root 的子树。每棵子树的根结点有且仅有一个直接前驱,但有零个或多个直接后继。图 6.1 给出了一棵树的逻辑结构图
43、示,它如同一棵倒长的树。 结点:包含一个数据元素及若干指向其它结点的分支信息。 结点的度:一个结点的子树个数称为此结点的度。 叶结点:度为 0 的结点,即无后继的结点,也称为终端结点。 分支结点:度不为 0 的结点,也称为非终端结点。 孩子结点:一个结点的直接后继称为该结点的孩子结点。在图 6.1 中,B、C 是 A的孩子。 双亲结点:一个结点的直接前驱称为该结点的双亲结点。在图 6.1 中,A 是 B、C的双亲。 兄弟结点:同一双亲结点的孩子结点之间互称兄弟结点。在图 6.1 中,结点H、I、J 互为兄弟结点。 祖先结点: 一个结点的祖先结点是指从根结点到该结点的路径上的所有结点。在图6.1
44、 中,结点 K 的祖先是 A、 B、E。 子孙结点: 一个结点的直接后继和间接后继称为该结点的子孙结点。在图 6.1 中,结点 D 的子孙是 H、I、J、M。 树的度 :树中所有结点的度的最大值。 结点的层次 :从根结点开始定义,根结点的层次为 1,根的直接后继的层次为 2,依此类推。 树的高度(深度) :树中所有结点的层次的最大值。 有序树 :在树 T 中,如果各子树 Ti 之间是有先后次序的,则称为有序树。 森林: m(m0)棵互不相交的树的集合。将一棵非空树的根结点删去,树就变成一个森林;反之,给森林增加一个统一的根结点,森林就变成一棵树。6.2 二叉树6.2.1 二叉树的定义与基本操作
45、定义:满足以下两个条件的树型结构叫做二叉树(Binary Tree):(1) 每个结点的度都不大于 2;(2) 每个结点的孩子结点次序不能任意颠倒。6.2.2 二叉树的性质性质 1:在二叉树的第 i 层上至多有 2i-1个结点(i1)。性质 2:深度为 k 的二叉树至多有 2k-1 个结点(k1) 。性质 3:对任意一棵二叉树 T,若终端结点数为 n0,而其度数为 2 的结点数为 n2,则n0= n2+1满二叉树:深度为 k 且有 2k-1 个结点的二叉树。在满二叉树中,每层结点都是满的,即每层结点都具有最大结点数。完全二叉树:深度为 k,结点数为 n 的二叉树,如果其结点 1n 的位置序号分
46、别与满二叉树的结点 1n 的位置序号一一对应,则为完全二叉树。满二叉树必为完全二叉树,而完全二叉树不一定是满二叉树。性质 4:具有 n 个结点的完全二叉树的深度为logN+1性质 5: 对于具有 n 个结点的完全二叉树,如果按照从上到下和从左到右的顺序对二叉树中的所有结点从 1 开始顺序编号,则对于任意的序号为 i 的结点有:(1)如 i=1,则序号为 i 的结点是根结点,无双亲结点;如 i1,则序号为 i 的结点的双亲结点序号为/2(2)如 2in,则序号为 i 的结点无左孩子;如 2in,则序号为 i 的结点的左孩子结点的序号为 2i。(3)如 2i1n,则序号为 i 的结点无右孩子;如
47、2i1n,则序号为 i 的结点的右孩子结点的序号为 2i1。6.2.3 二叉树的存储结构二叉树的结构是非线性的,每一结点最多可有两个后继。二叉树的存储结构有两种:顺序存储结构和链式存储结构。1顺序存储结构顺序的存储结构是用一组连续的存储单元来存放二叉树的数据元素。2链式存储结构可以设计每个结点至少包括三个域:数据域、左孩子域和右孩子域:其中,LChild 域指向该结点的左孩子,Data 域记录该结点的信息,RChild 域指向该结点的右孩子域。6.3 二叉树的遍历与线索化6.3.1 二叉树的遍历二叉树的遍历是指按一定规律对二叉树中的每个结点进行访问且仅访问一次。二叉树的遍历操作是二叉树中最基本
48、的运算。 先序遍历( DLR)操作过程:若二叉树为空,则空操作,否则依次执行如下 3 个操作: 访问根结点; 按先序遍历左子树; 按先序遍历右子树。 中序遍历( LDR)操作过程:若二叉树为空,则空操作,否则依次执行如下 3 个操作: 按中序遍历左子树; 访问根结点; 按中序遍历右子树。 后序遍历( LRD)操作过程:若二叉树为空,则空操作,否则依次执行如下 3 个操作: 按后序遍历左子树; 按后序遍历右子树; 访问根结点。显然,这种遍历是一个递归过程。6.3.3 遍历算法应用1输出二叉树中的结点遍历算法将走遍二叉树中的每一个结点,故输出二叉树中的结点并无次序要求,因此可用三种遍历中的任何一种算法完成。2输出二叉树中的叶子结点输出二叉树中的叶子结点要求与输出二叉树中的结点相比,是一个有条件的输出问题,条件是在遍历过程中走到每一个结点时须进行测试,看是否满足叶结点的条件。3统计叶