- 研究范围:
- 数据元素之间固有的逻辑关系——数据逻辑结构
- 数据元素及关系在计算机内的表示——数据存储结构
- 对数据结构的操作——算法
- 基本概念和术语
- 数据:用来描述现实世界的文字、字符、图像、声音,以及能够输入到计算机中并能被计算机处理的符号集合。
- 数据元素:是数据的基本单位,是数据这个集合中的个体,也称为元素、结点、顶点、记录。一个数据元素可以由若干个数据项组成。数据项是数据不可分割的最小标识单位,也称为字段、域、属性。
- 数据对象:是具有相同性质的数据元素的集合,是数据的一个子集。
- 数据结构:是相互之间存在一种或多种特定关系的数据元素的集合,包括逻辑结构、存储结构和数据的操作。
- 逻辑结构:是从逻辑关系上描述数据,与数据的存储无关,是独立于计算机的,可以看作是从具体问题抽象出来的数学模型。从逻辑上可以把数据结构分为线性结构和非线性结构。集合、树、图都属于非线性结构。
- 集合:结构中的数据元素之间除了“同属于一个集合”的关系之外,没有其他关系
- 线性结构:结构中的数据元素之间存在“一对一”的关系。若结构为非空集,则除了第一个数据元素和最后一个数据元素以外,其他每个数据元素都只有一个直接前驱和一个直接后继。
- 树状结构:结构中的数据元素之间存在“一对多”的关系。若结构为非空集,则除了第一个数据元素外,其他每个数据元素都只有一个直接前驱,以及零个或多个直接后继。
- 图状结构:结构中的数据元素之间存在“多对多”的关系。若结构为非空集,则每个数据元素可有多个(零个)直接前驱和多个(零个)直接后继。
- 存储结构:数据元素及其关系在计算机内的表示称为数据的存储结构。
- 顺序:把逻辑上相邻的数据元素存储在物理位置也相邻的存储单元中,借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系。
- 链式:借助指针表达数据元素之间的逻辑关系。不要求逻辑上相邻的数据元素在物理位置上也相邻。
- 索引:在存储数据元素的同时,还建立附加的索引表。通过索引表,可以找到存储数据元素的结点。顺序存储为基础。在数据文件的基础上增加了一个索引表文件。通过索引表建立索引,可以把一个顺序表分成几个顺序子表,其目的是在查找时提高查找效率,避免盲目查找。
- 哈希:又叫散列存储。根据哈希函数和处理冲突的方法确定数据元素的存储位置。顺序存储为基础。通过数据元素与存储地址之间建立起某种映射关系,使每个数据元素与每一个存储地址之间尽量达到一一对应的目的。这样,查找时可以大大提高效率。
- 数据的操作:是在数据的逻辑结构上定义的操作算法,如插入、删除、检索等。
- 逻辑结构:是从逻辑关系上描述数据,与数据的存储无关,是独立于计算机的,可以看作是从具体问题抽象出来的数学模型。从逻辑上可以把数据结构分为线性结构和非线性结构。集合、树、图都属于非线性结构。
- 线性表
- 线性表可以用顺序存储结构和链式存储结构来表示,分别称为顺序表和链表。
- 线性表是由n个相同性质的数据元素组成的有穷序列。长度为0的线性表为空表,空表中不包含任何数据元素。非空表中的每个数据元素在表中都有一个确定的位置,可用位序号i来表示第i个元素α1在表中的位置。
- 特点:
- 存在唯一一个称为“第一个”的数据元素,它没有直接前驱;
- 存在唯一一个称为“最后一个”的数据元素,它没有直接后继;
- 除第一个数据元素外,表中的每个数据元素有且仅有一个直接前驱;
- 除最后一个数据元素外,表中的每个数据元素有且仅有一个直接后继。
- 顺序表
- 线性表不能被计算机直接处理,必须为它寻找合适的存储结构。线性表的存储结构主要有两种:顺序存储结构和链式存储结构。
- 定义:用一组地址连续的存储单元依次存储线性表中每一个数据元素,这种存储结构称为线性表的顺序存储结构,用这种结构表示的线性表称为顺序表。
- 特点:用数据元素在计算机内物理位置相邻来表示线性表中数据元素之间的逻辑关系。即线性表中数据元素之间的前驱、后继关系映射到数据元素在存储单元地址的相邻关系上。可见,逻辑关系相邻的两个数据元素在物理位置上也相邻。线性表中第一个数据元素的存储位置,称为线性表的首地址或基地址。只要知道了线性表的首地址,就可以随机存取线性表中任意位置上的数据元素。因此,可以称线性表的顺序存储结构为随机存取结构。
- 顺序表的基本操作:初始化、表中元素个数、判断是否为空、插入、删除、查找、获取、遍历。
- 链表
- 链式存储结构与顺序存储结构不同的是:它不要求逻辑上相邻的数据元素在物理位置上也相邻,它通过指针来表示数据元素之间的逻辑关系。
- 单链表
- 概念:线性表的链式存储结构是用一组地址任意的存储单元依次存储线性表中的各个数据元素。数据元素存储在链结点中,链结点由数据域和指针域组成,数据域存放数据元素自身的数据信息,指针域用以存放一个指示其直接后继存储位置的信息。
- 具有n个数据元素的线性表对应的n个链接点通过链接方式链接成一个链表,即为线性表的链式存储结构。由于链表中每个链接点中仅包含一个指针域,故称这样的链表为线性链表或单链表。
- 用线性链表表示线性表时,数据元素之间的逻辑关系是通过结点中的指针表示的。指向链表第一个结点的指针称为链表的头指针,头指针标明链表的首地址,通过它可以存取整个链表。链表中,每个结点都通过它的指针域指向后继结点,但链表的最后一个结点的指针为空(NULL),用于表示它是最后一个结点,在图中用“^”表示。链表中结点个数称为链表的长度。当链表为空表时,头指针为空,链表长度为0。
- 带头结点的单链表
- 在链表的第一个结点之前附设一个结点,称为头结点。其结构与链表中其他结点的结构相同,只是在头结点的数据域中不存放数据,其指针域指向链表的第一个结点。
- 头结点的引入使得单链表的头指针永远不为空,从而给插入、删除等操作带来了方便。对于插入操作,当选用不带头结点的单链表时,如果在第一个结点之前插入一个新节点,则需要改变头指针的值,让其指向新插入的结点;而在其他位置插入时,不需要改变头指针的值,这样就使插入操作变得复杂了。当选用带头结点的单链表时,如果在第一个结点之前插入一个新结点,则不必要改变头指针的值,只需要改变头结点指针域的值,这与在其他位置的插入操作一样,从而简化了插入操作。删除同理。由此可见,带头结点的单链表只是付出了一个结点存储空间的代价,却使单链表的操作简化了许多。
- 循环链表
- 是链表的又一种形式,特点是链表中的最后一个结点的指针域不为空,而是指向头结点,从而使整个链表形成一个环。循环链表也分为带头结点的循环链表和不带头结点的循环链表。
- 优点:从表中任一结点出发均可找到其他结点。
- 双向链表
- 基本概念:是链表的又一种形式,是每个结点中既有指向后继的指针域,又有指向前驱的指针域。每个结点中包括三个域,data域为数据域,prior域为指向前驱结点的指针域,next域为指向后继结点的指针域。与单链表类似,双向链表也分为带头结点的双向链表和不带头结点的双向链表,也可以分为双向循环链表和非双向循环链表,还可以分为带头结点的双向循环链表和不带头结点的双向循环链表。
- 优点:可以顺着结点的prior域逆向扫描链表,可以很容易找到结点的前驱,从而简化许多操作。而单链表或循环单链表的扫描都是从左到右进行的,即顺着结点的next域所指的方向进行。它们最大的缺陷是不能逆向扫描链表,找一个结点的前驱必须遍历表才行。
- 静态链表
- 基本概念:一类比较特殊的链表,它用数组存放线性表中的元素,但并不按数组下标依次存放,而是给每个数组元素增加一个“指针”域,用来存放下一个数据元素在数组中的位置(数组下标),从而构造一个用数组实现的链表,这种链表称为静态链表。所谓静态,是指申请结点的内存空间不是动态的,而是静态的。这里的“指针”并不是真正意义上的指针,而是数组的下标,因此也称为游标。静态链表利用游标来模拟链表的指针,使得插入和删除操作不需要移动数据元素,仅需修改游标,故仍具有链式存储结构的主要优点。
- 顺序表和链表的比较
- 顺序存储结构优缺点
- 优点
- 比较简单
- 可以实现随机存取,存取速度快
- 每个结点只需存储元素本身信息,不需额外空间
- 缺点
- 需要占用一片连续的存储空间,并且需要事先估计存储空间的大小。如果空间分配得太大,有可能用不完从而造成浪费。如果空间分配得小,又有可能不够用。
- 做插入或删除操作时,需要移动大量的元素,效率较低。
- 优点
- 链式存储结构优缺点
- 优点
- 不需要占用连续的存储空间,存储空间是动态分配的,在使用链表前不用事先估计存储空间的大小。
- 在插入或删除操作时,不需要移动大量的元素。虽然链表的插入和删除操作的时间复杂度和顺序表的插入和删除操作一样。但一个是比较操作,一个是移动操作。显然二者所花费的时间不可同日而语。
- 缺点
- 操作算法较复杂
- 不能随机存储。一般情况下,查找结点要从头指针开始,遍历链表。
- 需要额外空间来表示元素间的关系,空间代价较高。
- 优点
- 结论
- 顺序存储结构适合线性表的长度不经常发生变化,不经常进行插入和删除操作,经常进行存取和查询操作
- 链式存储结构适合线性表的长度不可预知,需要频繁进行插入和删除操作。
- 顺序存储结构优缺点
- 栈和队列
- 概述:栈和队列是受限的线性表,它们与线性表的逻辑结构完全相同,所不同的是线性表允许在任何位置进行插入和删除操作,而栈只允许在一端进行插入和删除操作;队列只允许在一端进行插入操作,另一端进行删除操作。
- 栈的定义与操作
- 定义:栈是限定只能在一端进行插入和删除的线性表。允许进行插入和删除操作的一端称为栈顶,另一端为栈底。为了操作方便,通常用一个栈顶指针top指示栈顶位置。在栈顶进行的插入操作称为入栈或进栈,在栈顶进行的删除操作称为出栈或退栈。
- 当栈中没有数据元素时,称为空栈
- 栈的特点:先进后出(Last In First Out,LIFO),即后入栈的元素先出栈。因此栈又称为后进先出的线性表。
- 分类:根据所采用的存储结构不同,栈分为顺序栈和链栈。
- 顺序栈:栈的顺序存储表示称为顺序栈。与顺序表类似,可以用一维数组表示栈,用指针top指示栈顶元素在顺序栈中的位置。
- 链栈:栈的链式存储表示称为链式栈,简称链栈。与链表相似,可以用线性链表实现链式栈。栈中每一个元素用一个链接点表示,每个结点由一个数据域和一个指针域组成。其中,数据域是用来存放数据元素,指针域用来表示元素之间的逻辑关系。同时附设一个指针top,用来指定栈顶元素所在结点的存储位置。链式栈可以有头结点,也可以没有头结点。对于带头结点的链式栈,当头结点的指针域为空时,表示空栈。对于不带头结点的链式栈,栈为空的条件是栈顶指针top为空。
- 队列的定义与操作
- 定义:队列是限定在一端进行插入,在另一端进行删除的线性表。队列中允许插入一端称为队尾,通常用一个队尾指针rear指示队尾位置。队列中允许删除的一端称为队头,通常用一个队头指针front指示队头位置。
- 在队尾插入元素的操作称为入队,在队头删除元素的操作称为出队。入队时,只涉及队尾指针的变化;出队时,只涉及队头指针的变化。
- 当队列中没有数据元素时,称为空队。
- 特点:先进先出(First In First Out,FIFO),即先入队的元素先出队。因此队列又称为先进先出的线性表。
- 分类:根据所采用的存储结构不同,队列也分为顺序队列和链式队列。
- 顺序队列:用顺序存储方式实现的队列称为顺序队列。与顺序表类似,队列中的数据元素依次存储于地址连续的存储空间中,并用队头指针front指向队头元素,用队尾指针rear指向队尾元素的下一个位置(这样做是为了某些操作的方便,并不是唯一的选择,也可以让队头指针front指向队头元素的前一个位置,队尾指针rear指向队尾元素)。入队操作步骤:新元素插入队尾指针所指的位置;队尾指针增一,指向新的位置。出队操作步骤:队头元素出队;队头指针增一。顺序队列的溢出问题:由于顺序存储方式的特点,顺序队列会产生溢出问题。当队满时若进行入队操作,就会产生空间的溢出,称为“上溢出”;当队空时若进行出队操作,也会产生空间的溢出,称为“下溢出”。此外,还有“假溢出”问题,较实用的方法是采用循环队列解决“假溢出”问题。
- 链式队列:队列的链式表示称为链队列。与顺序队列一样,链队列也有队头和队尾指针。队头指针指向链队列的头结点,队尾指针指向链队列的最后一个结点。在队尾插入新结点(入队),在队头删除结点(出队)。由于链队列也是链表的一种,所以链队列也分为带头结点的链队列和不带头结点的链队列。
- 栈和队列的应用
- 只要存在“后进先出”的情况,即可使用栈;存在“先进先出”的,即可使用队列。
- 数制转换
- 表达式计算
- 一个算术表达式是由操作数、运算符和表示运算关系的括号组成,如:(10+20)×4 - 10÷5,运算符在操作数中间,这样的表达式称为中缀表达式,不适于计算机求解表达式。与中缀表达式对应的还有后缀表达式和前缀表达式。
- 运算符和操作数之后的表达式称为后缀表达式,上述例子的后缀表达式为:10 20 + 4 × 10 5 ÷ -,特点如下:
- 操作数与中缀表达式的操作数先后次序相同,而运算符的先后次序不同;
- 后缀表达式中没有括号,而且运算符没有优先级;
- 计算过程严格按照从左到右的顺序执行;
- 从以上特点看出,后缀表达式比较适合计算机求解。求解过程:从左到右依次扫描后缀表达式,若遇到运算符,则对该运算符前面的连续两个操作数用该运算符进行运算。
- 运算符和操作数之前的表达式称为前缀表达式,上述例子的前缀表达式为:- × + 10 20 4 ÷ 10 5,特点同后缀表达式,求解过程从右往左。
- 利用计算机求解表达式分为两个步骤:第一、把中缀表达式转换为后缀表达式或前缀表达式;第二、按照后缀表达式或前缀表达式的运算过程计算表达式的值。
- 串
- 字符串是一种特殊的线性表,简称为串。
- 概念:是由n(n≥0)个字符组成的有限序列。
- 串的术语:
- 长度为零的串称为空串,表示串中不包含任何字符。
- 由一个或多个空格组成的串称为空格串。空格串依然有长度,因此它不是空串。
- 由串中任意连续字符组成的子序列称为子串,而包含子串的串称为该子串的主串。空串是任意串的子串。
- 单个字符在字符串的序号(大于等于0的整数)称为该字符在串中的位置,而子串的第一个字符在主串中的位置称为子串的位置。
- 若两个串的长度相等且对应位置上的字符也相等,则称两个串相等。
- 线性表的数据元素可以是任意数据类型,而串的数据元素只能是字符类型;
- 线性表一次操作一个数据元素,而串一次操作多个数据元素,即以子串为操作单位。
- 串的定长顺序存储结构表示
- 定义:与线性表的顺序存储结构类似,也是采用静态内存分配方式,分配一组地址连续的存储单元,用来存放串的字符序列。
- 串的堆存储表示
- 定义:是指采用动态内存分配方式,分配一组地址连续的存储单元,用来存放串的字符序列。由于采用动态内存分配方式,因此,串的长度是可变的,不存在串截断的问题。
- 串的链式存储结构
- 定义:与线性表的链式存储结构类似,串的链式存储也是用链表存储串值的字符序列。与线性表的链式存储结构不同的是,串的链式存储存在结点大小的问题,即每个结点存放一个字符,还是存放多个字符。每个结点存放多个字符的链式存储结构又称为块链。
- 串的模式匹配
- 扫描主串S,寻找子串T在主串S中首次出现的起始位置,称为串的模式匹配。其中,主串S又称为目标串;子串T又称为模式串。下面是顺序存储结构的两种串的模式匹配算法——Brute-Force算法和KMP算法。
- Brute-Force算法
- 基本思想:也称为朴素的模式匹配算法,其基本思想是从主串的第一个字符起,与模式串的第一个字符比较。若相等,则依次比较后续字符;否则,从主串的第二个字符起,重新与模式串中的字符比较。重复这个过程,直至模式串中的每个字符依次与主串中的一个连续字符序列相等,则匹配成功;否则,匹配失败。
- KMP算法
- Knuth克努特、Morris莫里斯、Pratt普拉特三个人同时提出了模式匹配的改进算法,称为Knuth-Morris-Pratt算法,简称KMP算法。该算法较Brute-Force算法效率高。
- 回溯是否必要
- Brute-Force算法效率低的原因在于回溯,即在某趟匹配失败后,主串指针i要回到本趟比较的首字符的下一个字符位置,模式串指针j要回到首字符位置,然后进行新一趟的匹配。
- 主串S,模式串J:当s(i) 与 t(j)比较不相等时,主串指针i不必回溯,模式串指针j向右“滑动”到k位置,直接比较s(i)与t(k)。问题的关键是如何确定k值。答案是使用next(j)函数。
- 数组和广义表
- 从某种意义上来说,数组和广义表是线性表的推广,即它们的数组元素构成线性表,而数据元素本身又是一个数据结构。线性表的顺序存储就是用一堆数组来实现的,除此之外,数组还是一种数据结构。
- 数组
- 概念:数组是n(n≥1)个具有相同数据类型的数据元素构成的有限序列,并且这些数据元素占用一片地址连续的内存单元。
- 数组中的数据元素可以用该元素在数组中的位置来表示,即数据元素与位置之间有一一映射关系。该位置通常称作数组的下标。
- 数组一般分为一维数组、二维数组和n维数组。一维数组就是定长的线性表。二维数组可以看成是一维数组,但其每个数据元素又是一个一维数组。同理n维数组也可以看成是一维数组,但其每个数据元素又是一个n-1维数组。由此可见,n维数组是线性表在维数上的扩张,即线性表中的元素又是一个线性表。
- 数组的顺序存储
- 通常对数组只做随机访问元素和修改元素值操作,不做插入和删除操作。这样,数组建立后,其数据元素个数和元素间的关系不再发生变动,因此,一般采用顺序存储结构表示数组。
- 由于计算数组中每个元素存储位置的时间相等,所以存取数组中任意一个元素的时间也相等,即数组是随机存取的存储结构。
- 矩阵
- 广义表
- 线性表要求它的每个数据元素必须是结构上不可再分的单个元素。而广义表中的数据元素既可以是单个元素,也可以是广义表。因此,从这个意义上来说,广义表是线性表的推广,线性表是广义表的特例。
- 树和二叉树
- 数据结构分为线性结构和非线性结构两大类。树和二叉树就是非线性结构中非常重要的一员,它适宜描述具有层次结构的数据。
- 树
- 概念:树是由n(n≥0)个元素构成的有限集合。其中,n=0称为空树;n>0称为非空树。对于非空树,都满足以下条件:
- 有且仅有一个称为根的结点,它比较特殊,没有前驱结点;
- 其余结点被分成m(m≥0)个互不相交的有限集,其中每一个集合又是一棵树,称为根的子树。
- 从树的定义来看,树是递归定义的。即一棵树由若干棵子树构成,而子树又由更小的若干棵子树构成。除根节点以外,树中的每个结点都有且只有一个直接前驱结点,若干个直接后驱结点。而根节点没有直接前驱结点。这反映了数据元素的层次关系,因此,凡是分类分级的问题都可用树来描述。
- 基本术语
- 结点的度:结点所拥有子树的个数称为结点的度。
- 树的度:树中所有结点的度的最大值称为树的度。
- 叶结点:度为零的结点称为叶结点,也称为终端结点或叶子。
- 分支结点:度不为零的结点称为分支结点,也称为非终端结点。除根节点以外,分支结点也称为内部结点。显然,一棵树除叶结点以外,其他结点都是分支结点。
- 孩子结点和双亲结点:树中一个结点的子树的根节点称为孩子结点。该结点就称为孩子结点的双亲结点。
- 兄弟结点:具有同一双亲的孩子结点互为兄弟结点。
- 结点的祖先:从根到该结点所经分支上的所有结点,称为结点的祖先。
- 结点的子孙:以某结点为根的子树中的任一结点都称为该结点的子孙。
- 结点的层次:树是一种层次结构,树中的每个结点都处在某个层次上。从根节点开始,根节点定义为第一层,它的孩子定义为第二层,以此类推。
- 树的深度:树中所有结点的层次的最大值称为树的深度,也称为树的高度。
- 有序树:如果树中各结点的子树是按照从左到右有序排列的,即各子树的位置不能交换,这样的树称为有序树。如果交换子树的位置,则产生一棵新的有序树。
- 无序树:如果树中各结点的子树的排列是无序的,称为无序树。即使交换了子树的位置,也不会构成新的树。
- 森林:m(m≥0)棵互不相交的树的集合称为森林。如果去掉一棵树的根,则树就变成了森林。同样,给森林添加一个根,则森林就变成了树。
- 树的表示方法
- 树的逻辑表示方法主要有:树形表示法、嵌套集合表示法、凹入表示法和广义表表示法。
- 树形表示法:以圆圈表示结点,以连线表示结点之间的关系。虽然连线不带箭头,但隐含有方向,即从上到下的方向。表示上方结点是下方结点的前驱,下方结点是上方结点的后继。
- 嵌套集合表示法:也称为文氏图表示法。用圆圈表示树、子树和结点,并用包含关系表示结点间的关系。需要注意的是同一个根节点下的各子树对应的圆圈不能相交。
- 凹入表示法:就是结点逐层缩进,即孩子结点缩进于双亲结点。
- 广义表表示法:就是用广义表表示树。用名称表示树的根,括号内表示子树。
- 树的存储结构
- 在实际应用中,大多采用链式存储结构表示树。
- 双亲表示法
- 双亲表示法就是以一组地址连续的存储空间存储树的结点。其中,每个结点包含数据域和指针域。数据域存放数据元素的值,指针域存放一个指向其双亲的指针。但指向其双亲的指针并不是真正指向内存地址的指针,而是类似于静态链表中的游标,即数组的下标。
- 规定根节点的指针域的值为-1.双亲表示法充分利用了每个结点(根节点除外)仅有一个双亲的性质。在这种存储结构中,寻找结点的双亲非常容易,但寻找结点的孩子则需要遍历整个结构。
- 孩子表示法
- 孩子表示法就是在结点中设置指向每个孩子的指针域,利用指针指向该结点的所有孩子结点。
- 由于树中的结点可能有多个孩子,而且每个结点的孩子数可能不相同。为了不浪费存储空间,最好为每个结点设置变长的指针域个数,但算法实现起来非常麻烦。因此,孩子表示法大多采用按树的度设置结点的指针域的个数。
- 在这种存储结构中,寻找结点的孩子非常容易,但寻找结点的双亲则不容易。
- 为了克服孩子表示法和双亲表示法的缺点,可以把二者结合起来。将树的所有结点存储在一个一维数组中,每个结点包含一个数据域、一个双亲指针域和一个孩子链表指针域。其中,双亲指针域用于存放双亲结点的位置,孩子链表指针域用于存放孩子链表的头指针。孩子链表是一个单链表,用于存放结点的所有孩子。这种表示法称为孩子链表表示法。
- 孩子兄弟表示法
- 孩子兄弟表示法就是在结点中设置两个指针域,一个指针域指向该结点的第一个孩子,另一个指针域指向其右兄弟。当然,在节点中还包括一个数据域。
- 孩子兄弟表示法有利于查找结点的孩子和兄弟,但寻找结点的双亲仍然不方便。
- 概念:树是由n(n≥0)个元素构成的有限集合。其中,n=0称为空树;n>0称为非空树。对于非空树,都满足以下条件:
- 二叉树
- 二叉树是n(n≥0)个结点构成的有限集合。当n=0时,它是一棵空二叉树;当n>0时,它由一个根节点和两棵互不相交的、分别称作左子树和右子树的二叉树构成。 二叉树也是一个递归定义:
- 二叉树的度只能是0、1或2;
- 二叉树是有序树,它的左、右子树是有次序的,即使只有一棵子树也要区分是左子树还是右子树。
- 二叉树的五种基本形态:空二叉树、只有根节点的二叉树、右子树为空的二叉树、左右子树均不空的二叉树和左子树为空的二叉树。任何复杂的二叉树均可由这五种基本形态复合而成。
- 两种特殊形态的二叉树
- 满二叉树
- 如果二叉树的所有分支结点都有左子树和右子树,并且所有叶子结点都有二叉树的最下一层,则称这样的二叉树为满二叉树。
- 满二叉树是所有二叉树中结点数最多的二叉树。满二叉树中,没有度为1的结点,只有度为0和度为2的结点。
- 完全二叉树
- 在一棵具有n个结点的二叉树中,如果它的结构和满二叉树的前n个结点的结构相同,则称这样的二叉树为完全二叉树。
- 在完全二叉树中,只有最下面两层结点的度数可以小于2。对比满二叉树和完全二叉树,可以看出,满二叉树是完全二叉树的特例。
- 满二叉树
- 二叉树的性质
- 二叉树的存储结构
- 既有顺序存储结构,也有链式存储结构,但链式存储结构较常用。
- 二叉树的顺序存储结构
- 就是用一组地址连续的存储单元依次存放二叉树的数据元素。
- 可以将一棵具有n个结点的完全二叉树上所有结点按照其编号依次存放于一维数组中。数组中每个元素的下标与该元素在完全二叉树中相应结点的编号相对应,因此数组下标之间的关系也反映了二叉树中结点之间的逻辑关系。但是,对于一般的非完全二叉树,若仍按照从上到下和从左到右的次序存储在一维数组中,则数组下标之间的关系不能反映二叉树中结点之间的逻辑关系。为了实现顺序存储,首先需要把非完全二叉树转换成完全二叉树。具体做法是为非完全二叉树增添一些实际上不存在的“空节点”,使之成为完全二叉树形态,然后再按上述完全二叉树的顺序存储结构进行存储。在存储时,“空节点”以空值存储。
- 对于完全二叉树,用顺序存储结构比较合适,既能充分利用存储空间,又能简化二叉树的操作。对于非完全二叉树,不适宜采用顺序存储结构。另外,顺序存储结构所固有的缺陷——插入和删除操作效率低,依然存在。
- 二叉树的链式存储结构
- 二叉树的链式存储结构是指用链表形式来存储二叉树。根据链表中每个结点指针域的数目又可分为二叉链表和三叉链表。
- 二叉链表
- 链表中每个结点包含3个域,分别是数据域、左孩子指针域和右孩子指针域。其中data域存放结点的数据,left域和right域分别存放指向左孩子和右孩子的指针。
- 与单链表类似,二叉链表也分为带头结点和不带头结点两种类型。
- 是一种使用较普遍的二叉树存储结构。它的结构简单,可方便地实现二叉树的大多数操作。缺点是不便于对双亲结点的操作。
- 三叉链表
- 在二叉链表的基础上增加了指向双亲的指针域,从而解决了二叉链表对双亲操作不方便的问题。
- 二叉树的遍历
- 二叉树的遍历是指按一定次序访问二叉树中的每个结点,且每个结点仅被访问一次。这里的“访问”是指对结点的某种处理。换个说法,遍历就是按照某种规则将树中的元素人为排列成一个线性序列的过程。
- 按照二叉树的定义,任何一个非空二叉树都由根节点、左子树和右子树构成。因此,遍历二叉树的过程就是按照某种次序遍历这三部分的过程。而这三部分可以组成6种次序,即D(访问根节点)L(遍历左子树)R(遍历右子树)、LDR、LRD、DRL、RDL、RLD。若限定先左后右的次序,则只有前三种情况符合要求。分别称为前序遍历、中序遍历和后序遍历。
- 前序遍历:若二叉树非空,则按以下次序进行遍历:
- 访问根节点;
- 前序遍历根节点的左子树;
- 前序遍历根节点的右子树。
- 需要注意的是:遍历左子树和右子树,仍然需要按照上述次序进行,即前序遍历也是一个递归定义。
- 前序遍历的递归算法简洁、明了,但递归算法的效率不高。如果需要高效算法,可以采用以下非递归算法。非递归算法的关键是利用堆栈实现要求的访问次序。
- 中序遍历:若二叉树非空,则按以下次序进行遍历:
- 中序遍历根节点的左子树;
- 访问根节点;
- 中序遍历根节点的右子树。
- 后序遍历:若二叉树非空,则按以下次序进行遍历:
- 后序遍历根节点的左子树;
- 后序遍历根节点的右子树;
- 访问根节点。
- 二叉树是n(n≥0)个结点构成的有限集合。当n=0时,它是一棵空二叉树;当n>0时,它由一个根节点和两棵互不相交的、分别称作左子树和右子树的二叉树构成。 二叉树也是一个递归定义:
- 线索二叉树
- 遍历二叉树就是把二叉树中所有结点排成一个线性序列的过程。在这个线性序列中,除了第一个和最后一个结点以外,每个结点都有一个直接前驱和一个直接后继结点。然而直接前驱和直接后继信息在二叉树的存储结构中并没有体现出来,只能在二叉树的遍历过程中得到这些信息。为了保存遍历得到的结点直接前驱和直接后继信息,通常的做法是建立线索二叉树。由于遍历方法不同,所获得的线性序列中,结点的前驱和后继也不同,因此线索二叉树又分为前序线索二叉树、中序线索二叉树和后序线索二叉树。
- 通过对二叉链表的分析可知,存储n个结点的二叉链表具有n+1个空指针域。线索二叉树正是利用了这些空指针域,在这些空指针域中设置指向直接前驱和直接后继结点的指针,这些指针被称为线索,加了线索的二叉树被称为线索二叉树。对二叉树以某种方法遍历使其变为线索二叉树的过程称为线索化。
- 森林
- 哈夫曼树
- 图
- 另一种非线性结构,比树更复杂,它的数据元素之间存在多对多的关系,即图中任意一个结点都有多个前驱结点和后继结点。
- 概念:图中的数据元素称为顶点。图中两个顶点有关联关系,称作顶点A和顶点B之间有一条边。图是由顶点的非空有限集合和顶点间关系集合构成的数据结构。
- 基本术语
- 无向图:由没有方向的边构成的图称为无向图。无向图中的边由顶点的无序偶对组成。
- 有向图:由有方向的边构成的图称为有向图。
- 弧:有向图中的边由顶点的有序偶对组成。顶点偶对表示从顶点A指向顶点B的一条有向边,也称为弧。顶点A是有向边的始点,也成为弧尾。顶点B是有向边的终点,也成为弧头。
- 完全图:含有n个结点和(1/2)n(n-1)条边的无向图称为完全图。在完全图中,任意两个顶点之间均有边相连。
- 有向完全图:含有n个顶点和n(n-1)条弧的有向图称为有向完全图。在有向完全图中,任意两个顶点之间均有两条方向相反的弧。
- 邻接点:在无向图中,若存在一条边(vi,vj),则称vi和vj互为邻接点。称边(vi,vj)依附于顶点vi和vj或称边(vi,vj)与顶点vi和vj相关联。
- 顶点的度:在无向图中,与顶点v相关联的边数称为顶点v的度,记作TD(v)。在有向图中,顶点的度又分为顶点的入度和顶点的出度。顶点的入度是指以顶点v为弧头的弧的数目,记作ID(v);顶点的出度是指以顶点v为弧尾的弧的数目,记作OD(v)。顶点v的度等于顶点v的入度和出度之和,即TD(v)=ID(v)+OD(v)。
- 路径:在图G中,从顶点vi出发,经过一系列的边或弧能够到达顶点vj,则称顶点vi到顶点vj的顶点序列为从顶点vi到顶点vj的路径。这条路径上所包含的边的数目称为路径长度。
- 简单路径:若路径上各顶点互不重复,则称这样的路径为简单路径。
- 环或回路:若路径上第一个顶点和最后一个顶点相同,则称这样的路径为环或回路。
- 子图:只选取原图的部分顶点和部分边组成的图。
- 连通图:在无向图中,若从顶点vi到顶点vj有路径存在,则称vi和vj是连通的。如果无向图中任意两个顶点都是连通的,则称该无向图为连通图;否则,就是非连通图。无向图中的极大连通子图称为该图的连通分量。
- 强连通图:在有向图中,若一对顶点vi和vj(vi≠vj)存在从vi到vj和从vj到vi的有向路径,则称vi和vj是连通的。若有向图中任意两个顶点vi和vj(vi≠vj)之间都是连通的,则称该有向图为强连通图。有向图中的极大强连通子图称为强连通分量。
- 权:图中边或弧上附带的数据称为权。
- 网:带权的图称为网。
- 生成树:包含连通图全部顶点的极小连通子图称作该图的生成树,即以最少的边连接连通图中所有顶点。
- 图的存储结构
- 图的存储结构有多种,其中最常用的是邻接矩阵和邻接表。
- 邻接矩阵
- 定义:表示顶点之间相邻关系的矩阵。它以矩阵的行和列表示顶点,以矩阵中的元素表示边和弧。邻接矩阵是图的顺序存储结构。
- 在图的邻接矩阵存储结构中,常用一维数组存放顶点信息,用二维数组作为邻接矩阵存放顶点之间关系的信息。
- 邻接表
- 定义:当图的边数远远小于图的顶点数时,邻接矩阵就变成了稀疏矩阵,此时用邻接矩阵存储图就会浪费大量存储空间。较好的解决方法是采用邻接表。
- 邻接表是图的链式存储结构。邻接表由边表和顶点组成。边表就是对图中的每个顶点建立的单链表,单链表中存放与同一个顶点相邻接的邻接点,相当于邻接矩阵中的一行。实际上,单链表中的邻接点与该顶点可以组成一条边,因此可以认为边表中存放的就是边的信息。
- 顶点表用于存放图中每个顶点的信息以及指向该顶点边表的头指针。顶点表通常采用顺序存储结构。
- 十字链表
- 是适用于有向图的链式存储结构。它仍然由边表和顶点组成,只不过边表和顶点表的结点结构发生了变化。边表中的结点用于表示一条弧。
- 在十字链表中,可以很容易地找到以某结点为弧尾的弧,也可以很容易地找到以某顶点为弧头的弧,因此求顶点的入度和出度非常容易。实际上,十字链表是邻接表和逆邻接表的结合体。
- 邻接多重表
- 是适用于无向图的链式存储结构,是邻接表的改进形式,它解决了在邻接表中对边操作不方便的问题。在邻接多重表的边表中存放的是真正的边,即便的两个顶点存放于边表的一个结点中。
- 图的遍历
- 从图中指定的顶点出发,按照指定的搜索方法访问图的所有顶点,且每个顶点仅被访问一次,这个过程称为图的遍历。
- 主要有两种方法:深度优先搜索遍历(Depth-First Search,DFS)和广度优先搜索遍历(Breadth-First Search,BFS)。
- 深度优先搜索
- 连通图的深度优先搜索遍历
- 类似于树的先根遍历,是树的先根遍历的推广。
- 遍历方法:从图中指定的顶点v 出发,先访问v,然后依次从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相同的顶点都被访问到为止。换而言之,首先访问v,然后从v的未被访问过的邻接点中任取一个顶点w,访问w;再从w的未被访问过的邻接点中任取一个顶点s,访问s;依次类推,直至一个顶点的所有邻接点均被访问过,则依照先前的访问次序回退到最近被访问过的顶点,若它还有未被访问过的邻接点,则从这些未被访问过的邻接点中任取一个重复以上过程,直至图中所有顶点均被访问过为止。
- 非连通图的深度优先搜索遍历
- 以邻接矩阵作为存储结构,实现非连通图的深度优先搜索遍历。
- 若是连通图,则一次遍历即可访问图的每一个顶点。若是非连通图,则一次遍历仅能访问开始顶点所在连通分量中的每一个顶点,其他连通分量中的顶点则无法访问到。因此,对于非连通图,在遍历完一个连通分量之后,还要再选择一个开始顶点,遍历下一个连通分量,重复这个过程,直至图中的所有顶点均被访问过为止。
- 连通图的深度优先搜索遍历
- 广度优先搜索
- 连通图的广度优先搜索遍历
- 类似于树的层次遍历(按照从上到下,从左到右的顺序遍历),是树的层次遍历的推广。
- 遍历方法:从图中指定的顶点v出发,先访问v,再依次访问v的未被访问的所有邻接点wi,然后分别从这些邻接点wi出发依次访问它们的未被访问的所有邻接点tj;依次类推,直至图中所有顶点均被访问过为止。访问邻接点wi的次序要带入到访问邻接点tj中。假设邻接点w1先于邻接点w2被访问,w1的邻接点为t1,w2的邻接点为t2,则先被访问的邻接点w1,它的邻接点t1也要先被访问,后被访问的邻接点w2,它的邻接点t2也要后被访问,即先访问t1后访问t2。
- 非连通图的广度优先搜索遍历
- 以邻接表作为存储结构,实现非连通图的广度优先搜索遍历。
- 与深度优先搜索遍历一样,若是连通图,则一次遍历即可访问图中的每一个顶点。若是非连通图,则依次遍历图的每个连通分量。
- 连通图的广度优先搜索遍历
- 最小生成树
- 生成树是连通图中的极小连通子图,它由连通图的n个顶点和不构成回路的n-1条边构成。显然,满足上述条件的生成树有多颗,换言之,图的生成树不唯一。
- 对于带权的图,其生成树的边也带权。在这些带权的生成树中必有一棵边的权值之和最小的生成树,这棵生成树就是最小(代价)生成树。
- 构造最小生成树的方法有两种,分别是普里姆算法(Prim)和克鲁斯卡尔(Kruskal)算法。
- 最短路径
- 路径长度一般是指路径上边的数目。对于带权图,路径长度是指路径所经过的所有边上的权值之和。图的顶点间可能存在多条路径,其中路径长度最短的路径称为最短路径。
- 分为两种情况:从图中某个顶点到其余顶点间的最短路径和图中每对顶点之间的最短路径。
- 从某个顶点到其余顶点的最短路径
- 迪杰斯特拉算法:为求解一个确定顶点(称为源点)到图中其余顶点的最短路径问题,迪杰斯特拉提出了一个按路径长度递增的次序产生最短路径的算法。
- 此算法把带权图中的所有顶点分成两个集合V和W。集合V中存放已找到最短路径的顶点,集合W中存放当前还未找到最短路径的顶点。
- 弗洛伊德算法:
- 拓扑排序和关键路径
- 拓扑排序
- AOV网
- 在有向图中,如果用图中的顶点表示活动(事件、任务),用弧(有向边)表示活动间的先后关系,则称这样的有向图为顶点活动网(Activity On Vertex Network,AOV网)。
- 在AOV网中,若存在一条从顶点vi到顶点vj的有向路径,则称vi是vj的前驱,vj是vi的后继。若<vi,vj>是网中的一条弧,则称vi是vj的直接前驱,vj是vi的直接后继。
- 拓扑排序
- 对于一个AOV网,若存在满足以下性质的一个线性序列,则这个线性序列称为拓扑序列。构造拓扑序列的操作称为拓扑排序。
- 网中的所有顶点都在该序列中;
- 在从顶点vi到顶点vj存在一条路径,则在线性序列中,vi一定排在vj的前面。
- 对于一个AOV网,若存在满足以下性质的一个线性序列,则这个线性序列称为拓扑序列。构造拓扑序列的操作称为拓扑排序。
- AOV网
- 关键路径
- AOE网
- 在带权有向图中,如果用顶点表示事件,用有向边表示活动,边上的权值表示活动持续的时间,则称这样的有向图为边表示活动的网(Activity On Edge Network,AOE网)。
- 对一个只有开始点和一个完成点的工程,可用AOE网来表示。网中仅有一个入度为0的顶点称为源点,它表示工程的开始点。同时,网中也仅有一个出度为0的顶点称为汇点,它表示工程的完成点。
- 关键路径
- 在AOE网中,从源点到汇点之间可能有多条路径,这些路径中具有最大路径长度的路径称为关键路径。关键路径上的活动称为关键活动。关键活动持续时间的总和(关键路径的长度)就是完成一个工程的最短工期。
- 与关键路径有关的几个基本概念
- 事件的最早发生时间
- 事件的最迟发生时间
- 活动的最早开始时间
- 活动的最迟开始时间
- 活动的时间余量
- 确定关键路径
- 方法:首先,求出所有事件的最早发生时间和最迟发生时间,然后利用它们再求出所有活动的最早开始时间和最迟开始时间,最后根据活动的时间余量是否为零确定关键活动。关键活动所在的路径就是关键路径。
- AOE网
- 拓扑排序
- 查找
- 常见的查找方法主要有顺序查找、折半查找、分块查找、二叉排序树等
- 查找的基本概念
- 查找表
- 静态查找表
- 动态查找表
- 关键字
- 主关键字
- 次关键字
- 查找
- 平均查找长度
- 顺序查找
- 是在静态查找表上进行的查找。这里的静态查找表通常用顺序表表示。
- 基本思想:从顺序表的一端开始,用指定的关键字与顺序表中数据元素的关键字逐一进行比较。若有与之相等的数据元素,则查找成功,返回数据元素在顺序表中的位置;否则,查找失败,返回0。
- 折半查找
- 也称二分查找,应用于静态查找表。它要求查找表是有序表,通常采用有序的顺序表。也就是说顺序表中的数据元素按关键字有序(非递增或非递减)排列。
- 查找过程:首先确定待查找区间的中间位置,然后把待查找关键字key与中间位置上数据元素的关键字mkey作比较;若key=mkey,则查找成功;若key<mkey,则在待查找区间的前半子区间继续这样的查找;若key>mkey,则在待查找区间的后半子区间继续这样的查找;直至找到或查找区间的上界小于下界(没找到)为止。
- 由于每查找一次即把查找区间缩小一半,因此称作折半查找。
- 分块查找
- 又称为索引顺序查找,应用于静态查找表。它要求顺序表中的数据元素是“分块有序”的。所谓分块有序,是指将顺序表均分成k块,并保证前一块中的最大关键字小于后一块的最小关键字,但每一块的关键字不一定有序。
- 为了加快查找速度,分块查找又构建了一个索引表。它用每块中的最大关键字及该块在顺序表中的起始位置构成索引表。这样在进行查找时,首先查找索引表,以确定要查找的数据元素在哪一块中。然后,在已经确定的块中进行查找。由于索引表是有序表,因此对索引表的查找,可以采用折半查找,也可以采用顺序查找。而在块查找时,只能用顺序查找,因为块内关键字不一定有序。
- 二叉排序树
- 二叉排序树或者是空树,或是具有以下性质的二叉树:
- 若左子树非空,则左子树所有结点的关键字值均小于它的根节点的关键字值;
- 若右子树非空,则右子树所有结点的关键字值均大于等于它的根节点的关键字值;
- 左右子树本身又各是一棵二叉排序树。
- 可见,二叉排序树的定义与二叉树一样,也是一个递归定义。对二叉排序树进行中序遍历可以得到一个有序序列。
- 二叉排序树或者是空树,或是具有以下性质的二叉树:
- B - 树 和 B + 树
- B - 树
- B - 树是对二叉排序树的一种扩展,它是一种多路平衡查找树,在文件系统中使用较多。所谓多路,是指树的分支多余二叉;平衡是指所有叶子结点均在同一层上,以避免出现单支树的情况。需要注意的是,B - 树是一种树而非二叉树。
- B - 树的阶:树中所有结点的孩子结点最大值称为B - 树的阶。通常用m来表示,从查找效率来考虑,通常取m≥3。
- B - 树:一棵m阶B - 树或者是空树,或者是满足以下性质的m叉树:
- 树中每个结点至多有m棵子树;
- 若根节点不是叶子结点,则根节点至少有两棵子树;
- 除根节点以外,其他结点至少有[m/2]棵子树;
- 所有叶子结点都在同一层上,并且叶子结点所在的层数为树的深度。
- B - 树的查找
- B + 树
- B + 树是指一棵m阶B + 树或者是空树,或者是满足以下性质的m叉树:
- 树中每个结点至多有m棵子树;
- 若根节点不是叶子结点,则根节点至少有两棵子树;
- 除根节点以外,其他结点至少有[m/2]棵子树;
- 有n棵子树的结点有n个关键字;
- 所有叶子结点都在同一层上,按从小到达的顺序存放全部关键字,并且各个叶子结点顺序链接。
- 所有非叶结点中仅包含它的各个子节点中最大关键字及指向子节点的指针。
- B + 树的查找
- B + 树是指一棵m阶B + 树或者是空树,或者是满足以下性质的m叉树:
- B - 树
- 哈希表
- 定义
- 哈希函数:在数据元素的关键字与其存储位置之间建立一个对应关系,使每个关键字与表中一个唯一的存储位置相对应,称这个对应关系为哈希函数,也称为散列函数,记为h(key)。按这个思想建立的表称为哈希表。
- 哈希地址:由哈希函数得到的存储位置称为哈希地址。
- 冲突:在构造哈希表时,不同的关键字可能得到同一个哈希地址,这种现象称为冲突。在构造哈希表时,冲突在所难免。
- 同义词:把具有不同关键字而有相同哈希地址的数据元素称为同义词。
- 哈希表:根据设定的哈希函数和处理冲突的方法将一组关键字映射到一个有限的地址集上,并以关键字在地址集中的“象”作为数据元素在表中的存储位置,这种表便称为哈希表。
- 在构造哈希表时,如果遇到冲突,则可以根据设定的处理冲突的方法,为数据元素选择一个新的哈希地址,如此重复,直至不产生冲突为止。
- 哈希函数的构造方法
- 为减少冲突,应构造合适的哈希函数。所谓合适的哈希函数,是指经过哈希函数映射后,哈希地址应均匀分布在整个地址区间中,而且计算过程尽可能简单,节省时间。
- 几种常用的哈希函数构造方法:
- 直接定址法:取关键字本身或关键字的某个线性函数值为哈希地址。即h(key)=key或h(key)=a*key+b,(a和b均为常数)
- 除留余数法:选择一个适当的正整数m(m≤表长),并用数据元素的关键字除以m,取所得的余数作为哈希地址。即h(key)=key%m。是最常用的一种方法。
- 数字分析法:在由r位数字构成的关键字中,选取其中若干位数构成哈希地址。在用数字分析法构造哈希函数时,要事先知道可能出现关键字的全部或大部。通过分析后,去掉值分布不均匀的位,选取值分布较均匀的位。
- 平方取中法:取关键字平方后的中间几位为哈希地址。通过求关键字的平方值可以扩大相近数的差别,同时一个数平方后的中间几位与这个数的每一位都有关,因此,平方取中法产生的哈希地址较为均匀,冲突的机会相对较小。对于平方取中法,到底取几位由哈希表的表长决定。
- 折叠移位法:把一个关键字分成位数相同的若干段,然后将各段的叠加和(舍去进位)作为哈希地址。折叠法又分为移位叠加和间界叠加。移位叠加是将各段的最低位对齐,然后相加;间界叠加则是相邻的段沿边界来回折叠,然后对齐相加。
- 处理冲突的方法
- 开放定址法
- 使用某种探查技术在哈希表中形成一个探查序列,当冲突发生时,沿此序列逐个单元地查找,直到找到空闲单元地址的方法。
- 按照行程探查序列的方法不同,可将开放定址法分为线性探查法、平方探查法、双哈希函数探查法。
- 链地址法
- 链地址法解决冲突的做法是:把所有关键字为同义词的数据元素存放在同一个链表中。
- 优点:处理冲突简单,且无聚集现象,因此平均查找长度较短;在哈希表中,删除结点的操作易于实现。
- 缺点:需要额外的空间存放指针,因此表较小时,开放定址法较为节省空间。
- 适用于冲突较严重及经常在哈希表中删除数据元素的场合。
- 开放定址法
- 定义
- 排序
- 将一组次序任意的数据元素转变为按其关键字值递增或递减次序排列的过程,称为排序。
- 稳定的排序方法
- 不稳定的排序方法
- 内部排序
- 外部排序
- 插入排序
- 直接插入排序
- 折半插入排序
- 希尔排序
- 交换排序
- 冒泡排序
- 快速排序
- 选择排序
- 直接选择排序
- 堆排序
- 归并排序
- 二路归并
- 三路归并
- 四路归并
- 基数排序
- 各种排序方法的比较
- 当考虑排序速度快时,快速排序、希尔排序和堆排序可供选择,其中快速排序是速度最快的;
- 当考虑排序序列规模时,若规模较小可采用选择排序和插入排序,若规模较大可采用归并排序、堆排序和快速排序;
- 当待排序序列基本有序时,直接插入排序和冒泡排序效果较好。
-
转载于:https://www.cnblogs.com/bookwed/p/6763300.html