课程说明:
随着个人计算机和Internet的飞速发展,形形色色的信息处理系统已渗入到社会和生活的各个领域。但是,信息处理系统的软件和硬件这两大组成部分发展极不平衡,与飞速发展的硬件相比,软件的发展速度相对迟缓,这就需要越来越多的人掌握设计高性能软件的技术,以推动社会信息化的进程。因为无论是系统软件还是应用软件,其核心是数据结构及其算法,所以作为软件设计技术的理论基础,“数据结构”就不仅仅是计算机学科的核心课程,也应该是所有应用计算机的其他学科所必须掌握的课程。
概论:
数据(Data):数据是信息的载体。它能够被计算机识别、存储和加工处理,是计算机程序加工的"原料"。随着计算机应用领域的扩大,数据的范畴包括:整数、实数、字符串、图像和声音等。数据元素(Data Element):数据元素是数据的基本单位。数据元素也称元素、结点、顶点、记录。一个数据元素可以由若干个数据项(也可称为字段、域、属性)组成。 数据项是具有独立含义的最小标识单位。数据结构(Data Structure):数据结构指的是数据之间的相互关系,即数据的组织形式。
数据结构是计算机软件和计算机应用专业的核心课程之一,在众多的计算机系统软件和应用软件中都要用到各种数据结构。因此,仅掌握几种计算机语言是难以应付众多复杂的课题的。要想有效地使用计算机,还必须学习数据结构的有关知识。
数据的运算通过算法(Algorithm)描述,讨论算法是数据结构课程的重要内容之一。
线性表:
线性结构是最简单且最常用的数据结构。线性表是一种典型的线性结构。
顺序表是用向量实现的线性表,向量的下标可以看作结点的相对地址。因此顺序表的的特点是逻辑上相邻的结点其物理位置亦相邻。
顺序表上实现的基本运算:表的初始化、求表长、取表中第i个结点、查找值为x的结点、插入、删除等。
链式存储是最常用的存储方式之一,它不仅可用来表示线性表,而且可用来表示各种非线性的数据结构。
算法的时间复杂度是O(n)。链表上实现的插入和删除运算,无须移动结点,仅需修改指针。
循环链表是一种首尾相接的链表。
双(向)链表中有两条方向不同的链,即每个结点中除next域存放后继结点地址外,还增加一个指向其直接前趋的指针域prior。
顺序表和链表各有短长。在实际应用中究竟选用哪一种存储结构呢?这要根据具体问题的要求和性质来决定。
广义表:
广义表(Lists,又称列表)是线性表的推广。即广义表中放松对表元素的原子限制,容许它们具有其自身结构。
栈和队列:
栈和队列是两种特殊的线性表,它们的逻辑结构和线性表相同,只是其运算规则较线性表有更多的限制,故又称它们为运算受限的线性表。栈和队列被广泛应用于各种程序设计中。
栈的顺序存储结构简称为顺序栈,它是运算受限的顺序表。
栈的链式存储结构称为链栈。
队列(Queue)是只允许在一端进行插入,而在另一端进行删除的运算受限的线性表。
队列的顺序存储结构称为顺序队列,顺序队列实际上是运算受限的顺序表。为充分利用向量空间,克服"假上溢"现象的方法是:将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量。存储在其中的队列称为循环队列(Circular Queue)。
队列的链式存储结构简称为链队列。它是限制仅在表头删除和表尾插入的单链表。
栈和队列的应用非常之广,只要问题满足后进先出和先进先出原则,均可使用栈和队列作为其数据结构。
舞伴问题:假设在周末舞会上,男士们和女士们进入舞厅时,各自排成一队。跳舞开始时,依次从男队和女队的队头上各出一人配成舞伴。若两队初始人数不相同,则较长的那一队中未配对者等待下一轮舞曲。现要求写一算法模拟上述舞伴配对问题。
串:
串(又称字符串)是一种特殊的线性表,它的每个结点仅由一个字符组成。在早期的程序设计语言中,串仅在输入或输出中以直接量的形式出现,并不参与运算。随着计算机的发展,串在文字编辑、词法扫描、符号处理以及定理证明等许多领域得到越来越广泛的应用。在高级语言中开始引入了串变量的概念,如同整型、实型变量一样,串变量也可以参加各种运算。
对于串的基本运算,很多高级语言均提供了相应的运算符或标准的库函数来实现。下面以C语言中串运算介绍串的基本运算:求串长、串复制、联接、串比较、字符定位等。
因为串是特殊的线性表,故其存储结构与线性表的存储结构类似。只不过由于组成串的结点是单个字符,所以存储时有一些特殊的技巧。
用单链表方式存储串值,串的这种链式存储结构简称为链串。
串是特殊的线性表,故顺序串和链串上实现的运算分别与顺序表和单链表上进行的操作类似。C语言的串库<string.h>里提供了丰富的串函数来实现各种基本运算,因此我们对各种串运算的实现不作讨论。利用串函数实现串的基本运算部分内容。
多维数组:
多维数组和广义表是一种复杂的非线性结构,它们的逻辑特征是:一个数据元素可能有多个直接前驱和多个直接后继。
矩阵是科学与工程计算问题中常用的数学对象之一。
所谓特殊矩阵是指非零元素或零元素的分布有一定规律的矩阵。常见的有对称矩阵、三角矩阵和对角矩阵等。
设矩阵Amn中有s个非零元素,若s远远小于矩阵元素的总数(即s<<m×n),则称A为稀疏矩阵。
树:
树形结构是一类重要的非线性结构。树形结构是结点之间有分支,并具有层次关系的结构。它非常类似于自然界中的树。树结构在客观世界中是大量存在的,例如家谱、行政组织机构都可用树形象地表示。树在计算机领域中也有着广泛的应用,例如在编译程序中,用树来表示源程序的语法结构;在数据库系统中,可用树来组织信息;在分析算法的行为时,可用树来描述其执行过程。
二叉树是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树的形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。
1、二叉树第i层上的结点数目最多为2i-1(i≥1);2、深度为k的二叉树至多有2k-1个结点(k≥1);3、在任意-棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则no=n2+1;4、具有n个结点的完全二叉树的深度为:。
顺序存储结构、链式存储结构、二叉链表(二叉树的常用链式存储结构)、带双亲指针的二叉链表。
所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问题。遍历是二叉树上最重要的运算之一,是二叉树上进行其它运算之基础。
n个结点的二叉链表中含有n+1个空指针域。利用二叉链表中的空指针域,存放指向结点在某种遍历次序下的前趋和后继结点的指针(这种附加的指针称为"线索")。这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
树或森林与二叉树之间有一个自然的一一对应关系。任何一个森林或一棵树可惟一地对应到一棵二叉树;反之,任何一棵二叉树也能惟一地对应到一个森林或一棵树。
讨论树的三种常用表示法:双亲链表表示法、孩子链表表示法、孩子兄弟链表表示法。
树T的前序遍历、树的后序遍历、前序遍历森林、后序遍历森林。
在权为wl,w2,…,wn的n个叶子所构成的所有二叉树中,带权路径长度最小(即代价最小)的二叉树称为最优二叉树或哈夫曼树。
数据压缩过程称为编码。即将文件中的每个字符均转换为一个惟一的二进制位串。数据解压过程称为解码。即将二进制位串转换为对应的字符。
图:
图G由两个集合V和E组成,记为:G=(V,E)。其中:V是顶点的有穷非空集合,E是V中顶点偶对(称为边)的有穷集。通常,也将图G的顶点集和边集分别记为V(G)和E(G)。E(G)可以是空集。若E(G)为空,则图G只有顶点而没有边。
在图的邻接矩阵表示法中:用邻接矩阵表示顶点间的相邻关系,用一个顺序表来存储顶点信息。
图的邻接表表示法类似于树的孩子链表表示法。对于图G中的每个顶点vi,该方法把所有邻接于vi的顶点vj链成一个带头结点的单链表,这个单链表就称为顶点vi的邻接表(Adjacency List)。
深度优先遍历和广度优先遍历是最为重要的两种遍历图的方法。它们对无向图和有向图均适用。
广度优先遍历类似于树的按层次遍历。采用的搜索方法的特点是尽可能先对横向进行搜索,故称其为广度优先搜索(Breadth-FirstSearch)。相应的遍历也就自然地称为广度优先遍历。
如果连通图G的一个子图是一棵包含G的所有顶点的树,则该子图称为G的生成树(SpanningTree)。生成树是连通图的包含图中的所有顶点的极小连通子图。图的生成树不惟一。从不同的顶点出发进行遍历,可以得到不同的生成树。
对于连通的带权图(连通网)G,其生成树也是带权的。权最小的生成树称为G的最小生成树(Minimum SpannirngTree)。最小生成树可简记为MST。
带权图的最短路径问题即求两个顶点间长度最短的路径。其中:路径长度不是指路径上边数的总和,而是指路径上各边的权值总和。路径长度的的具体含义取决于边上权值所代表的意义。
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若<u,v> ∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(TopoiSicai Order)的序列,简称拓扑序列。
排序:
所谓排序(sort)或分类,就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来。
假设待排序的记录存放在数组R[1..n]中。初始时,R[1]自成1个有序区,无序区为R[2..n]。从i=2起直至i=n为止,依次将R[i]插入当前的有序区R[1..i-1]中,生成含n个记录的有序区。
希尔排序(Shell Sort)是插入排序的一种。因D.L.Shell于1959年提出而得名。先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插人排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
将被排序的记录数组R[1..n]垂直排列,每个记录R[i]看作是重量为R[i].key的气泡。根据轻气泡不能在重气泡之下的原则,从下往上扫描数组R:凡扫描到违反本原则的轻气泡,就使其向上"飘浮"。如此反复进行,直到最后任何两个气泡都是轻者在上,重者在下为止。
快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。
选择排序(Selection Sort)的基本思想是:每一趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的子文件的最后,直到全部记录排序完毕。
堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
归并排序(Merge Sort)是利用"归并"技术来进行排序。归并是指将若干个已排序的子文件合并成一个有序的文件。
桶排序的思想是把[0,1)划分为n个大小相同的子区间,每一子区间是一个桶。然后将n个记录分配到各个桶中。因为关键字序列是均匀分布在[0,1)上的,所以一般不会有很多个记录落入同一个桶中。由于同一桶中的记录其关键字不尽相同,所以必须采用关键字比较的排序方法(通常用插入排序)对各个桶进行排序,然后依次将各非空桶中的记录连接(收集)起来即可。
箱排序也称桶排序(Bucket Sort),其基本思想是:设置若干个箱子,依次扫描待排序的记录R[0],R[1],…,R[n-1],把关键字等于k的记录全都装入到第k个箱子里(分配),然后按序号依次将各非空的箱子首尾连接起来(收集)。
基数排序(Radix Sort)是对箱排序的改进和推广。
简单排序中直接插入最好,快速排序最快,当文件为正序时,直接插入和冒泡均最佳。
查找:
一般,假定被查找的对象是由一组结点组成的表(Table)或文件,而每个结点则由若干个数据项组成。并假设每个结点都有一个能惟一标识该结点的关键字。查找(Searching)的定义是:给定一个值K,在含有n个结点的表中找出关键字等于给定值K的结点。若找到,则查找成功,返回该结点的信息或该结点在表中的位置;否则查找失败,返回相关的指示信息。
基本思想是:从表的一端开始,顺序扫描线性表,依次将扫描到的结点关键宇和给定值K相比较。若当前扫描到的结点关键字与K相等,则查找成功;若扫描结束后,仍未找到关键字等于K的结点,则查找失败。
二分查找又称折半查找,它是一种效率较高的查找方法。二分查找要求:线性表是有序表,即表中结点按关键字有序,并且要用向量作为表的存储结构。不妨设有序表是递增有序的。
分块查找(Blocking Search)又称索引顺序查找。它是一种性能介于顺序查找和二分查找之间的查找方法。
当用线性表作为表的组织形式时,可以有三种查找法。其中以二分查找效率最高。但由于二分查找要求表中结点按关键字有序,且不能用链表作存储结构,因此,当表的插入或删除操作频繁时,为维护表的有序性,势必要移动表中很多结点。这种由移动结点引起的额外时间开销,就会抵消二分查找的优点。也就是说,二分查找只适用于静态查找表。若要对动态查找表进行高效率的查找,可采用下面介绍的几种特殊的二叉树或树作为表的组织形式。不妨将它们统称为树表。下面将分别讨论在这些树表上进行查找和修改操作的方法。
当查找的文件较大,且存放在磁盘等直接存取设备中时,为了减少查找过程中对磁盘的读写次数,提高查找效率,基于直接存取设备的读写操作以"页"为单位的特征。1972年R.Bayer和E.M.McCreight提出了一种称之为B-树的多路平衡查找树。它适合在磁盘等直接存取设备上组织动态的查找表。
设所有可能出现的关键字集合记为U(简称全集)。实际发生(即实际存储)的关键字集合记为K(|K|比|U|小得多)。散列方法是使用函数h将U映射到表T[0..m-1]的下标上(m=O(|U|))。这样以U中关键字为自变量,以h为函数的运算结果就是相应结点的存储地址。从而达到在O(1)时间内就可完成查找。
散列函数的选择有两条标准:简单和均匀。
通常有两类方法处理冲突:开放定址(Open Addressing)法和拉链(Chaining)法。前者是将所有结点均存放在散列表T[0..m-1]中;后者通常是将互为同义词的结点链成一个单链表,而将此链表的头指针放在散列表T[0..m-1]中。
散列表上的运算有查找、插入和删除。其中主要是查找,这是因为散列表的目的主要是用于快速查找,且插入和删除均要用到查找操作。
文件:
文件(File)是性质相同的记录的集合。
顺序文件是指按记录进入文件的先后顺序存放、其逻辑顺序和物理顺序一致的文件。注意:一切存储在顺序存取存储器(如磁带)上的文件,都只能是顺序文件。
索引文件由主文件和索引表构成。主文件:文件本身。索引表:在文件本身外建立的一张表,它指明逻辑记录和物理记录之间的一一对应关系。
ISAM为Indexed Sequential Access Methed(索引顺序存取方法)的缩写,它是一种专为磁盘存取文件设计的文件组织方式,采用静态索引结构。由于磁盘是以盘组、柱面和磁道三级地址存取的设备,则可对磁盘上的数据文件建立盘组、柱面和磁道多级索引,下面只讨论在同一个盘组上建立的ISAM文件。
VSAM是Virtual Storage Access Method(虚拟存储存取方法)的缩写,它也是一种索引顺序文件的组织方式,采用B+树作为动态索引结构。
散列文件是利用散列存储方式组织的文件,亦称直接存取文件。即根据文件中关键字的特点,设计一个散列函数和处理冲突的方法,将记录散列到存储设备上。
对每个需要查询的次关键字建立一个索引,同时将具有相同次关键字的记录链接成一个链表,并将此链表的头指针、链表长度及次关键字,作为索引表的一个索引项。通常多重表文件的主文件是一个顺序文件。
倒排文件和多重表文件不同。在次关键字索引中,具有相同次关键字的记录之间不进行链接,而是列出具有该次关键字记录的物理地址。倒排文件中的次关键字索引称做倒排表。倒排表和主文件一起就构成了倒排文件。
习题:
十个章节所附练习题及答案。
大纲:
课程性质与设置目的、考试内容与考核目标、有关说明与实施要求、题型举例等相关信息。
参考书目:
[1]Knuth D E.The Art of Computer Programming,1:Fundamental Algrithms;3:Sorting and Searching.Addison Wesley,1973
[2]Horowitz E,Sahni S.Fundamentals of Data Structures.Computer Science Press,1976
[3]Wirth N.Algorithms+Data Structures=Programs.Prentice Hall,1978
[4]Gotlieb C C,Gotlieb L R.Data Types and Structures.Prentice Hall,1978
[5]Tenenbaum A M,Augensetein M J.Data Structures Using PASCAL.Prentice Hall,1981
[6]Aho A V,Hopcroft J E,Ullman J D.Data Structures and Algorithms.AddisonWesley,1983
[7]Horwitz E,Sahni S.Fundamentals of Computer Algorithms.Computer Science Press,1978
[8]Kochan S G.Programming in C.Hayden Book Company,1983
[9]Thomas H.Cormen et al.Introduction to Algorithms.The MIT Press,1995
[10]Williaw Ford et al.Data Structures with C++.Prentice Hall Inc.,1996
[11]Robert Kruse et al.Data Structures & Program Design in C.2nd Ed.Prentice Hall,1997
[12]黄刘生,唐策善:《数据结构》第二版,中国科学技术大学出版社,2000年
[13]严蔚敏,吴伟民:《数据结构》,第二版,清华大学出版社,1992年
[14]许卓群等:《数据结构》,高等教育出版社,1987年
[15]仲萃豪,冯玉琳:《程序设计方法学》,北京科学技术出版社,1985年