【软考】【软件设计师】【知识模块】【第3章:数据结构】
第三章:数据结构:
数据结构是程序设计的重要基础
学会数据结构的目的:
学会从问题出发,分析和研究计算机加工的数据的特性,
以便为应用所涉及的数据选择适当的逻辑结构、存储结构及其相应的操作方法;
【对于一个数据结构,需要考虑三个因素:逻辑结构、存储结构、运算方法(操作方法)】
为提高利用计算机解决问题的效率服务;
数据结构是指:数据元素的集合及元素间的相互关系和构造方法。
元素间的相互关系:数据的逻辑结构
元素间关系的存储:存储结构(或称之为 物理结构)
数据结构的分类:
线性结构
非线性结构
又分为树结构、图结构;
数据结构是算法设计的基础。
线性结构:线性结构主要用于对客观世界中具有单一前驱和后继的数据关系进行描述。
线性表
按照存储方式分类:
采用顺序存储:用一组地址连续的存储的存储单元 依次存储线性表中的数据元素,
现象:逻辑上相邻的两个元素,在物理位置上也相邻。
优点:可以随机存取表中的元素,即可以对数据随机访问。
缺点:插入和删除需要移动其他元素;(有时的移动量挺大)
特点:
各个结点的空间应该需要事先分配完毕,后续不论是否有数据,这个空间都不会变化,指导删除整个数据结构为止。
采用链式存储:用 通过指针链接起来的结点 来存储数据元素;
基本结构:数据源 和指针域;每个结点都有这两项;
数据域用于存储数据元素的值;
指针域则存储当前元素的直接前驱 或 直接后继的位置信息。指针域中的信息称为 指针(或链)
特点:存储各个元素的节点的物理地址并不要求是连续的。
在存储元素的同时,必须要存储元素的逻辑关系。
结点空间只有在需要申请的时候才会被申请,无需事先分配。
需要一个头指针head,指向第一个结点,就可以顺序的访问到表中的任意一个元素。
链式存储的线性表,需要删除或插入时,实际只是针对指针的修改,数据域没有做必要的修改。
在实际应用中,为了简化对链表状态的判定和处理,特别引入一个不存储数据元素的结点,称之为头结点,将其作为链表的第一个结点并令头指针指向该节点。
缺点:不能对数据元素进行随机访问
有点:插入和删除操作,不需要移动元素。
单链表的查找、插入、删除运算,参考P102
其他几种链式存储结构:
双向链表:每个结点包含有两个指针,一个是直接前驱,一个是直接后继。
特点:可以从表中任意结点出发,遍历整个链表。
循环链表:在单向链表(或双向链表)的基础上,令表尾结点的指针指向链表的第一个结点,构成循环链表。
特点:可以从任何节点开始遍历整个链表。
静态链表:借助数组来描述线性表的链式存储结构,用数组元素的下标来表示元素的所在节点指针。
线性表的基本操作:插入、删除、查找;
栈与队列
逻辑结构和线性表相同;
特点:运算有所限制;
栈:先进性出 LIFO
队列:先进后出 FIFO
栈:
只能通过访问一端来实现数据存储和检索的一种线性数据结构。
栈顶:进行插入和删除的一端
栈底:与栈顶对应的另一端
空栈:不含数据元素的栈
栈的运算:
初始化栈InitStack(S) :创建一个空栈
判断空栈 isEmpty(S):当栈为空时,返回“真”,否则返回“假”
入栈Push(S,x):将元素x加入栈顶,并更新栈顶指针。
出栈Pop(S):将栈顶的元素从栈中删除,并更新栈顶的指针。
若需要得到栈顶的值,可将Pop(S)定义为一个返回栈顶元素值的函数。
Top(S):返回栈顶元素的值,但不修改栈顶指针。
疑问:
栈的元素个数是由栈顶指针定义的吗?栈内的元素类型是怎么规范定义的?
初始化栈,会定义栈的空间大小。
栈的存储结构:
顺序存储
一组地址连续的存储的存储单元 依次存储自栈顶到栈底的数据元素。
设指针top指示栈顶元素的位置。
需要预定义(或申请)栈的存储空间,
链式存储(链栈)
用链表作为存储结构的栈
不用设置链表的头指针,栈顶指针就是链表头指针。
栈的应用
典型应用:表达式求值、括号匹配等;
在计算机语言的实现,也会用到栈。
将递归过程转变为非递归过程的处理,栈也会有重要应用。
队列:
一种先进先出的线性表;
先进先出 First In First Out;
特性:
只允许在一端插入元素,在另一端删除元素。
允许插入元素的一端,称之为队尾(Rear)。
允许删除元素的一端,称之为对头(Front)。
对头和队尾各有一个指针。( 队头指针front 、队尾指针rear;)
队列的基本运算:
初始化队列InitQueue(Q):创建一个空的队列Q;
判断空isEmpty(Q):当队列为空时,返回“真”,否则返回“假”。
入队 EnQueue(Q,x):将元素x加入到队列Q的队尾,并更新队尾指针。
出队DelQueue(Q):将对头元素从队列Q中删除,并更新对头指针。
读队头元素FrontQue(Q) : 返回队头元素的值,但不更新对头的指针。
队列的存储结构:
顺序存储结构:(同其他顺序存储数据结构)
为了降低运算的复杂度,元素入队只修改队尾指针;元素出队只修改对头指针。
但由于顺序队列的存储空间容量是提前设定的,所以队尾指针会有一个上限值,当队尾指针达到该上限时,就不能只修改队尾指针来增加元素了。
环形队列:(通过整除取余运算来实现。)
可维持入队出队的操作运算的简单性。
根据队列操作的定义,Q.rear == Q.front 时,即对头指针和队尾指针相等是,可能队列为空,也可能队列为满,显然这种情况会有问题。
两种结局办法:1、设置一个标志位,当出现上述情况时,代表队列为空还是为满;
2、牺牲一个存储单元,约定:队列的尾指针所指的位置的下一个位置是队头指针时 表示队列满。(由此想到的一个问题,若队列长度为1,那么这个队列是不是永远不能插入元素?)
队列的链式存储:(链队列)
为了便于操作,给链队列添加了一个头结点,并令头指针指向头结点。
队列为空的判断条件是:头指针和尾指针的值相同,并均指向头结点。
队列的应用:
常用于处理需要排队的场合,例如打印任务、离散事件的计算机模拟等
串:
字符串,是一种特殊的线性表;
其元素为字符。
运算时,常把一个串视作一个整体来处理。
串,仅由字符构成的优先序列;
串的几个概念:
空串:长度为0的串;
空格串:由一个或多个空格组成的串。
子串:由串中任意长度的连续字符构成的序列
主串:含有子串的串
子串在主串的位置,是指 子串首次出现时,子串的第一个字符在主串中的位置。
空串是任意串的子串。
串相等:两个串的长度相等,且对应的序号的字符也相等;
串比较:比较操作从两个串的第一个字符开始进行,字符的码值大者所在的串为大,比较字符的ASCII码值;
串的基本操作:
赋值操作 StrAssign(s,t) : 将串s 的值赋给串t 。
连接操作Concat(s,t):j将串 t 接续在串s 的尾部,形成一个新的串;
求串长StrLength(s):返回串s 的长度;
串比较StrCompare(s,t):比较两个串的大小。
返回值 -1,0,1 分别表示 s < t 、s = t 、 s > t ;
求子串SubString(s,strat,len): 返回串s 中 从start 开始的 、长度为len 的字符序列。
串的存储结构:
顺序存储结构:一组地址连续的存储单元 ,来存储串值的字符序列;
由于串都是由字符构成,可通过程序语言提供的 字符数组 定义串的存储空间,
也可以根据串长的需要动态申请字符串的空间。
链式存储结构:用链表存储串中的字符。
每个结点中可以存储一个字符,也可以存储多个字符。
考虑到存储密度问题,节点大小的选择会直接影响到串的处理效率。
串的模式匹配:子串的定位操作
子串 也被称之为 模式串;
朴素的模式匹配算法: 布鲁特--福斯算法;
改进的模式匹配算法: KMP算法;
数组、矩阵和广义表:
数组、广义表 可看作是线性表的推广。
数据元素仍然是一个表;
数组:定长线性表在维数上的扩展。
线性表中的元素又是一个线性表。
n维数组是一种“同构”的数据结构,其每个数据元素类型相同、结构一致。
数组结构的特点:
元素数目固定,一个被定义的数组,其元素个数固定。
元素具有相同的类型。
下标关系具有上下界的约束,且下标有序;
数组的基本运算:
给定一个下标,存取相应的数据元素。
给定一个下标,修改相应的数据元素中某个数据项的值。
数组的顺序存储:
数组一般不做插入和删除运算,一旦定义了数组,则结构中的数据元素个数和元素之间的关系就不再发生变动,因此数组适合采用顺序存储结构。
矩阵:
数据结构中,主要关注的是:如何在节省存储空间的情况下使矩阵的各种运算能高效地进行。
压缩存储:在一些矩阵中,存在很多值相同的元素或者0元素,为了节省存储空间,可以对这类矩阵进行压缩存储。
多个值相同的元素只分配一个存储单元,0不分配存储单元。
特殊矩阵:值相同的元素(或0元素)在矩阵中的分布有一定的规律
稀疏矩阵:与特殊矩阵相对,无规律分布;
广义表:线性表的推广;
广义表和线性表的区别:
广义表的元素既可以是单元素、也可以是有结构的表。
线性表的元素都是结构上不可分的单元素。
广义表一般记为:

