概念总结
第一章 概论
1. 数据结构描述的是按照一定逻辑关系组织起来的待处理数据元素的表示及相关操作,涉及数据的逻辑结构、存储结构和运算
2. 数据的逻辑结构是从具体问题抽象出来的数学模型,反映了事物的组成结构及事物之间的逻辑关系
可以用一组数据(结点集合K )以及这些数据之间的 一组二元关系(关系集合R )来表示:(K, R)
结点集K 是由有限个结点组成的集合,每一个结点代表一个数据或一组有明确结构的数据 关系集R 是定义在集合K 上的一组关系,其中每个关系r (r ∈R )都是K ×K 上的二元关系 3. 数据类型 a. 基本数据类型 整数类型(integer)、实数类型(real)、布尔类型(boolean)、字符类型(char )、指针类型(pointer ) b. 复合数据类型
复合类型是由基本数据类型组合而成的数据类型;复合数据类型本身,又可参与定义结构更为复杂的结点类型
4. 数据结构的分类:线性结构(一对一)、树型结构(一对多)、图结构(多对多) 5. 四种基本存储映射方法:顺序、链接、索引、散列 6. 算法的特性:通用性、有效性、确定性、有穷性
7. 算法分析:目的是从解决同一个问题的不同算法中选择比较适合的一种,或者对原始算法进行改造、加工、使其优化 8. 渐进算法分析
a .大Ο分析法:上限,表明最坏情况 b .Ω分析法:下限,表明最好情况
c .Θ分析法:当上限和下限相同时,表明平均情况
第二章 线性表
1. 线性结构的基本特征
a. 集合中必存在唯一的一个“第一元素” b. 集合中必存在唯一的一个“最后元素” c. 除最后元素之外,均有唯一的后继 d. 除第一元素之外,均有唯一的前驱 2. 线性结构的基本特点:均匀性、有序性 3. 顺序表
a. 主要特性:元素的类型相同;元素顺序地存储在连续存储空间中,每一个元素唯一的索引值;使用常数作为向量长度
b. 线性表中任意元素的存储位置:Loc(ki) = Loc(k0) + i * L(设每个元素需占用L 个存储单元)
c. 线性表的优缺点:
优点:逻辑结构与存储结构一致;属于随机存取方式,即查找每个元素所花时间基本一样 缺点:空间难以扩充 d. 检索:ASL=【Ο(1)】
e. 插入:插入前检查是否满了,插入时插入处后的表需要复制【Ο(n )】 f. 删除:删除前检查是否是空的,删除时直接覆盖就行了【Ο(n )】
4. 链表 4.1单链表
a. 特点:逻辑顺序与物理顺序有可能不一致;属于顺序存取的存储结构,即存取每个数据元素所花费的时间不相等
b. 带头结点的怎么判定空表:head 和tail 指向单链表的头结点 c. 链表的插入(q->next=p->next; p->next=q;)【Ο(n )】 d. 链表的删除(q=p->next;p->next = q->next;delete q;)【Ο(n )】 e. 不足:next 仅指向后继,不能有效找到前驱 4.2双链表
a. 增加前驱指针,弥补单链表的不足
b. 带头结点的怎么判定空表:head和tail 指向单链表的头结点 c. 插入:(q->next = p->next; q->prev = p; p->next = q; q->next->prev = q;) d. 删除:(p->prev->next = p->next;p->next->prev = p->prev; p->prev = p->next = NULL; delete p;) 4.3顺序表和链表的比较 4.3.1主要优点
a. 顺序表的主要优点
没用使用指针,不用花费附加开销;线性表元素的读访问非常简洁便利 b. 链表的主要优点
无需事先了解线性表的长度;允许线性表的长度有很大变化;能够适应经常插入删除内部元素的情况
4.3.2应用场合的选择 a. 不宜使用顺序表的场合
经常插入删除时,不宜使用顺序表;线性表的最大长度也是一个重要因素 b. 不宜使用链表的场合
当不经常插入删除时,不应选择链表;当指针的存储开销与整个结点内容所占空间相 比其比例较大时,应该慎重选择
第三章 栈与队列
1. 栈
a. 栈是一种限定仅在一端进行插入和删除操作的线性表;其特点后进先出;插入:入栈(压栈);删除:出栈(退栈);插入、删除一端被称为栈顶(浮动),另一端称为栈底(固定);实现分为顺序栈和链式栈两种 b. 应用: 1)数制转换 while (N) {
N%8入栈; N=N/8;
}
while (栈非空){
出栈; 输出;}
2)括号匹配检验
不匹配情况:各类括号数量不同;嵌套关系不正确 算法:
逐一处理表达式中的每个字符ch :
ch=非括号:不做任何处理 ch=左括号:入栈
ch=右括号:if (栈空) return false
else {
出栈,检查匹配情况, if (不匹配) return false }
如果结束后,栈非空,返回false 3)表达式求值 3.1中缀表达式:
计算规则:先括号内,再括号外;同层按照优先级,即先乘*、除/,后加+、减-;相同优先级依据结合律,左结合律即为先左后右 3.2后缀表达式:
::= + | -|
::= * |/|
::= • ::= | ∷= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 3.3中缀表达式转换为后缀表达式
InfixExp 为中缀表达式,PostfixExp 为后缀表达式
初始化操作数栈OP ,运算符栈OPND ;OPND.push('#'); 读取InfixExp 表达式的一项
操作数:直接输出到PostfixExp 中; 操作符: 当‘(’:入OPND; 当‘)’:OPND 此时若空,则出错;OPND 若非空,栈中元 素依次弹出,输入PostfixExpz 重
(’为止;若 为‘(’,弹出即可 复中,直到遇到‘
当‘四则运算符’:循环(当栈非空且栈顶不是‘(’&& 当前运算符优先级>栈顶运算符优先级),反复弹出栈顶运 算符并输入到PostfixExp 中,再将当前运算符压入栈 3.4后缀表达式求值 初始化操作数栈OP ;
while (表达式没有处理完) { item = 读取表达式一项; 操作数:入栈OP ;
运算符:退出两个操作数,
计算,并将结果入栈}
c. 递归使用的场合:定义是递归的;数据结构是递归的;解决问题的方法是递归的
2. 队列
a. 若线性表的插入操作在一端进行,删除操作在另一端进行,则称此线性表为队列 b. 循环队列判断队满对空:
队空:front==rear;队满:(rear+1)%n==front
第五章 二叉树
1. 概念
a. 一个结点的子树的个数称为度数
b. 二叉树的高度定义为二叉树中层数最大的叶结点的层数加1 c. 二叉树的深度定义为二叉树中层数最大的叶结点的层数
d. 如果一棵二叉树的任何结点,或者是树叶,或者恰有两棵非空子树,则此二叉树称作满二叉树
e. 如果一颗二叉树最多只有最下面的两层结点度数可以小于2;最下面一层的结点都集中在该层最左边的位置上,则称此二叉树为完全二叉树
f. 当二叉树里出现空的子树时,就增加新的、特殊的结点——空树叶组成扩充二叉树,扩充二叉树是满二叉树
外部路径长度E :从扩充的二叉树的根到每个外部结点(新增的空树叶)的路径长度之和 内部路径长度I :扩充的二叉树中从根到每个内部结点(原来二叉树结点)的路径长度之和 2. 性质
a. 二叉树的第i 层(根为第0层,i ≥0)最多有2^i个结点 b. 深度为k 的二叉树至多有2k+1-1个结点
c. 任何一颗二叉树,度为0的结点比度为2的结点多一个。n0 = n2 + 1 d. 满二叉树定理:非空满二叉树树叶数等于其分支结点数加1
e. 满二叉树定理推论:一个非空二叉树的空子树(指针) 数目等于其结点数加1 f. 有n 个结点(n>0)的完全二叉树的高度为⌈log2(n+1)⌉,深度为⌈log2(n+1)⌉−1 g. 对于具有n 个结点的完全二叉树,结点按层次由左到右编号,则有: 1) 如果i = 0为根结点;如果i>0,其父结点编号是 (i-1)/2
2) 当2i+1
1) 前序周游(tLR次序) :访问根结点;前序周游左子树;前序周游右子树 2) 中序周游(LtR次序) :中序周游左子树;访问根结点;中序周游右子树 3) 后序周游(LRt次序) :后序周游左子树;后序周游右子树;访问根结点
b. 广度周游二叉树:从二叉树的顶层(根结点)开始,自上至下逐层遍历;在同一层中,按照从左到右的顺序对结点逐一访问(实现:队列) 4. 存储
链式存储结构,
顺序存储结构(仅限完全二叉树:因为完全二叉树排列紧凑)
5. 二叉搜索树(BST )
a. 判定:是一颗空树;或者是具有下列性质的二叉树: 对于任何一个结点,设其值为K ,则该结点的 左子树(若不空) 的所有结点的值都小于K ; 右子树(若不空) 的所有结点的值都大于K ; 它的左右子树也分别为二叉搜索树
b. 性质:按照中序周游将各结点打印出来,得到的排列按照由小到大有序 c. 检索:
从根结点开始,在二叉搜索树中检索值K 如果根结点储存的值为K ,则检索结束
如果K 小于根结点的值,则只需检索左子树 如果K 大于根结点的值,则只检索右子树 该过程一直持续到找到K 或者遇上叶子结点 如果遇上叶子结点仍没有发现K ,则查找失败 **查找关键码:把查找时所经过的点一次写出 d. 插入:
用待插入结点与树根比较,若待插入的关键值小于树根的关键值,就进入左子树,否则进入右子树;在子树中,按照同样的方式沿检索路径直到叶结点,将新结点插入到二叉搜索树的叶子结点位置
e. 创建:从空的BST 开始,将关键码按BST 定义一次插入 f. 删除:
与插入相反,删除在查找成功之后进行,并且要求在删除二叉排序树上某个结点之后,仍然保持二叉排序树的特性,删除过程分为如下情况: 1)被删除的结点是叶子:直接将其删除即可
2)被删除的结点只有左子树或只有右子树:直接将要删除的点删除后,将该点的左(右)孩子和上面结点相连
3)被删除结点有左、右子树:若p 有左右子树,则在左子树里找中序周游的最后一个结点r ,将r 的右指针置成指向p 的右子树的根,用结点p 的左子树的根去代替被删除的结点p 6. 堆
a. 最小/大堆定义:
最小堆:是个关键码序列{k0, k1…kn-1},具有如下特性(i=0,1,„, ⌊n/2⌋-1)
k i ≤ k 2i+1(左孩子)
k i ≤ k 2i+2(右孩子) (即父≤2个孩子) 类似可以定义最大堆 k i ≥ k 2i+1 k i ≥ k 2i+2 (即父≥2个孩子) b. 建“初堆”:按序列建立完全二叉树,从其中最后一个有孩子的结点开始按堆的定义调整 c. 插入:插入点追加到最后,自下而上依次比较父与子,直到满足堆的定义 d. 删除:用最后结点替换被删结点,自上至下调整成堆
e. 移出最小/大值:可以将堆中最后一个位置上的元素(数组中实际的最后一个元素) 移到根的位置上,利用从左开始向下筛选对堆重新调整 7.Huffman 树 a. 概念
路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径 结点路径长度:从根结点到该结点的路径上分支的数目 树的路径长度:树中每个结点的路径长度之和 b. 带权的路径长度
树中所有叶子结点的带权路径长度之和
=其中: wk:权值 lk :结点到根的路径长度
c. 编码:左0右1
d. 如何构建:选取序列中最小的相加生成树如此反复
第六章 树
1. 概念
若∈N ,则称k 是k' 的父结 点,k' 是k 的子结点 若有序对及∈N , 则称k' 和k ″互为兄弟
若有一条由 k 到达ks 的路径,则 称k 是ks 的祖先,ks 是k 的子孙 2. 树/森林与二叉树的相互转换 a. 树转换成二叉树
加线: 在树中所有兄弟结点之间加一连线
抹线: 对每个结点,除了其最左孩子外,去除其与其余孩 子之间的连线 旋转: 以树的根结点为轴心,将整树顺时针转45° b. 二叉树转化成树
加线:若p 结点是双亲结点的左孩子,则将p 的右孩子,右孩子的右孩子,„„沿分支找到的所有右孩子,都与p 的双亲用线连起来
抹线:抹掉原二叉树中双亲与右孩子之间的连线 调整:将结点按层次排列,形成树结构 c. 森林转换成二叉树
将各棵树分别转换成二叉树 将每棵树的根结点用线相连
以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构 d. 二叉树转换成森林
抹线:将二叉树中根结点与其右孩子连线,及沿右分支搜索到 的所有右孩子间连线全部抹掉,使之变成孤立的二叉树 还原:将孤立的二叉树还原成树 3. 周游
a. 先根(次序) 周游
若树不空,则先访问根结点,然后依次先根周游各棵子树 b. 后根(次序) 周游
若树不空,则先依次后根周游各棵子树,然后访问根结点 c. 按层次周游
若树不空,则自上而下自左至右访问树中每个结点 4. 存储结构 “左子/右兄”二叉链表表示法:结点左指针指向孩子,右结点指向右兄弟,按树结构存储,无孩子或无右兄弟则置空 5. “UNION/FIND算法”(等价类)
判断两个结点是否在同一个集合中,查找一个给定结点的根结点的过程称为FIND 归并两个集合,这个归并过程常常被称为UNION
“UNION/FIND”算法用一棵树代表一个集合,如果两个结点在同一棵树中,则认为它们在同一个集合中;树中的每个结点(除根结点以外)有仅且有一个父结点;结点中仅需保存父指针信息,树本身可以 存储为一个以其结点为元素的数组 6. 树的顺序存储结构
a. 带右链的先根次序表示法
在带右链的先根次序表示中,结点按先根次序顺序存储在一片连续的存储单元中
每个结点除包括结点本身数据外,还附加两个表示结构的信息字段,结点的形式为:
info 是结点的数据;rlink 是右指针,指向结点的下一个兄弟;ltag 是一个左标记,当结点没有子结点(即对应二 叉树中结点没有左子结点时),ltag 为 1,否则为 0 b. 带双标记位的先根次序表示法
规定当结点没有下一个兄弟(即对应的二叉树中结点没有右子结点时)rtag 为1,否则为0 c. 带双标记位的层次次序表示法
结点按层次次序顺序存储在一片连续的存储单元中
第七章 图
1. 定义
a. 假设图中有n 个顶点,e 条边:
含有e=n(n-1)/2条边的无向图称作完全图 含有e=n(n-1) 条弧的有向图称作有向完全图
若边或弧的个数e
顶点的出度: 以顶点v 为弧尾的弧的数目 顶点的入度: 以顶点v 为弧头的弧的数目 c. 连通图、连通分量
若图G 中任意两个顶点之间都有路径相通,则称此图为连通图
若无向图为非连通图,则图中各个极大连通子图称作此图的连通分量 d. 强连通图、强连通分量
对于有向图,若任意两个顶点之间都存在一条有向路径,则称此有向图为强连通图 否则,其各个极大强连通子图称作它的强连通分量 e. 生成树、生成森林
假设一个连通图有n 个顶点和e 条边,其中n-1条边和n 个顶点构成一个极小连通子图,称该极小连通子图为此连通图的生成树
对非连通图,则将由各个连通分量构成的生成树集合称做此非连通图的生成森林 2. 存储结构
a. 相邻矩阵表示法
表示顶点间相邻关系的矩阵
若G 是一个具有n 个顶点的图,则G 的相邻矩阵是如下定义的n ×n 矩阵: A[i,j]=1,若(Vi, Vj)(或)是图G 的边
A[i,j]=0,若(Vi, Vj)(或)不是图G 的边 b. 邻接表表示法
为图中每个顶点建立一个单链表,第i 个单链表中的结点表示依附于顶点Vi 的边(有向图中指以Vi 为尾的弧)(建立单链表时按结点顺序建立) 3. 周游
a. 深度优先周游:
从图中某个顶点V0出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发,深度优先搜索遍历图中的其余顶点,直至图中所有与V0有路径相通的顶点都被访问到为止 b. 广度优先周游:
从图中的某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的邻接点,随后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有与V0有路径相通的顶点都被访问到为止,若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止 4. 拓扑排序
拓扑排序的方法是:1)选择一个入度为0的顶点且输出之 2)从图中删掉此顶点及所有的出边
3)回到第1步继续执行,直至图空或者图不空但找不到无前驱(入度为0)的顶点为止 5. 单源最短路径(Dijkstra 算法)
6. 每对顶点间的最短路径(Floyd 算法) 7. 最小生成树 a.Prim 算法 b.Kruskal 算法
c. 两种算法比较:Prim 算法适合稠密图,Kruskal 算法适合稀疏图
第八章 内排序
第十章 检索
1. 平均检索长度(ASL )是待检索记录集合中元素规模n 的函数, 其定义为:
ASL =
Pi 为检索第i 个元素的概率;Ci 为找到第i 个元素所需的比较次数 2. 散列 a. 除余法
用关键码key 除以M(取散列表长度) ,并取余数作为散列地址 散列函数为:hash(key) = key mod M b. 解决冲突的方法
开散列方法:把发生冲突的关键码存储在散列表主表之外(在主表外拉出单链表) 闭散列方法:把发生冲突的关键码存储在表中另一个位置上 c. 线性探查
基本思想:如果记录的基位置存储位置被占用,就在表中下移,直到找到一个空存储位置;依次探查下述地址单元:d0+1,d0+2,... ,m-1,0, 1,... , d0-1;用于简单线性探查的探查函数是:p(K, i) = i d. 散列表的检索
1. 假设给定的值为K ,根据所设定的散列函数h ,计算出散列地址h(K)
2. 如果表中该地址对应的空间未被占用,则检索失败,否则将该地址中的值与K 比较
3. 若相等则检索成功;否则,按建表时设定的处理冲突方法查找探查序列的下一个地址,如此反复下去,直到某个地址空间未被占用(可以插入),或者关键码比较相等(有重复记录,不需插入)为止
e. 散列表的删除:删除后在删除地点应加上墓碑(被删除标记) f. 散列表的插入:遇到墓碑不停止,知道找到真正的空位置
第十一章 索引技术
1. 概念:
a. 主码:数据库中的每条记录的唯一标识 b. 辅码:数据库中可以出现重复值的码 2.B 树
a. 定义:B 树定义:一个m 阶B 树满足下列条件: (1) 每个结点至多有m 个子结点; (2) 除根和叶外
其它每个结点至少有⌈⌉个子结点;
(3) 根结点至少有两个子结点 例外(空树,or 独根)
(4) 所有的叶在同一层, 可以有⌈⌉- 1到m-1个关键码
(5) 有k 个子结点的非根结点恰好包含k-1个关键码
b. 查找
在根结点所包含的关键码K1,„,Kj 中查找给定的关键码值(用顺序检索(key少)/二分检索(key多)) ;找到:则检索成功; 否则,确定要查的关键码值是在某个Ki 和Ki+1之间,于是取pi 所指结点继续查找; 如果pi 指向外部结点,表示检索失败. c. 插入
找到的叶是插入位置,若插入后该叶中关键码个数
删除的关键码不在叶结点层:先把此关键码与它在B 树里的后继对换位置,然后再删除该关键码(叶中删)
删除的关键码在叶结点层:删除后关键码个数不小于⌈⌉- 1——直接删除
关键码个数小于⌈⌉- 1,如果兄弟结点关键码个数不等于⌈⌉- 1——从兄弟结点移若干个关键码到该结点中来(父结点中的一个关键码要做相应变化) 如果兄弟结点关键码个数等于⌈⌉- 1——合并 3.B+树
m 阶B+树的结构定义如下:
(1)每个结点至多有m 个子结点; (2)每个结点(除根外) 至少有⌈⌉个子结点; (3)根结点至少有两个子结点;
(4)叶在同一层,有⌈⌉..m 个key ,叶包含全部key ,B+树的叶结点链接成一个双链表 (5)有k 个子结点的结点必有k 个关键码。
第十二章 高级数据结构
1. 广义表
a. 广义表的结构特点:
1. 广义表中的数据元素有相对次序
2. 广义表的长度定义为最外层包含元素个数
3. 广义表的深度定义为所含括弧的重数:“原子”的深度为 0 ;“空表”的深度为 1 4. 广义表可以共享
5. 广义表可以是一个递归的表:递归表的深度是无穷值,长度是有限值
6. 任何一个非空广义表 LS = (α1, α2, „, αn) 均可分解为: 表头Head( LS ) =α1和表尾Tail( LS )=(α2, „, αn) 两部分
b. 广义表的各种类型
纯表(pure list):从根结点到任何叶结点只有一条路径;也就是说任何一个元素(原子、子表)只能在广义 表中出现一次
可重入表( reentrant list ):图示对应于一个DAG ;其元素(包括原子和子表) 可能会在表中多次出现, 但不会出现回路
循环表( cyclic list ,递归表) :有向图中可能包含回路;循环表的深度为无穷大
2. 平衡的二叉搜索树(AVL )
a. 平衡因子
用bf(x)来表示结点x 的平衡因子,它被定义为:bf(x)=x的右子树的高度–x 的左子树的高度 b.AVL 的插入:按BST 建立,发现不满足AVL 定义即调整,插入时出现的情况:
1)LL/RR:中间元素成为双亲,左右各位孩子(满足BST 定义)
2)LR/RL:最后元素成为双亲,前两个为孩子(满足BST 定义)
附录:
二叉树前序周游
template
void BinaryTree::PreOrderWithoutRecusion
(BinaryTreeNode* root)
{ using std::stack; //使用STL (Standard Template Library)中的stack
stack* > aStack;
BinaryTreeNode* p=root;
while(p){
Visit(p->value( )); //先访问当前结点
if(p->rc !=NULL) aStack.push(p->rc( )); //右子非空入栈
if(p->lc !=NULL) p=p->lc( ); //继续向左下周游
else{ p = aStack.top( ); //向左下至空:出栈
aStack.pop( );}
}
}
二叉树中序周游
template
void BinaryTree::InOrderWithoutRecusion(
BinaryTreeNode* root){
using std::stack; //使用STL (Standard Template Library)中的stack
stack* > aStack; //栈aStack
BinaryTreeNode* p = root;
while( ! aStack.empty( ) || p) {
if(p){ //向左下走到底,不访问只入栈
aStack.push( p); //当前结点地址入栈
p=p->leftchild( ); } //指向左孩子
else { p=aStack.top( ); //取栈顶
aStack.pop( ); //出栈
Visit(p->value( )); //访问当前结点
p = p->rightchild( ); } //右跨一步(指向右子) 重复
}
}
二叉树后序周游
enum Tags{ Left, Right }; //枚举类型标志位
template
class StackElement { //栈元素的类型定义
public:
BinaryTreeNode* pointer; //指向二叉树结点的指针
Tags tag; }; //标志位
template
void BinaryTree::PostOrderWithoutRecusion
(BinaryTreeNode* root) {
using std::stack;//使用STL 栈部分 StackElement element; stack> aStack; //栈声明 BinaryTreeNode* p; if( root == NULL) return; //空树即返回
else p = root;
while( !aStack.empty( ) || p ) { //进入左子树
while( p !=NULL){
element.pointer = p; //准备栈元素
element.tag = Left; //标志置位
aStack.push( element); // 入栈
p = p ->leftchild(); } //沿左子树向下周游
e lement=aStack.top( ); //取栈顶元素
a Stack.pop(); //出栈
p =element.pointer;
i f ( element.tag == Left ){ //从左子树回来
element.tag = Right;
aStack.push( element );
p = p->rightchild( ); }
e lse{ Visit(p->value()); //访问当前结点
p = NULL; } //清空为了继续出栈
} }
图的深度周游
void DFS(Graph& G, int V){ //深度优先搜索算法实现
G.Mark[V]= VISITED; //访问顶点V 并标记其标志位
Visit(G, V); //访问V
for(Edge e=G. FirstEdge(V);G.IsEdge(e);e=G. NextEdge(e))
//递归周游与V 邻接未访问过的顶点
if ( G.Mark[G. ToVertices(e)]== UNVISITED)
DFS(G, G. ToVertices(e));
}
图的广度周游
void BFS(Graph& G, int V) {
using std::queue; queue Q; // //初始化队列
G.Mark[V]= VISITED;
Visit(G, V); //先访问
Q.push(V); //再入队
while(!Q.empty()) //如果队列仍然有元素
{ int V=Q.front( ); Q.pop( ); //出队
//将与该点相邻的每一个未访问点都访问完入队
for(Edge e=G.FirstEdge(V);G.IsEdge(e);e=G.NextEdge(e))
{ if(G.Mark[G.ToVertex(e)]== UNVISITED) {
G.Mark[G.ToVertex(e)]=VISITED;
Visit(G, G.ToVertex(e));
Q.push(G.ToVertex(e)); } } } }
Floyd 算法
void Floyd(Graph& G, Dist** &D){
int i, j, v; //i,j,v作为计数器
D=new Dist*[G.VerticesNum()]; //创建D[][]
for(i=0; ;i
D[i]=new Dist[G.VerticesNum()]; }
f or(i=0;i
if(i==j){ D[i][j].length=0; D[i][j].pre=i; }//对角线0
e lse{D[i][j].length =INFINITY; D[i][j].pre=-1;}//无路
f or(v=0;v
for(Edge e=G.FirstEdge(v);
G.IsEdge(e);e=G.NextEdge(e)){
D[v][G.ToVertex(e)].length=G.Weight(e);
D[v][G.ToVertex(e)].pre=v; } //按权值初始D
//顶点i 到顶点j 的路径若经过顶点v 变短,则修改
//D[i][j].length>(D[i][v].length+D[v][j].length
for(v=0;v
for(i=0;i
for(j=0;j
if(D[i][j].length >(D[i][v].length+D[v][j].length)){
D[i][j].length =D[i][v].length+D[v][j].length;
D[i][j].pre=D[v][j].pre; } } //继承顶点v 的pre
Dijkstra 算法
class Dist{ //D数组元素类, 存最短路径信息
p ublic: int index; //顶点索引值
int length; //与源s 的距离
int pre; } //当前顶点在路径中前驱顶点
void Dijkstra(Graph& G,int s, Dist* &D) { //s源
D=new Dist[ G.VerticesNum( ) ];
f or(int i=0;i
G.Mark[i]=UNVISITED; //初始化Mark 数组D 数组
D[i].index = i;
D[i].length= INFINITY; // ∞
D[i].pre=s; } //初值为源点s
D[s].length=0; //源点s 自身不参与选最短,清0
M inHeap H(G.EdgesNum( )); //最小堆用于选最小 H.Insert( D[ s ] ); //向堆中插入D[ s ]初始化
for( i=0; i
b ool FOUND=false; Dist d;
w hile ( !H.isEmpty( )){ d = H.RemoveMin( ); //选最小
if( G.Mark[d.index]= =UNVISITED){ //入选, 退出选循环
FOUND=true; break; } }
i f ( ! FOUND ) b reak; //没符合条件最短路径:退出循环
int v = d.index; //找到距离s 最小的顶点v
G .Mark[v]=VISITED; visit(G,v); //v标记已访问、输出
//因v 加入,D 值需改,只需改与v 相邻的点的值
for(Edge e=G.FirstEdge(v);G.IsEdge(e);e=G.NextEdge(e))
if(D[G.ToVertex(e)].length>(D[v].length +G.Weight(e)))
{D[G.ToVertex(e)].length=D[v].length]+G.Weight(e);
D[G.ToVertex(e)].pre=v;
H.Insert( D[G.ToVertex(e)] ); } } }
概念总结
第一章 概论
1. 数据结构描述的是按照一定逻辑关系组织起来的待处理数据元素的表示及相关操作,涉及数据的逻辑结构、存储结构和运算
2. 数据的逻辑结构是从具体问题抽象出来的数学模型,反映了事物的组成结构及事物之间的逻辑关系
可以用一组数据(结点集合K )以及这些数据之间的 一组二元关系(关系集合R )来表示:(K, R)
结点集K 是由有限个结点组成的集合,每一个结点代表一个数据或一组有明确结构的数据 关系集R 是定义在集合K 上的一组关系,其中每个关系r (r ∈R )都是K ×K 上的二元关系 3. 数据类型 a. 基本数据类型 整数类型(integer)、实数类型(real)、布尔类型(boolean)、字符类型(char )、指针类型(pointer ) b. 复合数据类型
复合类型是由基本数据类型组合而成的数据类型;复合数据类型本身,又可参与定义结构更为复杂的结点类型
4. 数据结构的分类:线性结构(一对一)、树型结构(一对多)、图结构(多对多) 5. 四种基本存储映射方法:顺序、链接、索引、散列 6. 算法的特性:通用性、有效性、确定性、有穷性
7. 算法分析:目的是从解决同一个问题的不同算法中选择比较适合的一种,或者对原始算法进行改造、加工、使其优化 8. 渐进算法分析
a .大Ο分析法:上限,表明最坏情况 b .Ω分析法:下限,表明最好情况
c .Θ分析法:当上限和下限相同时,表明平均情况
第二章 线性表
1. 线性结构的基本特征
a. 集合中必存在唯一的一个“第一元素” b. 集合中必存在唯一的一个“最后元素” c. 除最后元素之外,均有唯一的后继 d. 除第一元素之外,均有唯一的前驱 2. 线性结构的基本特点:均匀性、有序性 3. 顺序表
a. 主要特性:元素的类型相同;元素顺序地存储在连续存储空间中,每一个元素唯一的索引值;使用常数作为向量长度
b. 线性表中任意元素的存储位置:Loc(ki) = Loc(k0) + i * L(设每个元素需占用L 个存储单元)
c. 线性表的优缺点:
优点:逻辑结构与存储结构一致;属于随机存取方式,即查找每个元素所花时间基本一样 缺点:空间难以扩充 d. 检索:ASL=【Ο(1)】
e. 插入:插入前检查是否满了,插入时插入处后的表需要复制【Ο(n )】 f. 删除:删除前检查是否是空的,删除时直接覆盖就行了【Ο(n )】
4. 链表 4.1单链表
a. 特点:逻辑顺序与物理顺序有可能不一致;属于顺序存取的存储结构,即存取每个数据元素所花费的时间不相等
b. 带头结点的怎么判定空表:head 和tail 指向单链表的头结点 c. 链表的插入(q->next=p->next; p->next=q;)【Ο(n )】 d. 链表的删除(q=p->next;p->next = q->next;delete q;)【Ο(n )】 e. 不足:next 仅指向后继,不能有效找到前驱 4.2双链表
a. 增加前驱指针,弥补单链表的不足
b. 带头结点的怎么判定空表:head和tail 指向单链表的头结点 c. 插入:(q->next = p->next; q->prev = p; p->next = q; q->next->prev = q;) d. 删除:(p->prev->next = p->next;p->next->prev = p->prev; p->prev = p->next = NULL; delete p;) 4.3顺序表和链表的比较 4.3.1主要优点
a. 顺序表的主要优点
没用使用指针,不用花费附加开销;线性表元素的读访问非常简洁便利 b. 链表的主要优点
无需事先了解线性表的长度;允许线性表的长度有很大变化;能够适应经常插入删除内部元素的情况
4.3.2应用场合的选择 a. 不宜使用顺序表的场合
经常插入删除时,不宜使用顺序表;线性表的最大长度也是一个重要因素 b. 不宜使用链表的场合
当不经常插入删除时,不应选择链表;当指针的存储开销与整个结点内容所占空间相 比其比例较大时,应该慎重选择
第三章 栈与队列
1. 栈
a. 栈是一种限定仅在一端进行插入和删除操作的线性表;其特点后进先出;插入:入栈(压栈);删除:出栈(退栈);插入、删除一端被称为栈顶(浮动),另一端称为栈底(固定);实现分为顺序栈和链式栈两种 b. 应用: 1)数制转换 while (N) {
N%8入栈; N=N/8;
}
while (栈非空){
出栈; 输出;}
2)括号匹配检验
不匹配情况:各类括号数量不同;嵌套关系不正确 算法:
逐一处理表达式中的每个字符ch :
ch=非括号:不做任何处理 ch=左括号:入栈
ch=右括号:if (栈空) return false
else {
出栈,检查匹配情况, if (不匹配) return false }
如果结束后,栈非空,返回false 3)表达式求值 3.1中缀表达式:
计算规则:先括号内,再括号外;同层按照优先级,即先乘*、除/,后加+、减-;相同优先级依据结合律,左结合律即为先左后右 3.2后缀表达式:
::= + | -|
::= * |/|
::= • ::= | ∷= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 3.3中缀表达式转换为后缀表达式
InfixExp 为中缀表达式,PostfixExp 为后缀表达式
初始化操作数栈OP ,运算符栈OPND ;OPND.push('#'); 读取InfixExp 表达式的一项
操作数:直接输出到PostfixExp 中; 操作符: 当‘(’:入OPND; 当‘)’:OPND 此时若空,则出错;OPND 若非空,栈中元 素依次弹出,输入PostfixExpz 重
(’为止;若 为‘(’,弹出即可 复中,直到遇到‘
当‘四则运算符’:循环(当栈非空且栈顶不是‘(’&& 当前运算符优先级>栈顶运算符优先级),反复弹出栈顶运 算符并输入到PostfixExp 中,再将当前运算符压入栈 3.4后缀表达式求值 初始化操作数栈OP ;
while (表达式没有处理完) { item = 读取表达式一项; 操作数:入栈OP ;
运算符:退出两个操作数,
计算,并将结果入栈}
c. 递归使用的场合:定义是递归的;数据结构是递归的;解决问题的方法是递归的
2. 队列
a. 若线性表的插入操作在一端进行,删除操作在另一端进行,则称此线性表为队列 b. 循环队列判断队满对空:
队空:front==rear;队满:(rear+1)%n==front
第五章 二叉树
1. 概念
a. 一个结点的子树的个数称为度数
b. 二叉树的高度定义为二叉树中层数最大的叶结点的层数加1 c. 二叉树的深度定义为二叉树中层数最大的叶结点的层数
d. 如果一棵二叉树的任何结点,或者是树叶,或者恰有两棵非空子树,则此二叉树称作满二叉树
e. 如果一颗二叉树最多只有最下面的两层结点度数可以小于2;最下面一层的结点都集中在该层最左边的位置上,则称此二叉树为完全二叉树
f. 当二叉树里出现空的子树时,就增加新的、特殊的结点——空树叶组成扩充二叉树,扩充二叉树是满二叉树
外部路径长度E :从扩充的二叉树的根到每个外部结点(新增的空树叶)的路径长度之和 内部路径长度I :扩充的二叉树中从根到每个内部结点(原来二叉树结点)的路径长度之和 2. 性质
a. 二叉树的第i 层(根为第0层,i ≥0)最多有2^i个结点 b. 深度为k 的二叉树至多有2k+1-1个结点
c. 任何一颗二叉树,度为0的结点比度为2的结点多一个。n0 = n2 + 1 d. 满二叉树定理:非空满二叉树树叶数等于其分支结点数加1
e. 满二叉树定理推论:一个非空二叉树的空子树(指针) 数目等于其结点数加1 f. 有n 个结点(n>0)的完全二叉树的高度为⌈log2(n+1)⌉,深度为⌈log2(n+1)⌉−1 g. 对于具有n 个结点的完全二叉树,结点按层次由左到右编号,则有: 1) 如果i = 0为根结点;如果i>0,其父结点编号是 (i-1)/2
2) 当2i+1
1) 前序周游(tLR次序) :访问根结点;前序周游左子树;前序周游右子树 2) 中序周游(LtR次序) :中序周游左子树;访问根结点;中序周游右子树 3) 后序周游(LRt次序) :后序周游左子树;后序周游右子树;访问根结点
b. 广度周游二叉树:从二叉树的顶层(根结点)开始,自上至下逐层遍历;在同一层中,按照从左到右的顺序对结点逐一访问(实现:队列) 4. 存储
链式存储结构,
顺序存储结构(仅限完全二叉树:因为完全二叉树排列紧凑)
5. 二叉搜索树(BST )
a. 判定:是一颗空树;或者是具有下列性质的二叉树: 对于任何一个结点,设其值为K ,则该结点的 左子树(若不空) 的所有结点的值都小于K ; 右子树(若不空) 的所有结点的值都大于K ; 它的左右子树也分别为二叉搜索树
b. 性质:按照中序周游将各结点打印出来,得到的排列按照由小到大有序 c. 检索:
从根结点开始,在二叉搜索树中检索值K 如果根结点储存的值为K ,则检索结束
如果K 小于根结点的值,则只需检索左子树 如果K 大于根结点的值,则只检索右子树 该过程一直持续到找到K 或者遇上叶子结点 如果遇上叶子结点仍没有发现K ,则查找失败 **查找关键码:把查找时所经过的点一次写出 d. 插入:
用待插入结点与树根比较,若待插入的关键值小于树根的关键值,就进入左子树,否则进入右子树;在子树中,按照同样的方式沿检索路径直到叶结点,将新结点插入到二叉搜索树的叶子结点位置
e. 创建:从空的BST 开始,将关键码按BST 定义一次插入 f. 删除:
与插入相反,删除在查找成功之后进行,并且要求在删除二叉排序树上某个结点之后,仍然保持二叉排序树的特性,删除过程分为如下情况: 1)被删除的结点是叶子:直接将其删除即可
2)被删除的结点只有左子树或只有右子树:直接将要删除的点删除后,将该点的左(右)孩子和上面结点相连
3)被删除结点有左、右子树:若p 有左右子树,则在左子树里找中序周游的最后一个结点r ,将r 的右指针置成指向p 的右子树的根,用结点p 的左子树的根去代替被删除的结点p 6. 堆
a. 最小/大堆定义:
最小堆:是个关键码序列{k0, k1…kn-1},具有如下特性(i=0,1,„, ⌊n/2⌋-1)
k i ≤ k 2i+1(左孩子)
k i ≤ k 2i+2(右孩子) (即父≤2个孩子) 类似可以定义最大堆 k i ≥ k 2i+1 k i ≥ k 2i+2 (即父≥2个孩子) b. 建“初堆”:按序列建立完全二叉树,从其中最后一个有孩子的结点开始按堆的定义调整 c. 插入:插入点追加到最后,自下而上依次比较父与子,直到满足堆的定义 d. 删除:用最后结点替换被删结点,自上至下调整成堆
e. 移出最小/大值:可以将堆中最后一个位置上的元素(数组中实际的最后一个元素) 移到根的位置上,利用从左开始向下筛选对堆重新调整 7.Huffman 树 a. 概念
路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径 结点路径长度:从根结点到该结点的路径上分支的数目 树的路径长度:树中每个结点的路径长度之和 b. 带权的路径长度
树中所有叶子结点的带权路径长度之和
=其中: wk:权值 lk :结点到根的路径长度
c. 编码:左0右1
d. 如何构建:选取序列中最小的相加生成树如此反复
第六章 树
1. 概念
若∈N ,则称k 是k' 的父结 点,k' 是k 的子结点 若有序对及∈N , 则称k' 和k ″互为兄弟
若有一条由 k 到达ks 的路径,则 称k 是ks 的祖先,ks 是k 的子孙 2. 树/森林与二叉树的相互转换 a. 树转换成二叉树
加线: 在树中所有兄弟结点之间加一连线
抹线: 对每个结点,除了其最左孩子外,去除其与其余孩 子之间的连线 旋转: 以树的根结点为轴心,将整树顺时针转45° b. 二叉树转化成树
加线:若p 结点是双亲结点的左孩子,则将p 的右孩子,右孩子的右孩子,„„沿分支找到的所有右孩子,都与p 的双亲用线连起来
抹线:抹掉原二叉树中双亲与右孩子之间的连线 调整:将结点按层次排列,形成树结构 c. 森林转换成二叉树
将各棵树分别转换成二叉树 将每棵树的根结点用线相连
以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构 d. 二叉树转换成森林
抹线:将二叉树中根结点与其右孩子连线,及沿右分支搜索到 的所有右孩子间连线全部抹掉,使之变成孤立的二叉树 还原:将孤立的二叉树还原成树 3. 周游
a. 先根(次序) 周游
若树不空,则先访问根结点,然后依次先根周游各棵子树 b. 后根(次序) 周游
若树不空,则先依次后根周游各棵子树,然后访问根结点 c. 按层次周游
若树不空,则自上而下自左至右访问树中每个结点 4. 存储结构 “左子/右兄”二叉链表表示法:结点左指针指向孩子,右结点指向右兄弟,按树结构存储,无孩子或无右兄弟则置空 5. “UNION/FIND算法”(等价类)
判断两个结点是否在同一个集合中,查找一个给定结点的根结点的过程称为FIND 归并两个集合,这个归并过程常常被称为UNION
“UNION/FIND”算法用一棵树代表一个集合,如果两个结点在同一棵树中,则认为它们在同一个集合中;树中的每个结点(除根结点以外)有仅且有一个父结点;结点中仅需保存父指针信息,树本身可以 存储为一个以其结点为元素的数组 6. 树的顺序存储结构
a. 带右链的先根次序表示法
在带右链的先根次序表示中,结点按先根次序顺序存储在一片连续的存储单元中
每个结点除包括结点本身数据外,还附加两个表示结构的信息字段,结点的形式为:
info 是结点的数据;rlink 是右指针,指向结点的下一个兄弟;ltag 是一个左标记,当结点没有子结点(即对应二 叉树中结点没有左子结点时),ltag 为 1,否则为 0 b. 带双标记位的先根次序表示法
规定当结点没有下一个兄弟(即对应的二叉树中结点没有右子结点时)rtag 为1,否则为0 c. 带双标记位的层次次序表示法
结点按层次次序顺序存储在一片连续的存储单元中
第七章 图
1. 定义
a. 假设图中有n 个顶点,e 条边:
含有e=n(n-1)/2条边的无向图称作完全图 含有e=n(n-1) 条弧的有向图称作有向完全图
若边或弧的个数e
顶点的出度: 以顶点v 为弧尾的弧的数目 顶点的入度: 以顶点v 为弧头的弧的数目 c. 连通图、连通分量
若图G 中任意两个顶点之间都有路径相通,则称此图为连通图
若无向图为非连通图,则图中各个极大连通子图称作此图的连通分量 d. 强连通图、强连通分量
对于有向图,若任意两个顶点之间都存在一条有向路径,则称此有向图为强连通图 否则,其各个极大强连通子图称作它的强连通分量 e. 生成树、生成森林
假设一个连通图有n 个顶点和e 条边,其中n-1条边和n 个顶点构成一个极小连通子图,称该极小连通子图为此连通图的生成树
对非连通图,则将由各个连通分量构成的生成树集合称做此非连通图的生成森林 2. 存储结构
a. 相邻矩阵表示法
表示顶点间相邻关系的矩阵
若G 是一个具有n 个顶点的图,则G 的相邻矩阵是如下定义的n ×n 矩阵: A[i,j]=1,若(Vi, Vj)(或)是图G 的边
A[i,j]=0,若(Vi, Vj)(或)不是图G 的边 b. 邻接表表示法
为图中每个顶点建立一个单链表,第i 个单链表中的结点表示依附于顶点Vi 的边(有向图中指以Vi 为尾的弧)(建立单链表时按结点顺序建立) 3. 周游
a. 深度优先周游:
从图中某个顶点V0出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发,深度优先搜索遍历图中的其余顶点,直至图中所有与V0有路径相通的顶点都被访问到为止 b. 广度优先周游:
从图中的某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的邻接点,随后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有与V0有路径相通的顶点都被访问到为止,若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止 4. 拓扑排序
拓扑排序的方法是:1)选择一个入度为0的顶点且输出之 2)从图中删掉此顶点及所有的出边
3)回到第1步继续执行,直至图空或者图不空但找不到无前驱(入度为0)的顶点为止 5. 单源最短路径(Dijkstra 算法)
6. 每对顶点间的最短路径(Floyd 算法) 7. 最小生成树 a.Prim 算法 b.Kruskal 算法
c. 两种算法比较:Prim 算法适合稠密图,Kruskal 算法适合稀疏图
第八章 内排序
第十章 检索
1. 平均检索长度(ASL )是待检索记录集合中元素规模n 的函数, 其定义为:
ASL =
Pi 为检索第i 个元素的概率;Ci 为找到第i 个元素所需的比较次数 2. 散列 a. 除余法
用关键码key 除以M(取散列表长度) ,并取余数作为散列地址 散列函数为:hash(key) = key mod M b. 解决冲突的方法
开散列方法:把发生冲突的关键码存储在散列表主表之外(在主表外拉出单链表) 闭散列方法:把发生冲突的关键码存储在表中另一个位置上 c. 线性探查
基本思想:如果记录的基位置存储位置被占用,就在表中下移,直到找到一个空存储位置;依次探查下述地址单元:d0+1,d0+2,... ,m-1,0, 1,... , d0-1;用于简单线性探查的探查函数是:p(K, i) = i d. 散列表的检索
1. 假设给定的值为K ,根据所设定的散列函数h ,计算出散列地址h(K)
2. 如果表中该地址对应的空间未被占用,则检索失败,否则将该地址中的值与K 比较
3. 若相等则检索成功;否则,按建表时设定的处理冲突方法查找探查序列的下一个地址,如此反复下去,直到某个地址空间未被占用(可以插入),或者关键码比较相等(有重复记录,不需插入)为止
e. 散列表的删除:删除后在删除地点应加上墓碑(被删除标记) f. 散列表的插入:遇到墓碑不停止,知道找到真正的空位置
第十一章 索引技术
1. 概念:
a. 主码:数据库中的每条记录的唯一标识 b. 辅码:数据库中可以出现重复值的码 2.B 树
a. 定义:B 树定义:一个m 阶B 树满足下列条件: (1) 每个结点至多有m 个子结点; (2) 除根和叶外
其它每个结点至少有⌈⌉个子结点;
(3) 根结点至少有两个子结点 例外(空树,or 独根)
(4) 所有的叶在同一层, 可以有⌈⌉- 1到m-1个关键码
(5) 有k 个子结点的非根结点恰好包含k-1个关键码
b. 查找
在根结点所包含的关键码K1,„,Kj 中查找给定的关键码值(用顺序检索(key少)/二分检索(key多)) ;找到:则检索成功; 否则,确定要查的关键码值是在某个Ki 和Ki+1之间,于是取pi 所指结点继续查找; 如果pi 指向外部结点,表示检索失败. c. 插入
找到的叶是插入位置,若插入后该叶中关键码个数
删除的关键码不在叶结点层:先把此关键码与它在B 树里的后继对换位置,然后再删除该关键码(叶中删)
删除的关键码在叶结点层:删除后关键码个数不小于⌈⌉- 1——直接删除
关键码个数小于⌈⌉- 1,如果兄弟结点关键码个数不等于⌈⌉- 1——从兄弟结点移若干个关键码到该结点中来(父结点中的一个关键码要做相应变化) 如果兄弟结点关键码个数等于⌈⌉- 1——合并 3.B+树
m 阶B+树的结构定义如下:
(1)每个结点至多有m 个子结点; (2)每个结点(除根外) 至少有⌈⌉个子结点; (3)根结点至少有两个子结点;
(4)叶在同一层,有⌈⌉..m 个key ,叶包含全部key ,B+树的叶结点链接成一个双链表 (5)有k 个子结点的结点必有k 个关键码。
第十二章 高级数据结构
1. 广义表
a. 广义表的结构特点:
1. 广义表中的数据元素有相对次序
2. 广义表的长度定义为最外层包含元素个数
3. 广义表的深度定义为所含括弧的重数:“原子”的深度为 0 ;“空表”的深度为 1 4. 广义表可以共享
5. 广义表可以是一个递归的表:递归表的深度是无穷值,长度是有限值
6. 任何一个非空广义表 LS = (α1, α2, „, αn) 均可分解为: 表头Head( LS ) =α1和表尾Tail( LS )=(α2, „, αn) 两部分
b. 广义表的各种类型
纯表(pure list):从根结点到任何叶结点只有一条路径;也就是说任何一个元素(原子、子表)只能在广义 表中出现一次
可重入表( reentrant list ):图示对应于一个DAG ;其元素(包括原子和子表) 可能会在表中多次出现, 但不会出现回路
循环表( cyclic list ,递归表) :有向图中可能包含回路;循环表的深度为无穷大
2. 平衡的二叉搜索树(AVL )
a. 平衡因子
用bf(x)来表示结点x 的平衡因子,它被定义为:bf(x)=x的右子树的高度–x 的左子树的高度 b.AVL 的插入:按BST 建立,发现不满足AVL 定义即调整,插入时出现的情况:
1)LL/RR:中间元素成为双亲,左右各位孩子(满足BST 定义)
2)LR/RL:最后元素成为双亲,前两个为孩子(满足BST 定义)
附录:
二叉树前序周游
template
void BinaryTree::PreOrderWithoutRecusion
(BinaryTreeNode* root)
{ using std::stack; //使用STL (Standard Template Library)中的stack
stack* > aStack;
BinaryTreeNode* p=root;
while(p){
Visit(p->value( )); //先访问当前结点
if(p->rc !=NULL) aStack.push(p->rc( )); //右子非空入栈
if(p->lc !=NULL) p=p->lc( ); //继续向左下周游
else{ p = aStack.top( ); //向左下至空:出栈
aStack.pop( );}
}
}
二叉树中序周游
template
void BinaryTree::InOrderWithoutRecusion(
BinaryTreeNode* root){
using std::stack; //使用STL (Standard Template Library)中的stack
stack* > aStack; //栈aStack
BinaryTreeNode* p = root;
while( ! aStack.empty( ) || p) {
if(p){ //向左下走到底,不访问只入栈
aStack.push( p); //当前结点地址入栈
p=p->leftchild( ); } //指向左孩子
else { p=aStack.top( ); //取栈顶
aStack.pop( ); //出栈
Visit(p->value( )); //访问当前结点
p = p->rightchild( ); } //右跨一步(指向右子) 重复
}
}
二叉树后序周游
enum Tags{ Left, Right }; //枚举类型标志位
template
class StackElement { //栈元素的类型定义
public:
BinaryTreeNode* pointer; //指向二叉树结点的指针
Tags tag; }; //标志位
template
void BinaryTree::PostOrderWithoutRecusion
(BinaryTreeNode* root) {
using std::stack;//使用STL 栈部分 StackElement element; stack> aStack; //栈声明 BinaryTreeNode* p; if( root == NULL) return; //空树即返回
else p = root;
while( !aStack.empty( ) || p ) { //进入左子树
while( p !=NULL){
element.pointer = p; //准备栈元素
element.tag = Left; //标志置位
aStack.push( element); // 入栈
p = p ->leftchild(); } //沿左子树向下周游
e lement=aStack.top( ); //取栈顶元素
a Stack.pop(); //出栈
p =element.pointer;
i f ( element.tag == Left ){ //从左子树回来
element.tag = Right;
aStack.push( element );
p = p->rightchild( ); }
e lse{ Visit(p->value()); //访问当前结点
p = NULL; } //清空为了继续出栈
} }
图的深度周游
void DFS(Graph& G, int V){ //深度优先搜索算法实现
G.Mark[V]= VISITED; //访问顶点V 并标记其标志位
Visit(G, V); //访问V
for(Edge e=G. FirstEdge(V);G.IsEdge(e);e=G. NextEdge(e))
//递归周游与V 邻接未访问过的顶点
if ( G.Mark[G. ToVertices(e)]== UNVISITED)
DFS(G, G. ToVertices(e));
}
图的广度周游
void BFS(Graph& G, int V) {
using std::queue; queue Q; // //初始化队列
G.Mark[V]= VISITED;
Visit(G, V); //先访问
Q.push(V); //再入队
while(!Q.empty()) //如果队列仍然有元素
{ int V=Q.front( ); Q.pop( ); //出队
//将与该点相邻的每一个未访问点都访问完入队
for(Edge e=G.FirstEdge(V);G.IsEdge(e);e=G.NextEdge(e))
{ if(G.Mark[G.ToVertex(e)]== UNVISITED) {
G.Mark[G.ToVertex(e)]=VISITED;
Visit(G, G.ToVertex(e));
Q.push(G.ToVertex(e)); } } } }
Floyd 算法
void Floyd(Graph& G, Dist** &D){
int i, j, v; //i,j,v作为计数器
D=new Dist*[G.VerticesNum()]; //创建D[][]
for(i=0; ;i
D[i]=new Dist[G.VerticesNum()]; }
f or(i=0;i
if(i==j){ D[i][j].length=0; D[i][j].pre=i; }//对角线0
e lse{D[i][j].length =INFINITY; D[i][j].pre=-1;}//无路
f or(v=0;v
for(Edge e=G.FirstEdge(v);
G.IsEdge(e);e=G.NextEdge(e)){
D[v][G.ToVertex(e)].length=G.Weight(e);
D[v][G.ToVertex(e)].pre=v; } //按权值初始D
//顶点i 到顶点j 的路径若经过顶点v 变短,则修改
//D[i][j].length>(D[i][v].length+D[v][j].length
for(v=0;v
for(i=0;i
for(j=0;j
if(D[i][j].length >(D[i][v].length+D[v][j].length)){
D[i][j].length =D[i][v].length+D[v][j].length;
D[i][j].pre=D[v][j].pre; } } //继承顶点v 的pre
Dijkstra 算法
class Dist{ //D数组元素类, 存最短路径信息
p ublic: int index; //顶点索引值
int length; //与源s 的距离
int pre; } //当前顶点在路径中前驱顶点
void Dijkstra(Graph& G,int s, Dist* &D) { //s源
D=new Dist[ G.VerticesNum( ) ];
f or(int i=0;i
G.Mark[i]=UNVISITED; //初始化Mark 数组D 数组
D[i].index = i;
D[i].length= INFINITY; // ∞
D[i].pre=s; } //初值为源点s
D[s].length=0; //源点s 自身不参与选最短,清0
M inHeap H(G.EdgesNum( )); //最小堆用于选最小 H.Insert( D[ s ] ); //向堆中插入D[ s ]初始化
for( i=0; i
b ool FOUND=false; Dist d;
w hile ( !H.isEmpty( )){ d = H.RemoveMin( ); //选最小
if( G.Mark[d.index]= =UNVISITED){ //入选, 退出选循环
FOUND=true; break; } }
i f ( ! FOUND ) b reak; //没符合条件最短路径:退出循环
int v = d.index; //找到距离s 最小的顶点v
G .Mark[v]=VISITED; visit(G,v); //v标记已访问、输出
//因v 加入,D 值需改,只需改与v 相邻的点的值
for(Edge e=G.FirstEdge(v);G.IsEdge(e);e=G.NextEdge(e))
if(D[G.ToVertex(e)].length>(D[v].length +G.Weight(e)))
{D[G.ToVertex(e)].length=D[v].length]+G.Weight(e);
D[G.ToVertex(e)].pre=v;
H.Insert( D[G.ToVertex(e)] ); } } }