c语言块链
❶ 关于数据结构的问题,用C语言描述
数据结构复习重点归纳笔记[清华严蔚敏版]
数据结构复习重点归纳[适于清华严版教材]
一、数据结构的章节结构及重点构成
数据结构学科的章节划分基本上为:概论,线性表,栈和队列,串,多维数组和广义表,树和二叉树,图,查找,内排,外排,文件,动态存储分配。
对于绝大多数的学校而言,“外排,文件,动态存储分配”三章基本上是不考的,在大多数高校的计算机本科教学过程中,这三章也是基本上不作讲授的。所以,大家在这三章上可以不必花费过多的精力,只要知道基本的概念即可。但是,对于报考名校特别是该校又有在试卷中对这三章进行过考核的历史,那么这部分朋友就要留意这三章了。
按照以上我们给出的章节以及对后三章的介绍,数据结构的章节比重大致为:
概论:内容很少,概念简单,分数大多只有几分,有的学校甚至不考。
线性表:基础章节,必考内容之一。考题多数为基本概念题,名校考题中,鲜有大型算法设计题。如果有,也是与其它章节内容相结合。
栈和队列:基础章节,容易出基本概念题,必考内容之一。而栈常与其它章节配合考查,也常与递归等概念相联系进行考查。
串 :基础章节,概念较为简单。专门针对于此章的大型算法设计题很少,较常见的是根据KMP进行算法分析。
多维数组及广义表
:基础章节,基于数组的算法题也是常见的,分数比例波动较大,是出题的“可选单元”或“侯补单元”。一般如果要出题,多数不会作为大题出。数组常与“查找,排序”等章节结合来作为大题考查。
树和二叉树
:重点难点章节,各校必考章节。各校在此章出题的不同之处在于,是否在本章中出一到两道大的算法设计题。通过对多所学校的试卷分析,绝大多数学校在本章都曾有过出大型算法设计题的历史。
图 :重点难点章节,名校尤爱考。如果作为重点来考,则多出现于分析与设计题型当中,可与树一章共同构成算法设计大题的题型设计。
查找
:重点难点章节,概念较多,联系较为紧密,容易混淆。出题时可以作为分析型题目给出,在基本概念型题目中也较为常见。算法设计型题中可以数组结合来考查,也可以与树一章结合来考查。
排序
:与查找一章类似,本章同属于重点难点章节,且概念更多,联系更为紧密,概念之间更容易混淆。在基本概念的考查中,尤爱考各种排序算法的优劣比较此类的题。算法设计大题中,如果作为出题,那么常与数组结合来考查。
二、数据结构各章节重点勾划:
第0章 概述
本章主要起到总领作用,为读者进行数据结构的学习进行了一些先期铺垫。大家主要注意以下几点:数据结构的基本概念,时间和空间复杂度的概念及度量方法,算法设计时的注意事项。本章考点不多,只要稍加注意理解即可。
第一章 线性表
作为线性结构的开篇章节,线性表一章在线性结构的学习乃至整个数据结构学科的学习中,其作用都是不可低估的。在这一章,第一次系统性地引入链式存储的概念,链式存储概念将是整个数据结构学科的重中之重,无论哪一章都涉及到了这个概念。
总体来说,线性表一章可供考查的重要考点有以下几个方面:
1.线性表的相关基本概念,如:前驱、后继、表长、空表、首元结点,头结点,头指针等概念。
2.线性表的结构特点,主要是指:除第一及最后一个元素外,每个结点都只有一个前趋和只有一个后继。
3.线性表的顺序存储方式及其在具体语言环境下的两种不同实现:表空间的静态分配和动态分配。静态链表与顺序表的相似及不同之处。
4.线性表的链式存储方式及以下几种常用链表的特点和运算:单链表、循环链表,双向链表,双向循环链表。其中,单链表的归并算法、循环链表的归并算法、双向链表及双向循环链表的插入和删除算法等都是较为常见的考查方式。此外,近年来在不少学校中还多次出现要求用递归算法实现单链表输出(可能是顺序也可能是倒序)的问题。
在链表的小题型中,经常考到一些诸如:判表空的题。在不同的链表中,其判表空的方式是不一样的,请大家注意。
5.线性表的顺序存储及链式存储情况下,其不同的优缺点比较,即其各自适用的场合。单链表中设置头指针、循环链表中设置尾指针而不设置头指针以及索引存储结构的各自好处。
第二章 栈与队列
栈与队列,是很多学习DS的同学遇到第一只拦路虎,很多人从这一章开始坐晕车,一直晕到现在。所以,理解栈与队列,是走向DS高手的一条必由之路,。
学习此章前,你可以问一下自己是不是已经知道了以下几点:
1.栈、队列的定义及其相关数据结构的概念,包括:顺序栈,链栈,共享栈,循环队列,链队等。栈与队列存取数据(请注意包括:存和取两部分)的特点。
2.递归算法。栈与递归的关系,以及借助栈将递归转向于非递归的经典算法:n!阶乘问题,fib数列问题,hanoi问题,背包问题,二叉树的递归和非递归遍历问题,图的深度遍历与栈的关系等。其中,涉及到树与图的问题,多半会在树与图的相关章节中进行考查。
3.栈的应用:数值表达式的求解,括号的配对等的原理,只作原理性了解,具体要求考查此为题目的算法设计题不多。
4.循环队列中判队空、队满条件,循环队列中入队与出队算法。
如果你已经对上面的几点了如指掌,栈与队列一章可以不看书了。注意,我说的是可以不看书,并不是可以不作题哦。
第三章 串
经历了栈一章的痛苦煎熬后,终于迎来了串一章的柳暗花明。
串,在概念上是比较少的一个章节,也是最容易自学的章节之一,但正如每个过来人所了解的,KMP算法是这一章的重要关隘,突破此关隘后,走过去又是一马平川的大好DS山河了,呵呵。
串一章需要攻破的主要堡垒有:
1.串的基本概念,串与线性表的关系(串是其元素均为字符型数据的特殊线性表),空串与空格串的区别,串相等的条件
2.串的基本操作,以及这些基本函数的使用,包括:取子串,串连接,串替换,求串长等等。运用串的基本操作去完成特定的算法是很多学校在基本操作上的考查重点。
3.顺序串与链串及块链串的区别和联系,实现方式。
4.KMP算法思想。KMP中next数组以及nextval数组的求法。明确传统模式匹配算法的不足,明确next数组需要改进之外。其中,理解算法是核心,会求数组是得分点。不用我多说,这一节内容是本章的重中之重。可能进行的考查方式是:求next和nextval数组值,根据求得的next或nextval数组值给出运用KMP算法进行匹配的匹配过程。
第四章 数组与广义表
学过程序语言的朋友,数组的概念我们已经不是第一次见到了,应该已经“一回生,二回熟”了,所以,在概念上,不会存在太大障碍。但作为考研课程来说,本章的考查重点可能与大学里的程序语言所关注的不太一样,下面会作介绍。
广义表的概念,是数据结构里第一次出现的。它是线性表或表元素的有限序列,构成该结构的每个子表或元素也是线性结构的,所以,这一章也归入线性结构中。
本章的考查重点有:
1.多维数组中某数组元素的position求解。一般是给出数组元素的首元素地址和每个元素占用的地址空间并组给出多维数组的维数,然后要求你求出该数组中的某个元素所在的位置。
2.明确按行存储和按列存储的区别和联系,并能够按照这两种不同的存储方式求解1中类型的题。
3.将特殊矩阵中的元素按相应的换算方式存入数组中。这些矩阵包括:对称矩阵,三角矩阵,具有某种特点的稀疏矩阵等。熟悉稀疏矩阵的三种不同存储方式:三元组,带辅助行向量的二元组,十字链表存储。掌握将稀疏矩阵的三元组或二元组向十字链表进行转换的算法。
4.广义表的概念,特别应该明确表头与表尾的定义。这一点,是理解整个广义表一节算法的基础。近来,在一些学校中,出现了这样一种题目类型:给出对某个广义表L若干个求了若干次的取头和取尾操作后的串值,要求求出原广义表L。大家要留意。
5.与广义表有关的递归算法。由于广义表的定义就是递归的,所以,与广义表有关的算法也常是递归形式的。比如:求表深度,复制广义表等。这种题目,可以根据不同角度广义表的表现形式运用两种不同的方式解答:一是把一个广义表看作是表头和表尾两部分,分别对表头和表尾进行操作;二是把一个广义表看作是若干个子表,分别对每个子表进行操作。
第五章 树与二叉树
从对线性结构的研究过度到对树形结构的研究,是数据结构课程学习的一次跃变,此次跃变完成的好坏,将直接关系到你到实际的考试中是否可以拿到高分,而这所有的一切,将最终影响你的专业课总分。所以,树这一章的重要性,已经不说自明了。
总体来说,树一章的知识点包括:
二叉树的概念、性质和存储结构,二叉树遍历的三种算法(递归与非递归),在三种基本遍历算法的基础上实现二叉树的其它算法,线索二叉树的概念和线索化算法以及线索化后的查找算法,最优二叉树的概念、构成和应用,树的概念和存储形式,树与森林的遍历算法及其与二叉树遍历算法的联系,树与森林和二叉树的转换。
下面我们来看考试中对以上知识的主要考查方法:
1.二叉树的概念、性质和存储结构
考查方法可有:直接考查二叉树的定义,让你说明二叉树与普通双分支树的区别;考查满二叉树和完全二叉树的性质,普通二叉树的五个性质:第i层的最多结点数,深度为k的二叉树的最多结点数,n0=n2+1的性质,n个结点的完全二叉树的深度,顺序存储二叉树时孩子结点与父结点之间的换算关系(左为:2*i,右为:2*i+1)。
二叉树的顺序存储和二叉链表存储的各自优缺点及适用场合,二叉树的三叉链表表示方法。
2.二叉树的三种遍历算法
这一知识点掌握的好坏,将直接关系到树一章的算法能否理解,进而关系到树一章的算法设计题能否顺利完成。二叉树的遍历算法有三种:先序,中序和后序。其划分的依据是视其每个算法中对根结点数据的访问顺序而定。不仅要熟练掌握三种遍历的递归算法,理解其执行的实际步骤,并且应该熟练掌握三种遍历的非递归算法。由于二叉树一章的很多算法,可以直接根据三种递归算法改造而来(比如:求叶子个数),所以,掌握了三种遍历的非递归算法后,对付诸如:“利用非递归算法求二叉树叶子个数”这样的题目就下笔如有神了。我会在另一篇系列文章()里给出三种遍历的递归和非递归算法的背记版,到时请大家一定熟记。
3.可在三种遍历算法的基础上改造完成的其它二叉树算法:
求叶子个数,求二叉树结点总数,求度为1或度为2的结点总数,复制二叉树,建立二叉树,交换左右子树,查找值为n的某个指定结点,删除值为n的某个指定结点,诸如此类等等等等。如果你可以熟练掌握二叉树的递归和非递归遍历算法,那么解决以上问题就是小菜一碟了。
4.线索二叉树:
线索二叉树的引出,是为避免如二叉树遍历时的递归求解。众所周知,递归虽然形式上比较好理解,但是消耗了大量的内存资源,如果递归层次一多,势必带来资源耗尽的危险,为了避免此类情况,线索二叉树便堂而皇之地出现了。对于线索二叉树,应该掌握:线索化的实质,三种线索化的算法,线索化后二叉树的遍历算法,基本线索二叉树的其它算法问题(如:查找某一类线索二叉树中指定结点的前驱或后继结点就是一类常考题)。
5.最优二叉树(哈夫曼树):
最优二叉树是为了解决特定问题引出的特殊二叉树结构,它的前提是给二叉树的每条边赋予了权值,这样形成的二叉树按权相加之和是最小的。最优二叉树一节,直接考查算法源码的很少,一般是给你一组数据,要求你建立基于这组数据的最优二叉树,并求出其最小权值之和,此类题目不难,属送分题。
6.树与森林:
二叉树是一种特殊的树,这种特殊不仅仅在于其分支最多为2以及其它特征,一个最重要的特殊之处是在于:二叉树是有序的!即:二叉树的左右孩子是不可交换的,如果交换了就成了另外一棵二叉树,这样交换之后的二叉树与原二叉树我们认为是不相同的两棵二叉树。但是,对于普通的双分支树而言,不具有这种性质。
树与森林的遍历,不像二叉树那样丰富,他们只有两种遍历算法:先根与后根(对于森林而言称作:先序与后序遍历)。在难度比较大的考试中,也有基于此二种算法的基础上再进行扩展要求你利用这两种算法设计其它算法的,但一般院校很少有这种考法,最多只是要求你根据先根或后根写出他们的遍历序列。此二者的先根与后根遍历与二叉树中的遍历算法是有对应关系的:先根遍历对应二叉树的先序遍历,而后根遍历对应二叉树的中序遍历。这一点成为很多学校的考点,考查的方式不一而足,有的直接考此句话,有的是先让你求解遍历序列然后回答这个问题。二叉树、树与森林之所以能有以上的对应关系,全拜二叉链表所赐。二叉树使用二叉链表分别存放他的左右孩子,树利用二叉链表存储孩子及兄弟(称孩子兄弟链表),而森林也是利用二叉链表存储孩子及兄弟。
树一章,处处是重点,道道是考题,大家务必个个过关。
第六章 图
如果说,从线性结构向树形结构研究的转变,是数据结构学科对数据组织形式研究的一次升华,那么从树形结构的研究转到图形结构的研究,则进一步让我们看到了数据结构对于解决实际问题的重大推动作用。
图这一章的特点是:概念繁多,与离散数学中图的概念联系紧密,算法复杂,极易被考到,且容易出大题,尤其是名校,作为考研课程,如果不考查树与图两章的知识,几乎是不可想象的。
下面我们看一下图这一章的主要考点以及这些考点的考查方式:
1.考查有关图的基本概念问题:
这些概念是进行图一章学习的基础,这一章的概念包括:图的定义和特点,无向图,有向图,入度,出度,完全图,生成子图,路径长度,回路,(强)连通图,(强)连通分量等概念。与这些概念相联系的相关计算题也应该掌握。
2.考查图的几种存储形式:
图的存储形式包括:邻接矩阵,(逆)邻接表,十字链表及邻接多重表。在考查时,有的学校是给出一种存储形式,要求考生用算法或手写出与给定的结构相对应的该图的另一种存储形式。
3.考查图的两种遍历算法:深度遍历和广度遍历
深度遍历和广度遍历是图的两种基本的遍历算法,这两个算法对图一章的重要性等同于“先序、中序、后序遍历”对于二叉树一章的重要性。在考查时,图一章的算法设计题常常是基于这两种基本的遍历算法而设计的,比如:“求最长的最短路径问题”和“判断两顶点间是否存在长为K的简单路径问题”,就分别用到了广度遍历和深度遍历算法。
4.生成树、最小生成树的概念以及最小生成树的构造:PRIM算法和KRUSKAL算法。
考查时,一般不要求写出算法源码,而是要求根据这两种最小生成树的算法思想写出其构造过程及最终生成的最小生成树。
5.拓扑排序问题:
拓扑排序有两种方法,一是无前趋的顶点优先算法,二是无后继的顶点优先算法。换句话说,一种是“从前向后”的排序,一种是“从后向前”排。当然,后一种排序出来的结果是“逆拓扑有序”的。
6.关键路径问题:
这个问题是图一章的难点问题。理解关键路径的关键有三个方面:一是何谓关键路径,二是最早时间是什么意思、如何求,三是最晚时间是什么意思、如何求。简单地说,最早时间是通过“从前向后”的方法求的,而最晚时间是通过“从后向前”的方法求解的,并且,要想求最晚时间必须是在所有的最早时间都已经求出来之后才能进行。这个问题拿来直接考算法源码的不多,一般是要求按照书上的算法描述求解的过程和步骤。
在实际设计关键路径的算法时,还应该注意以下这一点:采用邻接表的存储结构,求最早时间和最晚时间要采用不同的处理方法,即:在算法初始时,应该首先将所有顶点的最早时间全部置为0。关键路径问题是工程进度控制的重要方法,具有很强的实用性。
7.最短路径问题:
与关键路径问题并称为图一章的两只拦路虎。概念理解是比较容易的,关键是算法的理解。最短路径问题分为两种:一是求从某一点出发到其余各点的最短路径;二是求图中每一对顶点之间的最短路径。这个问题也具有非常实用的背景特色,一个典型的应该就是旅游景点及旅游路线的选择问题。解决第一个问题用DIJSKTRA算法,解决第二个问题用FLOYD算法。注意区分。
第七章 查找
在不少数据结构的教材中,是把查找与排序放入高级数据结构中的。应该说,查找和排序两章是前面我们所学的知识的综合运用,用到了树、也用到了链表等知识,对这些数据结构某一方面的运用就构成了查找和排序。
现实生活中,search几乎无处不在,特别是现在的网络时代,万事离不开search,小到文档内文字的搜索,大到INTERNET上的搜索,search占据了我们上网的大部分时间。
在复习这一章的知识时,你需要先弄清楚以下几个概念:
关键字、主关键字、次关键字的含义;静态查找与动态查找的含义及区别;平均查找长度ASL的概念及在各种查找算法中的计算方法和计算结果,特别是一些典型结构的ASL值,应该记住。
在DS的教材中,一般将search分为三类:1st,在顺序表上的查找;2nd,在树表上的查找;3rd,在哈希表上的查找。下面详细介绍其考查知识点及考查方式:
1.线性表上的查找:
主要分为三种线性结构:顺序表,有序顺序表,索引顺序表。对于第一种,我们采用传统查找方法,逐个比较。对于及有序顺序表我们采用二分查找法。对于第三种索引结构,我们采用索引查找算法。考生需要注意这三种表下的ASL值以及三种算法的实现。其中,二分查找还要特别注意适用条件以及其递归实现方法。
2.树表上的查找:
这是本章的重点和难点。由于这一节介绍的内容是使用树表进行的查找,所以很容易与树一间的某些概念相混淆。本节内容与树一章的内容有联系,但也有很多不同,应注意规纳。树表主要分为以下几种:二叉排序树,平衡二叉树,B树,键树。其中,尤以前两种结构为重,也有部分名校偏爱考B树的。由于二叉排序树与平衡二叉树是一种特殊的二叉树,所以与二叉树的联系就更为紧密,二叉树一章学好了,这里也就不难了。
二叉排序树,简言之,就是“左小右大”,它的中序遍历结果是一个递增的有序序列。平衡二叉树是二叉排序树的优化,其本质也是一种二叉排序树,只不过,平衡二叉树对左右子树的深度有了限定:深度之差的绝对值不得大于1。对于二叉排序树,“判断某棵二叉树是否二叉排序树”这一算法经常被考到,可用递归,也可以用非递归。平衡二叉树的建立也是一个常考点,但该知识点归根结底还是关注的平衡二叉树的四种调整算法,所以应该掌握平衡二叉树的四种调整算法,调整的一个参照是:调整前后的中序遍历结果相同。
B树是二叉排序树的进一步改进,也可以把B树理解为三叉、四叉....排序树。除B树的查找算法外,应该特别注意一下B树的插入和删除算法。因为这两种算法涉及到B树结点的分裂和合并,是一个难点。B树是报考名校的同学应该关注的焦点之一。
键树也称字符树,特别适用于查找英文单词的场合。一般不要求能完整描述算法源码,多是根据算法思想建立键树及描述其大致查找过程。
3.基本哈希表的查找算法:
哈希一词,是外来词,译自“hash”一词,意为:散列或杂凑的意思。哈希表查找的基本思想是:根据当前待查找数据的特征,以记录关键字为自变量,设计一个function,该函数对关键字进行转换后,其解释结果为待查的地址。基于哈希表的考查点有:哈希函数的设计,冲突解决方法的选择及冲突处理过程的描述。
第八章 内部排序
内排是DS课程中最后一个重要的章节,建立在此章之上的考题可以有多种类型:填空,选择,判断乃至大型算法题。但是,归结到一点,就是考查你对书本上的各种排序算法及其思想以及其优缺点和性能指标(时间复杂度)能否了如指掌。
这一章,我们对重点的规纳将跟以上各章不同。我们将从以下几个侧面来对排序一章进行不同的规纳,以期能更全面的理解排序一章的总体结构及各种算法。
从排序算法的种类来分,本章主要阐述了以下几种排序方法:插入、选择、交换、归并、计数等五种排序方法。
其中,在插入排序中又可分为:直接插入、折半插入、2路插入、希尔排序。这几种插入排序算法的最根本的不同点,说到底就是根据什么规则寻找新元素的插入点。直接插入是依次寻找,折半插入是折半寻找。希尔排序,是通过控制每次参与排序的数的总范围“由小到大”的增量来实现排序效率提高的目的。
交换排序,又称冒泡排序,在交换排序的基础上改进又可以得到快速排序。快速排序的思想,一语以敝之:用中间数将待排数据组一分为二。快速排序,在处理的“问题规模”这个概念上,与希尔有点相反,快速排序,是先处理一个较大规模,然后逐渐把处理的规模降低,最终达到排序的目的。
选择排序,相对于前面几种排序算法来说,难度大一点。具体来说,它可以分为:简单选择、树选择、堆排。这三种方法的不同点是,根据什么规则选取最小的数。简单选择,是通过简单的数组遍历方案确定最小数;树选择,是通过“锦标赛”类似的思想,让两数相比,不断淘汰较大(小)者,最终选出最小(大)数;而堆排序,是利用堆这种数据结构的性质,通过堆元素的删除、调整等一系列操作将最小数选出放在堆顶。堆排序中的堆建立、堆调整是重要考点。树选择排序,也曾经在一些学校中的大型算法题中出现,请大家注意。
归并排序,故名思义,是通过“归并”这种操作完成排序的目的,既然是归并就必须是两者以上的数据集合才可能实现归并。所以,在归并排序中,关注最多的就是2路归并。算法思想比较简单,有一点,要铭记在心:归并排序是稳定排序。
基数排序,是一种很特别的排序方法,也正是由于它的特殊,所以,基数排序就比较适合于一些特别的场合,比如扑克牌排序问题等。基数排序,又分为两种:多关键字的排序(扑克牌排序),链式排序(整数排序)。基数排序的核心思想也是利用“基数空间”这个概念将问题规模规范、变小,并且,在排序的过程中,只要按照基排的思想,是不用进行关键字比较的,这样得出的最终序列就是一个有序序列。
本章各种排序算法的思想以及伪代码实现,及其时间复杂度都是必须掌握的,学习时要多注意规纳、总结、对比。此外,对于教材中的10.7节,要求必须熟记,在理解的基础上记忆,这一节几乎成为很多学校每年的必考点。
至此,数据结构所有章节的章节重点问题,我们已经规纳完毕,使用清华严版教材的同学,在复习的同时,可以参照本贴给出的重点进行复习。但是,由于作者本人水平有限,可能有很多考点没有规纳出来,也可能有些考点规纳有误,在此,作者本人诚恳希望诸位朋友直面提出,我会不断完善和发布新的关于数据结构复习的总结以及笔记
严蔚敏数据结构为主的笔记二
第二章:线性表(包括习题与答案及要点)
--------------------------------------------------------------------------------
本章的重点是掌握顺序表和单链表上实现的各种基本算法及相关的时间性能分析,难点是使用本章所学的基本知识设计有效算法解决与线性表相关的应用问题。
要求达到<识记>层次的内容有:线性表的逻辑结构特征;线性表上定义的基本运算,并利用基本运算构造出较复杂的运算。
要求达到<综合应用>层次的内容有:顺序表的含义及特点,顺序表上的插入、删除操作及其平均时间性能分析,解决简单应用问题。
链表如何表示线性表中元素之间的逻辑关系;单链表、双链表、循环链表链接方式上的区别;单链表上实现的建表、查找、插入和删除等基本算法及其时间复杂度。循环链表上尾指针取代头指针的作用,以及单循环链表上的算法与单链表上相应算法的异同点。双链表的定义和相关算法。利用链表设计算法解决简单应用问题。
要求达到<领会>层次的内容就是顺序表和链表的比较,以及如何选择其一作为其存储结构才能取得较优的时空性能。
--------------------------------------------------------------------------------
线性表的逻辑结构特征是很容易理解的,如其名,它的逻辑结构特征就好象是一条线,上面打了一个个结,很形象的,如果这条线上面有结,那么它就是非空表,只能有一个开始结点,有且只能有一个终端结点,其它的结前后所相邻的也只能是一个结点(直接前趋和直接后继)。
关于线性表上定义的基本运算,主要有构造空表、求表长、取结点、查找、插入、删除等。
--------------------------------------------------------------------------------
线性表的逻辑结构和存储结构之间的关系。在计算机中,如何把线性表的结点存放到存储单元中,就有许多方法,最简单的方法就是按顺序存储。就是按线性表的逻辑结构次序依次存放在一组地址连续的存储单元中。在存储单元中的各元素的物理位置和逻辑结构中各结点相邻关系是一致的。
在顺序表中实现的基本运算主要讨论了插入和删除两种运算。相关的算法我们通过练习掌握。对于顺序表的插入和删除运算,其平均时间复杂度均为O(n)。
--------------------------------------------------------------------------------
线性表的链式存储结构。它与顺序表不同,链表是用一组任意的存储单元来存放线性表的结点,这组存储单元可以分布在内存中任何位置上。因此,链表中结点的逻辑次序和物理次序不一定相同。所以为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还存储了其后继结点的地址信息(即
❷ 可以不学数据结构直接学哈希表吗C语言实现
可以的,哈希表那部分和图,树联系不是很大。直接看是完全可以的,而且哈希这部分也比较容易些。
❸ 数据结构的一些小问题
5.4 文件系统的实现
5.4.1 存储空间的分配与回收
在计算机系统中,存储空间是一种宝贵的资源。外存储设备中的空间容量虽然比较大,但也不是无限的,故对文件删除之后而不再使用的空间,必须加以回收,然后在建立文件等操作中重新利用。
对于制度的存储设备,无所谓回收,也无所谓动态分配,这种存储设备在物理上就是不可重用的。
为了进行存储空间的分配与回收,在外存储设备上设置有空闲空间登记表,该表动态跟踪该外存储设备上所有还没有分配给任何文件的空闲块的数目和块号。
该空闲空间登记表虽然称为表,但不一定以一个二维表格的形式实现。从方便、高效和安全的角度考虑,通常把空闲空间登记表放在存储介质上。
对空闲空间表的访问与修改工作是经常发生的。在进行文件删除、文件建立、写文件等操作中都会访问和修改空闲空间表。
在设计空闲空间登记表的数据结构时,一般有四种不同的方案可以考虑,下面分别介绍。
1、 位示图
位示图的基本思想是,利用一串二进制位(BIT)的值来反映磁盘空间的分配使用情况。在位示图中,没一个磁盘中物理块用一个二进制位对应,如果某个物理块为空闲,则相应的二进制位为0;如果该物理块已分配了,则相应的二进位为,如图5-15所示。
位示图对空间分配情况的描述能力强。一个二进位就描述一个物理块的状态。另外,位示图占用空间较小,因此可以复制到内存,使查找既方便又快速。位示图适用于各种文件物理结构的文件系统。使用位示图能够简单有效地在盘上找到N个连续的空闲块。
2、 空闲块表
空闲块表是专门为空闲块建立的一张表,该表记录外存储器全部空闲的物理块:包括每个空闲块的第一个空闲物理块号和该空闲块只能感空闲物理块的个数。如图5-16所示。空闲块表方式特别适合于文件物理结构为顺序结构的文件系统。
在建立新文件时,应该寻找一组连续的空闲物理块,其空闲块个数恰好等于所申请值,或接近于所申请值。系统首先查找空闲块表,主要查看该空闲块表项中是否有符合上述申请值的对应表项,如果有,就将该表项从空闲块表中删去,并且把所对应的一组连续的空闲物理块分配给申请者。
当删除文件时,系统收回它所占用的物理块,并且考虑所收回的物理块是否可以与原有空闲块相邻接,以便合并成更大的空闲区域,最后修改有关空闲块表项。
3、 空闲块链表
将外存储器中所有的空闲物理块连成一个链表,用一个空闲块首指针指向第一个空闲块,随后的每个空闲块中都含有指向下一个空闲块的指针,最后一块的指针为空,表示链尾,这样就构成了一个空闲块链表,如图5-17所示。
在图45-17中,一个空闲块链表的首指针维持一个指向物理块12的指针,该块是第一个空闲物理块。物理块12包含一个指向物理块13的指针,物理块13指向物理块14,如此等等。
空闲块链表模式效率低。要遍历整张空闲块链表,必须读每一个物理块,这就需要大量的I/O时间。
在空闲块链表模式中对空间的申请和释放是以块为单位的。申请空间时从链首取空闲块。空间释放时将物理块接入链尾。
空闲块链表法节省内存,但申请空间和回收空间的速度较慢,实现效率较低。
4、 成组链接
对链接表的一个改进方案是将N个空闲盘块的地址存放在第一个空闲块中,如图5-18所示。期于N-1个空闲盘块是实际空闲的。假设每100个空闲块为一组。第一组的100个空闲块块号放在第二组的头一块中,而第二组的其余99块是完全空闲的。第二组的100个块号又放在第三组的头一块中,依次类推,组与组之间形成链接关系。在最后一组的块号中第2个单元填“0”,表示该块中指出的块号是最后一组的块号,空闲块链到此结束。在这个空闲块链中,不组100块的一个组的块号,通常放在内存的一个专用块中。这种方式称为成组链接。
系统在初始化时先把专用块内容读到内存中,当需分配空闲块时,就直接在内存中找到哪些块是空闲的,每分配一块后把空闲块数减1。但在把一组中的第一个空闲块分配出去之前,应把登记在该块中的下一组的块号及块数保存到专用块中(此时原专用块中的信息已经无用,因为它指出的一组空闲块都已被分配了)。当一组空闲块被分配完后,则再把专用块的内容读到内存中,指出另一组可供分配的空闲块。
假设初始化时系统已把专用块读入主存储器L单元开始的区域中,分配和回收的算法如下:
⑴分配一个空闲块
查L单元内容(空闲块数):
当空闲块数>1,I:=L+空闲块数;
从I单元得到一空闲块号;
把该块分配给申请者;
空闲块数减1。
当空闲块数=1,取出L+1单元内容(一组的第一块块号或0);
取值 =0,无空闲块,申请者等待;
≠0,把该块内容复制到专用块;
把专用块内容独到主存L开始的区域。
⑵归还一块
查L单元的空闲块数;
当空闲块数〈100,空闲块数加1;
J:=L+空闲块数;
规划块号填入J单元。
当空闲块数=100,把主存中登记的信息写入归还块中;
把归还块号填入L+1单元;
将L单元置成1。
采用成组链接后,分配回收空闲块时均在内存中查找和修改,只有在一组空闲块分配完或空闲的磁盘块构成一组时才需要启动磁盘读写。因此,成组链接的管理方式比普通的链接方式效率高。
成组链接这种方案能够迅速找到大量空闲盘块地址。有些版本的UNIX操作系统便采用了这种方案。
5.4.2 实现文件系统的表目
当用户申请打开一个文件时,系统要在内存中为该用户保存一些必要的信息,这些信息以表格栏目中内容的形式出现,被称为表目。在实现文件系统时所需要的表目有若干种,其中在内存中所需的重要表目有如下一些:
1、 系统打开文件表
系统打开文件表,专门用于保存已打开文件的文件控制块。该系统打开文件表放在内存。除了保存已打开文件的文件控制块之外,在该表格中还保存有已打开的文件号、共享计数、修改标志等等,图5-19。
2、 用户打开文件表
在每个进程中,都有一个“用户打开文件表”。该表的内容有文件描述符、打开方式、读写指针、系统打开文件表入口等,图5-20。另外在进程的进程控制块PCB中,还记录了“用户打开文件表”的位置。
3、 系统打开文件表与用户打开文件表之间的关系
用户打开文件表指向了系统打开文件表。如果多个进程共享同一个文件,则一定有多个用户打开文件表目对应着系统打开文件表的同一入口,图5-21。
5.4.3 记录的成组与分解
用户的文件毫无疑问是由用户按名自己的需要组织的。用户还可按信息的内在逻辑关系,把文件划分成若干个逻辑记录。显然,逻辑记录的大小是由文件性质决定的。
另外,存储介质上的物理分块与存储介质的特性有关,尤其是磁盘。磁盘上的块的大小是在磁盘初始化时预先划好的。因此,逻辑记录的大小往往与存储介质物理分块的大小不一直。
当用户文件的逻辑记录比存储介质的物理分块小得多时,把一个逻辑记录存入一个物理块中,就会造成存储空间的浪费。为此,可把多个逻辑记录存放在一个物理块中,当用户需要某个逻辑记录时再从一物理块信息中将其分解出来。
1、 记录的成组
把若干个逻辑记录合成一组存放一物理块的工作称“记录的成组”,每块中的逻辑记录个数称“块因子”。
记录的成组在不同存储介质上进行信息转储是很有用的。
由于信息交换以块为单位,所以,要进行成组操作时必须使用内存的缓冲区。该缓冲区的长度等于要进行成组的最大逻辑记录长度乘以成组的块因子。成组转储操作如图5-22所示。
在进行记录成组时,还应考虑逻辑记录的格式。这是因为在记录式文件中,有“定长记录格式”和“不定长记录格式”。对定长记录格式的文件按记录成组的方式存储到存储介质上,则除最后一块外,每块中存放的逻辑记录个数是相同的。故只要在文件目录中说明逻辑记录的长度和块因子,在需要使用某个记录时就能方便地将其找出。
如果是一个不定长记录格式的文件,各个逻辑记录的长度可能不相等,在进行记录成组操作时,就应在每个逻辑记录前附加说明该记录长度的控制信息。
2、 记录的分解
对应签署记录成组的操作,有必要考虑从一组逻辑记录中把一个逻辑记录分离出来的操作,这种操作称为“记录的分解”。
显然,从事记录的分解操作也要使用内存缓冲区。
当用户请求读一个文件中的某个记录时,文件系统首先找出该记录所在物理块的位置,然后把含有该记录的物理块全部信息读入内存缓冲区,由于读入内存缓冲区的物理块信息中含有多个逻辑记录,所以要再从内存缓冲区中分解出指定的记录,然后传送到用户工作区。
对定长记录格式,只要的逻辑记录的长度就可容易地进行分解。对不定长记录格式,要根据说明逻辑记录长度的控制信息,计算出用户所指定的记录在内存缓冲区中的位置,然后把记录分解出来。图5-23是记录的分解操作示例。
在图5-23中,用户要求读出逻辑记录K4。用户文件中的记录是成组存放在磁盘上的,系统找出含有记录K4的物理块,从中读出了6个逻辑记录K1,K2,K3,K4,K5和K6,并且知道这些逻辑记录的长度为80,块因子为6。该块信息被读入内存缓冲区后,根据逻辑记录的长度和块因子胃,立即就能取出其中的逻辑记录K4,并把K4传送到用户工作区。
从上面的讨论可以看到,为了提高存储空间的利用率和减少启动设备的次数,采用了记录的成组和分解技术。但是上述效果的获得也付出了代价,主要包括:需要设立内存缓冲区,另外操作系统增加了成组和分解的操作的功能。
5.4.4 文件的操作
文件系统是提供给用户使用的,用户可以进行按名存取所需要的文件。在文件系统的实现中,为用户提供使用文件的手段是文件系统的重要任务之一。
1、 建立文件
用户首先调用文件系统的“建立文件”操作,在请求调用该操作时,提供所要创建的文件的文件名及若干参数:用户名、文件名、存取方式、存储设备类型、记录格式、记录长度,等等。
系统依据用户提供的文件名及若干参数,为这一新创建的文件分配一个文件控制块,填写文件控制块中的有关项。
建立文件的实质是建立文件的文件控制块FCB,并建立必要的存储空间,分配空的FCB。从而建立起系统与文件的联系。
建立文件系统调用的一般格式为:CREATE(文件名,访问权限,(最大长度))。
建立文件的具体步骤如下:
⑴检查参数的合法性:
文件名是否符合命名规则,若是,则进行下一步⑵;否则报错,返回。
⑵检查同一目录下有无重名文件:
若没有,则进行下一步⑶;否则报错,返回。
⑶在目录中有无空闲位置;
若有,则进行下一步⑷;否则,不成功返回。
⑷填写目录项内容:
包括:文件名、用户名、存取权限、长度置零,首地址等等。
⑸返回
2、 打开文件
打开文件,是使用文件的第一步,任何一个文件使用前都要先打开,即把文件控制块FCB送到内存。
打开文件系统调用的一般格式为:FD=OPEN(文件路径名,打开方式)。
打开文件时,系统主要完成以下工作:
⑴根据文件路径名查目录,找到FCB主部。
⑵根据打开方式、共享说明和用户身份检查访问合法性。
⑶根据文件号查系统打开文件表,看文件是否已被打开。
如果是,共享计数加1;
否则,将外存中的CB主部等信息填入系统打开文件表空表项,共享计数置为1。
⑷在用户打开文件表中取一空表项,填写打开方式等,并指向系统打开文件表对应表项。
返回信息:FD:文件描述符,是一个非负整数,用于以后读写文件。
3、 读文件
打开文件后,就可以读取文件中的信息。
读文件系统调用的一般格式为:READ(文件名,(文件内位置),要读的长度,内存目的地址)。
隐含参数:文件主。
读写方式可为读、写合既读又写等。
读文件时,系统主要完成以下工作:
⑴检查长度是否为正整数;
若是,则进行下一步;否则,转向(10)。
⑵根据文件名查找目录,确定该文件在目录中的位置。
⑶根据隐含参数中的文件主和目录中该文件的存储权限数据,检查是否有权读:
若是,则进行下一步,否则,转向(10)。
⑷由文件内位置与要读的长度计算最末位置,将其与目录中的文件长度比较,超过否?
若是,则转向(10);否则,进行下一步(5)。也可将参数中的长度修正为目录中的文件长度。
⑸根据参数中的位置、长度和目录中的映射信息,确定物理块号、需要读出的块数等读盘参数。
⑹根据下一块号读块至内存缓冲区。
⑺取出要读的内容,也许要进行成组的分解,将取出的内容送至参数中的内存目的地址。
⑻根据块内长度或起始块号+块数,确定还读下一块吗?同时确定下一块号:
若是,则转向(5);否则,进行下一步(9)。
⑼正常返回。
⑽错误返回,返回响应错误号。
4、 写文件
写文件系统调用的一般格式为:WRITE文件名,记录键,内存位置)。
把内存中指定单元的数据作为指定的一个记录写入指定文件中,系统还将为其分配物理块,以便把记录信息写到外存上。
5、 关闭文件
若文件暂时不用每则应将它关闭。文件关闭后一般不能存取,若要存取,则必须再次打开
6、 删除文件
删除文件文件系统调用的一般格式为:DELETE(文件名)。
7、 指针定位
指针定位的一般格式为:SEEK(FD,新指针的位置)。
指针定位时,系统主要完成以下工作:
⑴由FD检查用户打开文件表,找到对应的入口;
⑵将用户打开文件表中文件读写指针位置设为新指针的位置,供后续读写命令存取该指针处文件内容。
希望对你有帮助
❹ 算法与数据结构试题 急用!!!
这是我写的顺序查找和二分查找代码
#include<iostream.h>
#define elemtype int
int sqsearch(elemtype a[],int n,elemtype x); //顺序查找
int sqsearch2(elemtype a[],int n,elemtype x); //顺序查找,打印查找过程
int binsearch(elemtype a[],int n,elemtype x); //折半查找
int binsearch2(elemtype a[],int n,elemtype x); //折半查找,打印查找过程
void printarray(elemtype a[],int n); //打印数组数据
int main()
{
int i,x;
const int n=9;
elemtype a1[10]={0,34,23,12,56,90,78,89,45,67};
elemtype a2[10]={0,12,23,34,45,56,67,78,89,90};
//顺序查找
cout<<"顺序查找:"<<endl;
cout<<"a1[]=";
printarray(a1,n);
cout<<"输入要查找的数据:";
cin>>x;
if((i=sqsearch(a1,n,x))>0) //找到
cout<<"找到x==a1["<<i<<"]"<<endl;
else //未找到
cout<<"找不到"<<x<<endl;
cout<<endl<<"查找过程:"<<endl;
sqsearch2(a1,n,x); //查找过程
cout<<"完成顺序查找!"<<endl;
//二分法查找
cout<<"二分法查找:"<<endl;
cout<<"a2[]=";
printarray(a2,n);
cout<<"输入要查找的数据:";
cin>>x;
if((i=binsearch(a2,n,x))>0) //找到
cout<<"找到x==a1["<<i<<"]"<<endl;
else //未找到
cout<<"找不到"<<x<<endl;
cout<<endl<<"查找过程:"<<endl;
binsearch2(a2,n,x);
cout<<"完成顺序查找!"<<endl;
return 0;
}
//在数组a[1.2...n]中顺序查找x
//找到时返回元素下标,否则返回0
int sqsearch(elemtype a[],int n,elemtype x) //a[]是数组,n是元素个数,x是要查找的数
{
int i;
if(a[0]==x)
return 1;
else
{
a[0]=x;
for(i=n;!(a[i]==x);--i); //若找到则i大于0
return i;
}
}
//在数组a[1.2...n]中顺序查找x,打印每次比较结果
//找到时返回元素下标,否则返回0
int sqsearch2(elemtype a[],int n,elemtype x) //a[]是数组,n是元素个数,x是要查找的数
{
int i;
a[0]=x;
for(i=n;!(a[i]==x);--i)
if(a[i]>x)
cout<<a[i]<<">"<<x<<endl;
else
cout<<a[i]<<"<"<<x<<endl;
return i;
}
//在数组a[1.2...n]中二分法查找x
//找到时返回元素下标,否则返回0
//前提:a[1.2...n]是非递减有序的
int binsearch(elemtype a[],int n,elemtype x) //二分查找
{
int mid,low=1,high=n;
while(low<=high)
{
mid=(low+high)/2;
if(x==a[mid])
return mid;
else if(x<a[mid])
high=mid-1;
else
low=mid+1;
}
return 0;
}
//在数组a[1.2...n]中二分法查找x,每次打印比较结果
//找到时返回元素下标,否则返回0
//前提:a[1.2...n]是非递减有序的
int binsearch2(elemtype a[],int n,elemtype x) //查找过程
{
int mid,low=1,high=n;
while(low<=high)
{
mid=(low+high)/2;
if(x==a[mid])
{
cout<<a[mid]<<"="<<x<<endl;
return mid;
}
else if(x<a[mid])
{
cout<<a[mid]<<">"<<x<<endl;
high=mid-1;
}
else
{
cout<<a[mid]<<"<"<<x<<endl;
low=mid+1;
}
}
return 0;
}
//打印顺组数据a[1....n]
void printarray(int a[],int n)
{
int i;
cout<<"{";
for(i=0;i<=n;i++)
{
cout<<a[i];
while(i<n)
{
cout<<",";
break;
}
}
cout<<"}"<<endl;
}