广义表的属性:
广义表的长度:广义表中元素的个数
广义表的深度:广义表展开后所含的括号的最大层数。
广义表的基本操作:
取表头:head(LS) :非空广义表的第一个元素就是表头;
取表尾:tail(LS) :非空广义表中,除表头以外,其他元素所构成的表称为表尾。
广义表的特点:
广义表可以是多层次的结构
广义表的元素是子表,子表的元素还可以是子表;
广义表中的元素可以是已经定义的广义表名字。
即:一个广义表可被其他广义表所共享;
广义表可以是一个递归的表
即:广义表中的元素也可以是本广义表的名字;
广义表的存储结构:
一种带有层次的非线性结构。
由于广义表中的元素本身又可以具有结构,因此难以用顺序存储结构表示。
通常广义表的存储结构采用链式存储结构。
广义表的特性:
若广义表不空,可以分解为表头和表尾两部分(表尾就是除表头的其他部分)
树与二叉树
树:
非常重要的非线性数据结构
一个数据元素可以有两个或两个以上的直接后继元素
可以用来描述客观世界中广泛存在的层次结构关系。
树:n个节点的有限集合,n ≥0;
空树:当节点数n= 0 时
任一个非空树(n> 0 ),有且仅有一个根节点。


树的定义是递归的;这也表明了树本身的固有特性:
一棵树由若干个子树构成,而子树有由更小的子树构成。
树的元素之间有严格的层次关系;
树上的任意一个结点,最多只和上层的一个结点有直接关系;
树上的任意一个结点,可能与下层的0个、一个或多个结点有直接关系。
树的基本概念:
双亲:对于结点的子树的根而言,结点是其双亲
孩子:结点的子树的根
兄弟:具有双亲的结点互为兄弟;
结点的度:一个结点的子树的个数
叶子结点:又被称为终端结点,度数为0的结点 。
内部结点:度数不为0 的非根的结点;叶子结点、根节点、内部结点共同组成一棵树;
结点的层次:根在第一层,根的孩子在第二层;以此类推。。。
树的高度:一棵树的最大层数记为树的高度(或称为深度。)
有序树:一棵树的所有子树是从左到右的,相互之间不能交换
无序树:一棵树的所有子树是从左到右的,相互之间可以交换
树和二叉树的区别:
二叉树中,节点的子树需要区分左子树、右子树,即使在结点只有一颗子树的时候。
二叉树结点最大度为2。(树的度不受限制。)
二叉树:
二叉树:n个节点的有限集合,它或者是空树(n=0),或者是一个根节点及两颗不想交的且分别称为左、右子树的二叉树所组成。
二叉树的定义同样具有递归性。
二叉树和树有许多联系,但两者是不同的概念。
二叉树的性质:
二叉树的第i层(i≥1) 上最多有2^(i-1) 个结点。
高度为k 的二叉树最多有2^k -1 个结点(k ≥1)
对于任何一颗二叉树,终端结点数n0 = 度数为2的节点数 n2 + 1; 即n0 = n2 + 1
具有n个结点的完全二叉树的深度为( [log 2 (n)] + 1); (需要对log2(n)取整 )
完全二叉树:
除了二叉树最后一层可以由空结点,其他层不应该有空结点;
对完全二叉树的所有结点进行编号,由上至下,由左至右,中间不应该有空结点
深度为k 的完全二叉树,,除了第K层外,其余各层含有最大的结点数。即每层的结点数都是有上限的,每层的结点数是上一层结点数的二倍。
满二叉树:深度为k 的二叉树,有2^k -1 个结点

