概念题
-
第一章 绪论
数据类型 原子类型 结构类型 抽象数据类型
数据的逻辑结构 线性结构 (一般线性表 受限线性表 线性表推广) 非线性结构(集合 树形结构 图状结构)
存储结构的分类 顺序存储 链式存储 索引存储 散列存储
一个算法的设计取决于所选定的逻辑结构,而算法的实现依赖于所采用的存储结构。 -
第二章 线性表
线性表的顺序存储结构的定义定义是用一组地址连续的存储单元依次存储线性表中·的数据元素。特点是在逻辑上相邻的两个元素在物理位置上也相邻
链式存储结构的定义不需要使用地址连续的存储单元,它通过链建立起数据元素之间的逻辑关系 -
第三章 栈和队列
递归的概念:使用被定义对象的自身来为其下定义
栈:是限定仅在表尾进行插入和删除操作,又称后进先出的线性表
队列:是只允许在表的一端进行插入,而在另一端删除元素,又称先进先出的线性表。
栈的典型应用:括号匹配 表达式求值 递归
队列的典型应用:层次遍历中的应用 计算机系统中的应用 -
第四章 数组和广义表
数组的概念 n个相同类型的数据元素构成的有限序列,每个数据元素称为一个数组元素。
数组的存储结构:使用计算机语言中的数组类型进行存储,一个数组的所有元素在内存中占用一段连续的存储空间 -
第五章 树和二叉树
树的定义:1、有且仅有一个特定的称为根的节点 2、其余节点可以分为互不相交的有限集,其中每个集合本身又是一棵树。
树的表示方法: 双亲表示法 孩子表示法 孩子兄弟表示法
树的存储结构:顺序存储结构 链式存储结构
二叉树的定义:1、或者为空二叉树 2、有且仅有一个特定的称为根的节点 且其余节点可以分为两个互不相交的有限集称为左子树和右子树,左子树和右子树又分别是一颗二叉树。
满二叉树:一颗高度为h,且含有 2h - 1 个节点的二叉树称为满二叉树。
完全二叉树:高度为h,有n个节点的二叉树,当且仅当其每个节点都与高度为h的满二叉树中的编号为1~n的节点一一对应时,称为完全二叉树。 -
第六章 图
连通 连通图 连通分量:在无向图中,有路径则连通,任两顶点皆连通称为连通图,极大连通子图称为连通分量。
强连通图 强连通分量 :在有向图中,两点之间互有路径则强连通,任意两顶点皆强连通则为强连通图,有向图中的极大连通子图称为有向图的强连通分量
邻接矩阵和邻接表存储结构用一个一维数组存储图中顶点的信息,用一个二维数组(称为邻接矩阵)存储图中各顶点之间的邻接关系。邻接矩阵是用数组来实现的,而邻接表是用链表来实现的。
拓扑排序的概念:1、每个顶点出现且仅出现一次 2、若顶点A在序列中排在B的前面,则在图中不存在从顶点B到顶点A的路径。
关键路径的定义:从源点到汇点的所有路径中,具有最大路径长度的路径称为关键路径
关键活动的定义:关键路径上的活动叫关键活动
最小生成树的概念:一个连通图的生成树包含图的所有顶点,并且包含尽可能少的边
二叉树的定义:1、或者为空二叉树 2、有且仅有一个特定的称为根的节点 且其余节点可以分为两个互不相交的有限集称为左子树和右子树,左子树和右子树又分别是一颗二叉树。
二叉排序(查找)树的基本概念:二叉排序树或者是一颗空树,或者是具有如下特征的二叉树 1、若左子树非空,则左子树上所有节点的值均小于根节点的值2、若右子树非空,则右子树上所有节点的值均大于根节点的值3、左右子树分别是一颗二叉排序树
平衡二叉树的基本概念:是一颗空树或者是具有下列性质的二叉排序树:1、左子树和右子树的高度差的绝对值不超过一。2、它的左子树和右子树都是平衡二叉树
哈夫曼树:带权路径长度之和最短的树 -
第七章 查找
查找 :在数据集合中寻找满足某种条件的数据元素的过程称为查找
分块查找的基本思想:将查找表分为若干子块。块内的元素可以无序,但块之间是有序的,即第一个块中的最大关键字小于第二个块中所有记录的关键字,第二个块中的最大关键字小于第三个块中的所有记录的关键字,以此类推。再建立一个索引表,索引表中的每个元素含有各块的最大关键字和各块中的第一个元素的地址,索引表按关键字有序排列。
冲突:不同的关键字得到同一散列地址
同义词:相同函数值的关键字对该散列函数来说是同义词。
处理冲突的方法:开放定址法 拉链法
散列表:关键字与存储地址之间的一种之间映射关系 -
排序
排序,就是重新排列表中的元素,使表中的元素按关键字有序的过程
排序的稳定性是指:关键字相同的两个元素,在排序前后的相对位置不发生改变。则称这个排序算法是稳定的,否则称这个排序算法是不稳定的。
基数排序:借助多关键字排序的思想对单逻辑关键字进行排序的方法。
查找
散列表的查找
注意事项:
-
散列地址从零开始
-
一个数取余自己本身是不变的 如 :7 % 7 = 7
-
开放地址的二次探测法的每次都是在初始计算失败的地址上再加上 di
-
ASL成功 = 比较次数 / 关键字的个数
-
ASL失败 = 比较次数 / 地址的范围
广义表
先科普一下广义表Head和Tail的作用
对于 非空广义表 来说,它的表头(即Head)既可能是单元素又可能是广义表。
例如:
Head( (a,b,c) )= a ;
Head(((a),(b)))=(a);
但其表尾(即Tail)一定是一个广义表,也就是说,广义表利用Tail提取后也一定还是一个广义表。这么说有点像废话,其实不然,这里面就隐藏着一个初学者很容易踩到的陷阱。
例如:
Tail((a,b,c)) 的答案显然是(b,c)
但 Tail(((a,b),(c,d))) 的答案是否是(c,d)呢,答案是否定的。
它的答案是 ((c,d))。这就是开头为什么要强调广义表利用Tail提取后也一定还是一个广义表的原因。因为如果广义表中除去表头部分后的部分仍是个广义表的话,就容易让我们混淆,以为答案直接是后部分,而忽略了给它多加一对括号。
下面,让我们做一道题试试手吧!
Tail ( Head ( Tail ( ( (a,b),(c,d) ) ) ) )
答案:
Tail(((a,b),(c,d)))=((c,d))而非(c,d)
Head( ((c,d)) )=(c,d);
Tail((c,d))=(d)
队列
循环队列
判空判满的条件
队空的条件: Q.front == Q.rear;
队满的条件: (Q.rear + 1)% MAXQSIZE==front;
图
关于图的几个概念定义:
- 连通图:在无向图中,若任意两个顶点vi与vj都有路径相通,则称该无向图为连通图。
- 强连通图:在有向图中,若任意两个顶点vi与vj都有路径相通,则称该有向图为强连通图。
- 连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
- 生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
- 最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。
最小生成树
Prim算法
Prim算法思想:
此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。
1 图的所有顶点集合为V;初始令集合u={s},v=V−u;
2 在两个集合u,v能够组成的边中,选择一条代价最小的边(u0,v0),加入到最小生成树中,
并把v0并入到集合u中。
3 重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。
4 由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组
closedge,用来维护集合v中每个顶点与集合u中最小代价边信息
Kruskal算法
此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。
1. 把图中的所有边按代价从小到大排序;
2. 把图中的n个顶点看成独立的n棵树组成的森林;
3. 按权值从小到大选择边,所选的边连接的两个顶点ui,vi,应属于两颗不同的树,
则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
4. 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。
最短路径
Dijkstra 算法
AOV网
定义
无权值的有向无环图,仅表示顶点之间的前后关系。其定点表示活动,用有向边 <vi,vj> 表示活动 vi 必须先于活动 vj 进行的这样一种关系。
AOE网
定义
有权值的有向无环图,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销。
性质
AOE网具有以下两个性质
- 只有在某顶点所代表的事件发生后,从该顶点出发的各个有向边所代表的活动才能开始;
- 只有在进入某顶点的各有向边所代表的活动都已结束时,该顶点所代表的事件才发生。
源点 :在AOE网中仅有一个入度为零的顶点,称为开始顶点(源点)
汇点 :在AOE网中仅有一个出度为零的顶点,称位结束顶点(汇点)
关键路径 :从源点到汇点的所有路径中,具有最大路径长度的路径称为关键路径。
关键活动 :关键路径上的活动称为关键活动。
结束节点的最早发生时间和最迟发生时间相同
事件 vk 的最早发生时间ve(k) :它是指从源点到顶点的最长路径长度。计算ve()值时,按从前往后的顺序进行,可以在拓扑排序的基础上计算。事件vk的最迟发生时间vl(k) :从后往前,后继结点的最迟发生时间减边权值,取最小值
弧的最早开始时间e() :等于该弧的起点的顶点的ve()
弧的最迟开始时间l(i) :等于该弧的终点的顶点的vl()减去该弧持续的时间
完成整个工程的最短时间就是关键路径的长度,即关键路径上各活动花费开销的总和,这是因为关键活动影响了整个工程的的时间。因此,只有找到关键路径,也就可以得出最短完成时间。
加速某一项关键活动不一定能缩短整个工程的工期,因为AOE网中可能存在多余关键路径。可能存在称为桥的一种特殊关键活动,它位于所有的关键路径上,只有它加速才会缩短整个周期
求关键路径的算法步骤如下
1 从源点出发,令ve(源点)= 0,按拓扑序求其余顶点的最早发生时间ve()
2 从汇点出发,令vl(汇点)= ve(汇点),按逆拓扑有序求其余顶点的最迟发生时间vl()
3 根据各顶点的ve()值求所有弧的最早开始时间e()
4 根据各顶点的vl()值求所有弧的最迟开始时间l()
5 求AOE网中的所有活动的差额d(),找出所有d()= 0的活动构成关键路径
拓扑排序
定义
所谓拓扑排序就是将AOV网中所有的顶点排成一个线性序列
该序列满足:若在AOV 网中由顶点vi到顶点vj有一条路径,则在该线性序列中的顶点vi必定在vj之前
满足下列条件
- 每个顶点出现且只出现一次
- 若顶点A在序列中排在顶点B的前面,则在图中不存在中顶点B到顶点A的路径。
过程
1 在一个有向图中,对所有的节点进行排序,要求没有一个节点指向它前面的节点。
2 先统计所有节点的入度,对于入度为0的节点就可以分离出来,然后把这个节点指向的节点的入度减1
3 一直做改操作,直到所有的节点都被分离出来。
4 如果最后不存在入度为0的节点,那就说明有环,不存在拓扑排序,也就是很多题目的无解的情况
下面是算法的演示过程。
排序
内部排序:排序期间元素全部存放在内存中的排序
外部排序:排序期间元素无法全部同时存放在内存中,必须在排序的过程中根据要求不断地在内、外存之间移动的算法
算法的稳定性:若待排序表中有两个元素 Ri 和 Rj ,其对应的关键字相同即 keyi = keyj,且在排序前 Ri 在 Rj 的前面,若使用某一排序算法后,Ri 仍然在 Rj 前面,则称这个排序算法是稳定的,否则称排序算法是不稳定的。
稳定的排序算法 冒泡排序、插入排序、归并排序和基数排序
不稳定的排序算法 :快、希、选、堆。(记忆:找到工作就可以“快些选一堆”美女来玩了 选择排序、快速排序、希尔排序、堆排序)因为此想法不符合社会主义核心价值观,所以它不稳定。
插入排序
直接插入排序
折半插入排序
希尔排序
交换排序
冒泡排序
快速排序
- 快速排序是所有内部排序算法中平均性能最优的算法
选择排序
简单选择排序
堆排序
堆的概念:堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。
大根堆:大根堆的最大元素存放在根顶点,且其任一非根节点的值小于等于其父节点的值
小根堆:根节点是最小值,其任一非根节点的值大于其父节点的值。
排升序要建大根堆,排降序建小根堆
筛选法建堆的过程
筛选法就是开始按现有的顺序从上到下,从左到右放到一个完全二叉树里面。
然后把这个树调节成堆。
调节的时候从最后一个有儿子的节点开始。 也就是从下往上,从右往左找,
找到的第一个有孩子的节点开始。依次把各个节点及下面的孩子组成的树调节成堆
归并排序和基数排序
归并排序
基数排序
外部排序
多路归并排序
二叉树
哈夫曼树(最优二叉树)
什么是哈夫曼树? 让我们先举一个例子。 判定树: 在很多问题的处理过程中,需要进行大量的条件判断, 这些判断结构的设计直接影响着程序的执行效率。例如,编制一 个程序,将百分制转换成五个等级输出。 若考虑上述程序所耗费的时间,就会发现该程序的缺陷。在 实际中,学生成绩在五个等级上的分布是不均匀的。当学生百分制 成绩的录入量很大时,上述判定过程需要反复调用,此时程序的 执行效率将成为一个严重问题 下面可以利用哈夫曼树寻找一棵最佳判定树,即总的比较次数 最少的判定树。我们称判定过程最优的二叉树为哈夫曼树,又称 最优二叉树
实例:
树的带权路径长度:如果树中每个叶子上都带有一个权值,则把树中所有叶子的带权路径长度之和称为树的带权路径长度。
其中带权路径长度最小的二叉树就称为哈夫曼树或最优二叉树
根据哈弗曼树的定义,一棵二叉树要使其WPL值最小,必须使权值越大的叶子结点越靠近根结点,而权值越小的叶子结点越远离根结点。
哈弗曼依据这一特点提出了一种构造最优二叉树的方法,其基本思想如下:
实例
哈夫曼树的在编码中的应用
在电文传输中,需要将电文中出现的每个字符进行二进制编码。在设计编码时需要遵守两个原则:
(1)发送方传输的二进制编码,到接收方解码后必须具有唯一性,即解码结果与发送方发送的电文完全一样;
(2)发送的二进制编码尽可能地短。下面我们介绍两种编码的方式。
-
等长编码
这种编码方式的特点是每个字符的编码长度相同(编码长度就是每个编码所含的二进制位数)。假设字符集只含有4个字符A,B,C,D,用二进制两位表示的编码分别为00,01,10,11。若现在有一段电文为:ABACCDA,则应发送二进制序列:00010010101100,总长度为14位。当接收方接收到这段电文后,将按两位一段进行译码。这种编码的特点是译码简单且具有唯一性,但编码长度并不是最短的。 -
不等长编码
在传送电文时,为了使其二进制位数尽可能地少,可以将每个字符的编码设计为不等长的,使用频度较高的字符分配一个相对比较短的编码,使用频度较低的字符分配一个比较长的编码。例如,可以为A,B,C,D四个字符分别分配0,00,1,01,并可将上述电文用二进制序列:000011010发送,其长度只有9个二进制位,但随之带来了一个问题,接收方接到这段电文后无法进行译码,因为无法断定前面4个0是4个A,1个B、2个A,还是2个B,即译码不唯一,因此这种编码方法不可使用。
因此,为了设计长短不等的编码,以便减少电文的总长,还必须考虑编码的唯一性,即在建立不等长编码时必须使任何一个字符的编码都不是另一个字符的前缀,这种编码称为前缀编码(prefix code)
- 利用字符集中每个字符的使用频率作为权值构造一个哈夫曼树;
- 从根结点开始,为到每个叶子结点路径上的左分支赋予0,右分支赋予1,并从根到叶子方向形成该叶子结点的编码
例题:
假设一个文本文件TFile中只包含7个字符{A,B,C,D,E,F,G},这7个字符在文本中出现的次数为{5,24,7,17,34,5,13}
利用哈夫曼树可以为文件TFile构造出符合前缀编码要求的不等长编码
具体做法:
- 将TFile中7个字符都作为叶子结点,每个字符出现次数作为该叶子结点的权值
- 规定哈夫曼树中所有左分支表示字符0,所有右分支表示字符1,将依次从根结点到每个叶子结点所经过的分支的二进制位的序列作为该
结点对应的字符编码 - 由于从根结点到任何一个叶子结点都不可能经过其他叶子,这种编码一定是前缀编码,哈夫曼树的带权路径长度正好是文件TFile编码的总长度
通过哈夫曼树来构造的编码称为哈弗曼编码(huffman code)
哈夫曼编码是最优前缀编码
二叉查找树
性质
- 二叉查找树有一个性质,即对二叉查找树进行中序遍历,即可得到有序的数列。
- 二叉查找树的查询复杂度,和二分查找一样,插入和查找的时间复杂度均为 O(logn)
插入与删除
二叉查找树的插入算法
空树,就首先生成根节点;不是空树就按照查找的算法,找到父节点,然后作为叶子节点插入,如果值已经存在就插入失败
。
删除操作有如下几种情况:
(1)如果删除的是叶节点,可以直接删除;
(2)如果被删除的元素有一个子节点,可以将子节点直接移到被删除元素的位置;
(3)如果有两个子节点,这时候就采用中序遍历,找到待删除的节点的后继节点,将其与待删除的节点互换,此时待删除节点的位置已经是叶子节点,可以直接删除。如下图:
平衡二叉树(AVL树)
定义
平衡二叉搜索树,又被称为AVL树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
由于普通的二叉查找树会容易失去”平衡“,极端情况下,二叉查找树会退化成线性的链表,导致插入和查找的复杂度下降到 O(n) ,所以,这也是平衡二叉树设计的初衷。那么平衡二叉树如何保持”平衡“呢?根据定义,有两个重点,一是左右两子树的高度差的绝对值不能超过1,二是左右两子树也是一颗平衡二叉树。
如下图所示,左图是一棵平衡二叉树,根节点10,左右两子树的高度差是1,而右图,虽然根节点左右两子树高度差是0,但是右子树15的左右子树高度差为2,不符合定义,所以右图不是一棵平衡二叉树。
平衡二叉树的调整方法
AVL树相较于普通的二叉搜索树,自主要的就是做了平衡化处理,使得二叉树变的平衡,高度降低。
在插入一个结点后应该沿搜索路径将路径上的结点平衡因子进行修改,当平衡因子大于1时,就需要进行平衡化处理。从发生不平衡的结点起,沿刚才回溯的路径取直接下两层的结点,如果这三个结点在一条直线上,则采用单旋转 进行平衡化,如果这三个结点位于一条折线上,则采用双旋转 进行平衡化。
有两种基本操作,左旋和右旋
左旋——自己变为右孩子的左孩子;右旋——自己变为左孩子的右孩子;
左旋
右旋
二叉树的线索树
二叉树虽然是非线性结构,但二叉树的遍历却为二叉树的节点导出了一个线性序列。
用二叉树作为存储结构时,取到一个节点,只能获取节点的左孩子和右孩子,不能直接得到节点的任一遍历序列的前驱或者后继。
为了保存这种在遍历中需要的信息,我们利用二叉树中指向左右子树的空指针来存放节点的前驱和后继信息
利用二叉树中的空指针域来存放在某种遍历次序下的前驱和后继,这种指针叫“线索”。这种加上了线索的二叉树称为线索二叉树。
根据线索的性质的不同,线索二叉树分为:前序线索二叉树 , 中序线索二叉树 , 后序线索二叉树
前序线索树
1.写出这颗二叉树的前序遍历。
2.左标记域=0时,左指针指向前序遍历的的前驱结点。
3.右标记域=0时,右指针指向前序遍历的的后继结点。
画出对应的线索二叉树即可,PS:线索可用虚线表示。
中序线索树
1.写出这颗二叉树的中序遍历。
2.左标记域=0时,左指针指向中序遍历的的前驱结点。
3.右标记域=0时,右指针指向中序遍历的的后继结点。
画出对应的线索二叉树即可,PS:线索可用虚线表示。
后序线索树
1.写出这颗二叉树的后序遍历。
2.左标记域=0时,左指针指向后序遍历的的前驱结点。
3.右标记域=0时,右指针指向后序遍历的的后继结点。
画出对应的线索二叉树即可,PS:线索可用虚线表示。
二叉树的三种遍历
void Order(BiTree T)
{
if (T != NULL)
{
//visit(T) 前序遍历
Order(T->lchild);
//visit(T) 中序遍历
order(T->rchild);
//visit(T) 后续遍历
}
}
先序遍历
先序遍历可以想象成,小人从树根开始绕着整棵树的外围转一圈,经过结点的顺序就是先序遍历的顺序
先序遍历结果:ABDHIEJCFKG
int Inorder2(BiTree T)
{
InitStack(S); BiTree p = T;
while(p || !isEmpty(S))
{
if(p)
{
visit(p);Push(S,p);
p = p->lchild;
}else{
Pop(S,p);
p = p->rchild;
}
}
}
中序遍历
中序遍历可以想象成,按树画好的左右位置投影下来就可以了
中序遍历结果:HDIBEJAFKCG
int Inorder2(BiTree T)
{
InitStack(S); BiTree p = T;
while(p || !isEmpty(S))
{
if(p)
{
Push(S,p);
p = p->lchild;
}else{
Pop(S,p);visit(p);
p = p->rchild;
}
}
}
后序遍历
后序遍历就像是剪葡萄,我们要把一串葡萄剪成一颗一颗的。
还记得我们先序遍历绕圈的路线么?
就是围着树的外围绕一圈,如果发现一剪刀就能剪下的葡萄(必须是一颗葡萄),就把它剪下来,组成的就是后序遍历了。
后序遍历结果:HIDJEBKFGCA
层序遍历
void LevelOrder(BiTree T){
LinkQueue Q;
InitQueue(Q);
BiTree p;
EnQueue(Q,T);
while(!IsEmpty(Q)){
DeQueue(Q,p);
visit(p);
if(p->lchild!=NULL){
EnQueue(Q,p->lchild);
}
if(p->rchild!=NULL){
EnQueue(Q,p->rchild);
}
}
}
有没有发现,除了根结点和空结点,其他所有结点都有三个箭头指向它。
一个是从它的父节点指向它,一个是从它的左孩子指向它,一个是从它的右孩子指向它。 一个结点有三个箭头指向它,说明每个结点都被经过了三遍。
一遍是从它的父节点来的时候,一遍是从它的左孩子返回时,一遍是从它的右孩子返回时。
其实我们在用递归算法实现二叉树的遍历的时候,不管是先序中序还是后序,程序都是按照上面那个顺序跑遍所有结点的。
先序中序和后序唯一的不同就是,在经过结点的三次中,哪次访问(输出或者打印或者做其他操作)了这个结点。有点像大禹治水三过家门,他会选择一次进去。
先序遍历顾名思义,就是在第一次经过这个结点的时候访问了它。就是从父节点来的这个箭头的时候,访问了它。
中序遍历也和名字一样,就是在第二次经过这个结点的时候访问了它。就是从左孩子返回的这个箭头的时候,访问了它。
后序遍历,就是在第三次经过这个结点的时候访问了它。就是从右孩子返回的这个箭头的时候,访问了它。
森林、树与二叉树相互转换
思路 : 树 -> 二叉树 -> 森林
树转换为二叉树
(1)加线。在所有兄弟结点之间加一条连线。
(2)去线。树中的每个结点,只保留它与第一个孩子(长子)结点的连线,删除它与其它孩子结点之间的连线。
(3)层次调整。以树的根节点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明。(注意第一个孩子是结点的左孩子,兄弟转换过来的孩子是结点的右孩子)
二叉树转换为树
是树转换为二叉树的逆过程。
(1)加线。若某结点X的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子结点、右孩子的右孩子的右孩子结点…,都作为结点X的孩子。将结点X与这些右孩子结点用线连接起来。
(2)去线。删除原二叉树中所有结点与其右孩子结点的连线。
(3)层次调整。
二叉树转换为森林
假如一棵二叉树的根节点有右孩子,则这棵二叉树能够转换为森林,否则将转换为一棵树。
(1)从根节点开始,若右孩子存在,则把与右孩子结点的连线删除。再查看分离后的二叉树,若其根节点的右孩子存在,则连线删除…。直到所有这些根节点与右孩子的连线都删除为止。
(2)将每棵分离后的二叉树转换为树。
森林转换为二叉树
(1)把每棵树转换为二叉树。
(2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连接起来。
相关习题
求二叉树的最大深度
int height(TreeNode* T) {
if (T==NULL) return 0;
int ldep = height(T->left);
int rdep = height(T->right);
if (ldep>rdep) return ldep + 1;
else return rdep + 1;
}
求二叉树的叶子节点数
int sumOfLeftLeaves(TreeNode* root) {
if (root == NULL) return 0;
if (root->left == NULL && root->right == NULL) return 1;
int sum = sumOfLeftLeaves(root->left) + sumOfLeftLeaves(root->right);
return sum;
}
求二叉树的各个节点的和
int sumOfLeftLeaves(TreeNode* root) {
if (root == NULL) return 0;
int sum = sumOfLeftLeaves(root->left)+sumOfLeftLeaves(root->right) + root->val;
return sum;
}
图的深度优先搜索类似于树的先根遍历
图的广度优先搜索类似于树的层次遍历
连通图→深度优先搜索遍历→深度搜索生成树
连通图→广度优先搜索遍历→广度搜索生成树
Reference:
[1] 《大话数据结构》
[2] 奋力向上的猪 (博客园)
[3] YuXi_0520
[4] http://blog.csdn.net/shuangde800 D_Double
[5] 勿在浮沙筑高台blog