单支树:深度为k,且只有k个结点的二叉树。
二叉树的存储结构
二叉树的顺序存储结构
必须将结点排列成一个适当 的线性序列。
参考完全二叉树的定义,将二叉树的每个节点进行编号,自上而下,自左向右,根据这个关系,就可以看成是一个线性表;
使用一组地址连续的存储单元存储二叉树中的结点。
对于二叉树的顺序存储结构,可以根据每个结点的编号,来推断其双亲节点的编号和左孩子、右孩子的编号。
假设结点 的编号为i;
双亲编号的计算:若i == 1,则节点i 为此二叉树的根。i节点无双亲;
若i > 1 ,则节点i 的双亲节点为 [i/2] (取整)
左孩子编号的计算:
若2i ≤ n ,则该节点的左孩子编号为2i,否则无左孩子;(n 为二叉树的结点个数)
右孩子编号的计算:
若2i + 1 ≤ n,则该结点的右孩子的编号为2i + 1, 否则无右孩子;(n 为二叉树的结点个数。)
特性:
完全二叉树采用顺序存储,既简单,也省空间。
一般的二叉树,不宜采用顺序存储结构,因为一般二叉树可能中间会存在大量的空结点,导致存储资源的大量浪费。
最坏的情况下,一般的二叉树,深度为k,且只有k个结点的二叉树(单支树)需要2^k -1 个存储单元。
二叉树的链式存储结构:
二叉树的结点包含有以下信息:数据元素、双亲、左子树的根、右子树的根 等信息,
可以使用三叉链表或二叉链表 来存储二叉树,链表的头指针指向二叉树的根结点。
即一个结点含有3个指针或两个指针。
两个指针,则每个指针分别指向自己的左子树的根 和 右子树的根 ,完全自上而下的,不可从下往上查询。
n个结点的二叉树,采用二叉链表做存储结构,必然会有n+1个空指针域。
三个指针,则两个指针分别指向自己的左子树的根 和 右子树的根,另外一个指针指向自己的双亲。
二叉树的链式存储结构存在一个缺陷:
只能找到一个结点的左、右孩子,不能直接得到结点在任一遍历序列中的前驱和后继,这些信息只能在遍历动态过程中才能得到。
可以引入线索二叉树来保存这些动态过程得到的信息。
对于二叉树,应该选择什么样的存储结构呢?
不同的存储结构中,实现二叉树的运算方法也不同
具体应采用什么存储结构,除了需要考虑二叉树的形态(如是否是完全二叉树。)外,还应考虑需要进行的运算特点。
二叉树的遍历:
即按照某种策略,访问树中的每个结点,且仅访问一次的过程。
遍历二叉树的实质,是按照某种规律规则,将树中的结点排成一个线性的序列的过程。
即对一个非线性结构进行线性化的过程。
序列有且仅有一个起始点和终结点,其余结点有且仅有唯一的直接前驱和直接后继。
二叉树的遍历的方法有:
1、层序遍历:先访问根结点,然后访问第一层所有结点,然后访问第二层所有结点。。。自上而下,自左向右;
2、先序遍历、中序遍历、后序遍历 三种方法:
思想:一颗非空的二叉树是由根结点、左子树、右子树三部分构成的,因此若能依次遍历这三部分,也就遍历了整颗二叉树。
约定:先遍历左子树,后遍历右子树
根据访问根结点的位置不同,区分为先序遍历、中序遍历、后序遍历;

遍历方法:从根结点出发,逆时针沿着二叉树的外缘移动,对每个节点均途径3次(叶子结点或部分只有一个孩子结点的结点,也是途径3次),
第一次经过结点的时候进行访问,则是先序遍历;
第二次经过结点的时候进行访问,则是中序遍历;
第三次经过结点的时候进行访问,则是后序遍历;
三种遍历过程的路线都是完全相同的。
这三种遍历算法的时间复杂度都是O(n) ,其中n为二叉树所包含的结点个数。
在遍历过程中,每进行一次递归调用,都是将函数的“活动记录” 亚入栈中,因此,栈的最大长度恰为树的高度。
最坏的情况下,二叉树是有n个结点,且高度为n的单支树,遍历算法的空间复杂度也为O(n).
借助栈,可以将二叉树的递归遍历算法转换为非递归算法:
线索二叉树:
二叉树的链式存储结构存在一个缺陷:
只能找到一个结点的左、右孩子,不能直接得到结点在任一遍历序列中的前驱和后继,这些信息只能在遍历动态过程中才能得到。
可以引入线索二叉树来保存这些动态过程得到的信息。
为了保存结点在任一序列中的前驱和后继信息,可以在二叉链表的基础上,再针对每个节点,加两个指针,分别存储直接前驱和直接后继的指针。
因为:n个结点的二叉树,采用二叉链表做存储结构,必然会有n+1个空指针域。所以,可以使用这些空指针域来存储 上述每个节点要添加的两个指针域。
线索链表:

每个节点含有两个标志位和两个指针域,以及一个数据元素;指针域的存放信息由标志位来定义;
线索:指向结点前驱和后继的指针;
线索二叉树:加上线索的二叉树;
线索化:对二叉树以某种次序遍历使其成为线索二叉树的过程
遍历过程中,用线索取代空指针
中序线索二叉树及其数据结构:

【关于最优二叉树、哈夫曼编码,没有做深入了解、研究,后续若遇到类似的问题,再深入学习吧;】
最优二叉树:【又称之为 哈夫曼树】
一类带权路径长度最短的树。
路径:从树中的一个结点到另一个结点之间的道路。
路径长度:路径上 的分支数目。
树的路径长度 是从树根到每个叶子之间的路径长度之和。
结点的带权路径长度 为从该结点到树根之间的路径长度与该结点权值的乘积
哈夫曼编码:
等长编码:对每个字符编制相同长度的二进制码
缺点:码串长度过长,通信效率不高。
前缀码(不等长编码):任一字符的编码都不是另一个字符的编码的前缀。
树和森林:
树的存储结构:
双亲表示法:在每个结点中附设一个指示器,指出其双亲结点在存储结构中的位置(即结点所在数组元素的下标)
根结点的双亲位置是-1;
优点:对于查找结点的双亲,非常方便
缺陷:对于查找结点的孩子及后代,需要遍历整个树。

孩子表示法:在存储结构中,用指针指示出结点的每个孩子;
为树中每个结点的孩子建立一个链表。
令每个结点的所有孩子结点构成一个用单链表表示的线性表。
n个结点的树具有n个单链表。
将这n个单链表的头指针又排成一个线性表。
优点:对于查找结点的子孙(不限于结点的孩子),非常方便
缺陷:对于查找结点的双亲,需要遍历整个树。

树的双亲孩子表示法:
双亲表示法和孩子表示法的结合:

孩子兄弟表示法
又称为 二叉链表表示法
在每个结点中分别设置两个指针,指向该节点的第一个孩子和下一个兄弟。
优势:便于实现 树、森林、二叉树 之间的转换;
充分利用二叉树的有关算法来实现树及森林的操作,对难以把握规律的树和森林有着重要现实意义。

树和森林的遍历
树 的遍历:
先根遍历:等同于二叉树的先序遍历;先访问树的根结点,然后依次先根遍历根的各棵子树。
后根遍历:等同于二叉树的中序遍历;先依次后根遍历树根的各棵子树,然后访问树根结点。
森林的遍历:
按照森林和树的相互递归定义,可得到的森林的两种遍历方法:
先序遍历森林:
若森林非空,首先访问森林中第一棵树的根结点,
然后先序遍历第一棵树根结点的子树森林,
最后先序遍历除第一棵树之外的剩余的树所构成的森林。
中序遍历森林:
若森林非空,首先中序遍历森林中第一棵树的子树森林
然后访问第一棵树的根结点
最后中序遍历除第一棵树之外的剩余的树所构成的森林。
树、森林、二叉树相互之间的转换:
任何一个森林、树都可以对应表示为一颗二叉树
利用树的孩子兄弟表示法可导出树与二叉树的对应关系
一棵树可以转换成唯一的一棵二叉树。
由于树根没有兄弟,所以树转换为二叉树后,二叉树的根一定没有右子树。
将一个森林转换为一个二叉树的方法;
先将森林中的每一棵树转换为二叉树,
再将第一棵树的根作为转换后的二叉树的根
第一棵树的左子树作为转换后的二叉树的左子树
第二棵树作为转换后的二叉树的右子树,
第三棵树作为转换后二叉树根的右子树的右子树
以此类推。
任何一棵二叉树也能对应到唯一的一个森林或一棵树。
如下图:
树转换为二叉树:

森林转换为二叉树:

二叉树转换为树或森林:

图
图是比树结构更复杂的一种数据结构;
在线性结构中,除了首结点没有直接前驱、末尾结点没有直接后继意外,其他结点都有唯一的一个直接前驱和直接后继。
在树结构中(非线性结构),除了根结点没有直接前驱外,其他结点都有唯一一个直接前驱和多个直接后继;
在图中(非线性结构),任意两个结点之间都可能有直接的关系,一个结点的直接前驱和直接后继将不会受到限制。
图的定义 和一些概念:
图G是由集合V 和E 构成的二元组,记作G=(V,E);
V是图中顶点的非空有限集合
E是图中边的有限集合。
图中任意一个顶点都有可能与图中其他顶点有关系。
顶点:图中的数据元素
边:数据元素之间的关系
有向图:每条边都是有方向的。
无向图:每条边都是无方向的。
完全图:无向图中,每个顶点都与其他剩下的各个顶点有关系
若一个无向图具有n个顶点,每个顶点与其他n-1个顶点之间都有边。
度:顶点v 的度,是指关联于该顶点的边的数目。
出度:以该顶点为起点的有向边的数量。
入度:以该顶点为终点的有向边的数量
度 == 出度 + 入度
路径:顶点vp到顶点vq的路径的一组序列;
对于无向图,路径无方向
对于有向图,路径是有方向的
路径的长度:路径上的边或狐的数量;
回路(环):第一个顶点和最后一个顶点相同的路径
简单路径:一条路径上,除了vp 和vq两个顶点可以相同外,其他的顶点均不相同
子图:对于G= (V,E ),G1 = (V1,E1);若V1 ∈ V且E1∈E,则称G1是G的子图;
连通:顶点Vi 到顶点Vj 有路径,则称顶点Vi和顶点Vj是连通的。
连通图:无向图G中任意两个顶点都是连通的;
连通分量:无向图中的极大连通子图;
强连通图:有向图中,属于V集合的每一对顶点Vi 和Vj ,且Vi ≠ Vj ;若Vi 到Vj 存在路径,Vj到Vi存在路径,则图G为强连通图。
强连通分量:有向图中的极大连通子图 称为 有向图的强连通分量;
网: 边(或狐)带有权值的图
有向树:如果有一个有向图,恰好有一个顶点的入度为0,其余顶点的入度均为1;
全序关系:无法将图中的顶点排列成一个线性序列
图的特性:图的顶点之间不存在全序关系,
任何一个顶点都可被看成第一个顶点
任一顶点的邻接点之间不存在次序关系。
为了便于运算,可以给图中的每个顶点赋予一个序号值。
图的存储结构:
邻接矩阵表示法:用一个矩阵来表示图中顶点之间的关系。
对于具有n个顶点的图G = (V,E),其邻接矩阵是一个n阶方阵,且满足:

由定义可了解,无向图的矩阵是对称的,而有向图的矩阵未必对称。
可根据矩阵求出任意两个顶点是否有边或狐相连。
可根据矩阵求出各个顶点的度。(需要区分有向图和无向图)
网(赋权图)的邻接矩阵可定义为:

Wij 是 边或弧的权值。
邻接链表表示法:为图中的每个顶点建立一个单链表。
第i个单链表中的结点表示依附于顶点 Vi 的边(对于有向图是以Vi为尾的弧)
邻接链表中的结点有 表结点 (或边结点) 和表头结点 两种类型,

表头结点通常以顺序形式存储。
图的遍历:
图的遍历,是指,从某个顶点出发,沿着某条搜索路径对图中的所有顶点进行访问且只访问一次的过程。
图的遍历算法,是求解 图的连通性问题、拓扑排序、求关键路径等算法 的基础。
深度优先搜索 Depth First Search ,DFS
类似于树 的先根遍历,第一次经过顶点的时候就访问顶点。
1.设置搜索指针p ,使p 指向顶点v。
2.访问p所指顶点,并使p指向与其相邻接的尚未被访问过的顶点。
3.若p所指顶点存在,则重复步骤2,否则执行步骤4;
4.沿着刚刚访问的次序和方向回溯到一个尚有邻接顶点且未访问过的顶点,并使p指向这个未被访问的顶点,然后重复步骤2 。直到所有顶点都被访问过为止。
深度优先遍历图的过程,实质上是对某个顶点查找其邻接点的过程,
其所耗费的时间取决于所采用的存储结构:
邻接矩阵存储方式:遍历所有顶点的时间是O(n²)
邻接表存储方式:遍历所有的顶点的邻接点的时间是O(e),遍历图的时间复杂度是O(n+e)
广度优先搜索 Breadth First Search ,BFS
从图中的某个顶点v出发,开始访问
在访问了v之后,依次访问v的各个未被访问的邻接点
然后分别从这些邻接点出发依次访问它们的邻接点;(并使“先被访问的顶点的邻接点” 先于 “后被访问的顶点的邻接点” 被访问)
图中所有已被访问的顶点的邻接点都被访问到,若还有未被访问的顶点
另选一个未被访问的顶点作为起点,重复上述过程。
特点:尽可能横向搜索
最先访问的顶点的邻接点也先被访问
广度优先搜索,会引入队列来保存已访问过 的顶点序列。
生成树及最小生成树
图的极小连通图: 对于有n个顶点的连通图,至少有n-1条边,而生成树中恰好有n-1条边,此时连通图的生成树就是该图的极小连通图。
在图的生成树中任意两个顶点上加一条边,必然形成回路。
图的生成树不是唯一的。
不同的出发顶点、不同的算法、不同的存储方式,锁得到的生成树都是不同的。
最小生成树是针对连通网而言的;
连通网,边是带权值的;
生成树的权:生成树各边的权值总和
最小生成树:权值最小的生成树。
求解最小生成树的方法:
普利姆算法(Prim)
(不再详细深入研究,若后续学习中有涉及到此部分知识的,可以参考P142)
克鲁斯卡尔算法(Kruskal):
(不再详细深入研究,若后续学习中有涉及到此部分知识的,可以参考P142)
拓扑排序和关键路径
AOV网:以有向图的顶点表示活动,有向边表示活动之间的优先关系,称这样的有向图为 “以顶点表示是活动的网”(即AOV网)
Activity On Vertex network
AOV网中不应该存在有向环;
有向无环图(DAG ) : 不存在回路的有向图; (Directed Acycline Graph )
如何确保一个有向图为有向无环图()DAG?
对 有向图 构造其顶点的拓扑有序序列,若图中所有顶点都在拓扑有序序列中,则AOV网就不存在环。
拓扑排序及其算法:
拓扑排序:将AOV网的所有顶点排成一个线性序列的过程。 并且该序列满足:
若在AOV 网中,从顶点Vi 到Vj 有一条路径,着在该线性序列中,顶点Vi 必然在顶点Vj之前。
对AOV网进行拓扑排序的方法:
1、在AOV网中选择一个入度为0,(没有前驱),的顶点,且输出它。
2、从网中删除该顶点及与该顶点有关的所有弧。
3、重复上述两步,直到网中不存在入度为0 的顶点为止。
(只要AOV网中存在环路,那么拓扑排序到最后肯定能够将这个环路找出来,最后删除顶点操作将会无法进行下去的。)
AOE网(一种带权有向图中用边表示活动的网):(可以简单理解为AOV网的路径上加上一个权值。)
定义:
带权有向图G中
以顶点表示事件
以有向边表示活动
以边上的权值表示是该活动需要持续的时间。
关键路径和关键活动
从源点到汇点的路径中,最长的路径被称之为关键路径;
关键路径所有的活动均是关键活动。
(不再详细深入了解,后续遇到相关知识点,再进一步学习。参考P145)
最短路径:
涉及到迪杰斯特拉算法。 Dijkstra
单源点的最短路径:考虑的是某单个源点到图中其余各顶点的最短路径。
每对顶点的最短路径:若有n个顶点,就执行n次迪杰斯特拉算法。
(不再详细深入了解,后续遇到相关知识点,再进一步学习。参考P146)
查找
查找是一种常用的基本运算;
查找表:由同一类型的数据元素(或记录)构成的集合。(既然是集合,就应该符合集合的特性?)
由于 集合 中的数据是完全松散的关系,因此查找表是一种非常灵活的数据结构;
静态查找表:
查询某个特定的数据元素是否在查找表中;
检索某个特定的数据元素的各种属性。
动态查找表:
在查找表中插入一个数据元素(原查找表不存在的元素),
在查找表中删除一个数据元素(原查找表已经存在的元素)。
关键字:数据元素某个数据项的值,用来识别(标识)数据元素的
主关键字:能够唯一标识一个数据元素的关键字;
次关键字:能够标识多个数据元素的关键字。
针对使用特定值,在查找表中查找时,本质上是在找这个特定值是否与某个关键字相等;
若存在这个关键字,则查找成功,给出相应的信息
若不存在这个关键字,则查找不成功,返回“空”记录,或空指针。
平均查找长度:给定关键字值 进行比较的次数的期望值 称为 查找算法在查找成功时的平均查找长度。
静态查找表的查找方法:
顺序查找:从查找表的一端,逐一查询
顺序查找的方法对于顺序存储方式和链式存储防水的查找表都适用。
折半查找:
对于将查找表中的元素存储在一个一维数组中,设定数组中的数据元素是根据关键字,按照一定规律(如从小到大)排序的。
分块查找;
又称索引顺序查找,是对顺序查找方法的一种改进;
查找效率介于顺序查找和折半查找之间。
动态查找表:
表结构本身是在查找过程中动态生成的。
二叉排序树
又称二叉查找树
可能为一颗空树
也可能具有以下性质的二叉树:
若它的左子树非空,则左子树上的所有结点的值均小于根结点的值
若它的右子树非空,则右子树上所有结点的值均大于根结点的值。
左、右子树本身是二叉排序树
二叉排序树的查找过程:
二叉排序树的 查找、插入、删除 操作过程 参考书 P155;
平衡二叉树:
又称AVL树
可能为一颗空树
也可能具有以下性质的二叉树:
左子树和右子树都是平衡二叉树,且左子树和右子树的高度之差的绝对值不超过1;
若将 二叉树结点的平衡因子 (BF) 定义为 该结点 的左子树的高度减去右子树的高度,则平衡二叉树上所有结点的平衡因子只能是-1,0,1
只要树上有一个结点的平衡因子的绝对值大于1,则二叉树就不平衡了。
平衡二叉树的插入操作:
LL型 单向右旋平衡处理:
RR型 单向左旋平衡处理:
LR型 先左后右双向旋转平衡处理
RL型 先右后左双向旋转平衡处理
平衡二叉树的删除操作:
B_树:
一棵m阶的B_树,或为空树,或为满足以下特性的m叉树:
树中每个结点最多有m棵子树。
若根结点不是叶子结点,则最少有两颗子树。
除根之外的所有非终端结点最少有[ m/2] 棵子树 (注意取整)
所有的非终端结点中包含下列数据信息

所有的叶子结点都出现在同一层次上,并且不带信息
B_树的应用???
哈希表
哈希函数:以记录的关键字为自变量的函数
哈希表的查找原理:通过计算一个哈希函数来得到该记录的存储地址。
哈希函数需要设定
哈希函数产生的冲突需要处理;
对于哈希表,需要考虑的两个问题:
1、如何构造哈希函数
2、如何解决冲突。
哈希函数的构造方法:
直接定址法
数字分析法
平方取中法
折叠法
随机数法
除留余数法
哈希函数的构造应该解决好两个主要问题:
1、哈希函数应是一个压缩映像函数,它应具较大的压缩性,以节省存储空间;
2、哈希函数应具有较好的散列性,虽然冲突是不可避免的,但应尽量减少。
构造哈希函数是,尽可能让关键字的所有部分都能起作用。
处理冲突的方法:
开放定址法
链地址法
再哈希法
建立一个公共溢出区
哈希表的查找
关于哈希函数的构造等知识点,前两章已经介绍过一次,哈希表等知识点,参考P162-165
排序
分类:
内部排序:只在内存中进行;
外部排序:数据量很大,内存不能完全容纳,需要对外存访问;
简单排序:
直接插入排序
冒泡排序
简单选择排序
希尔排序
快速排序
堆排序
归并排序
基数排序
内部排序方法小结:

外部排序:对大型文件进行排序
常见的:归并排序
归并排序的处理过程有两个阶段:
1、将大文件分段读入内存,使用一种内部排序算法进行排序,输出到外存另一个文件中;
2、将第一段产生的各个有序段,逐一归并。
多路平衡归并法(常用的一种归并方法):(使用二叉树方面的知识点。)
/* 第三章从二叉树,往后的知识点,部分知识点比较繁杂,没有逐一阅读、理解,待后续遇到此类知识点后再往回查阅资料吧。*/
本章节,对于算法的时间复杂度和空间复杂度的理解还有所欠缺。
————————(我是分割线)————————
参考:
1. 《软件设计师教程》(第五版,清华大学出版社,禇华、霍秋艳 主编)
2. 赛迪网校 :www.ccidedu.com(教学视频资源)
3. 美河学习在线团购组:www.eimhe.com (教学视频来源)
4.
备注:
初次编辑时间:2019年10月30日15:31:32
第一次修改时间:2019年11月2日17:50:31 /添加后续部分小节内容。
环境:Windows 7 / Python 3.7.2

浙公网安备 33010602011771号