二叉树遍历
- DLR–前序遍历(根在前,从左往右,一棵树的根永远在左子树前面,左子树又永远在右子树前面 )
- LDR–中序遍历(根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面)
- LRD–后序遍历(根在后,从左往右,一棵树的左子树永远在右子树前面,右子树永远在根前面)
先序遍历
中序遍历
后序遍历
深搜广搜
BFS:这是一种基于队列这种数据结构的搜索方式,它的特点是由每一个状态可以扩展出许多状态,然后再以此扩展,直到找到目标状态或者队列中头尾指针相遇,即队列中所有状态都已处理完毕。
DFS:基于递归的搜索方式,它的特点是由一个状态拓展一个状态,然后不停拓展,直到找到目标或者无法继续拓展结束一个状态的递归。
优缺点:
BFS:对于解决最短或最少问题特别有效,而且寻找深度小,但缺点是内存耗费量大(需要开大量的数组单元用来存储状态)。
DFS:对于解决遍历和求所有问题有效,对于问题搜索深度小的时候处理速度迅速,然而在深度很大的情况下效率不高
1.广度优先搜索(BFS)
广度优先搜索在进一步遍历图中顶点之前,先访问当前顶点的所有邻接结点。
a .首先选择一个顶点作为起始结点,并将其染成灰色,其余结点为白色。
b. 将起始结点放入队列中。
c. 从队列首部选出一个顶点,并找出所有与之邻接的结点,将找到的邻接结点放入队列尾部,将已访问过结点涂成黑色,没访问过的结点是白色。如果顶点的颜色是灰色,表示已经发现并且放入了队列,如果顶点的颜色是白色,表示还没有发现。
d. 按照同样的方法处理队列中的下一个结点。
基本就是出队的顶点变成黑色,在队列里的是灰色,还没入队的是白色。
用一副图来表达这个流程如下:
1.初始状态,从顶点1开始,队列={1}
2.访问1的邻接顶点,1出队变黑,2,3入队,队列={2,3,}
3.访问2的邻接结点,2出队,4入队,队列={3,4}
4.访问3的邻接结点,3出队,队列={4}
5.访问4的邻接结点,4出队,队列={ 空}
从顶点1开始进行广度优先搜索:
初始状态,从顶点1开始,队列={1}
访问1的邻接顶点,1出队变黑,2,3入队,队列={2,3,}
访问2的邻接结点,2出队,4入队,队列={3,4}
访问3的邻接结点,3出队,队列={4}
访问4的邻接结点,4出队,队列={ 空}
结点5对于1来说不可达。
2.深度优先搜索(DFS)
深度优先搜索在搜索过程中访问某个顶点后,需要递归地访问此顶点的所有未访问过的相邻顶点。
初始条件下所有节点为白色,选择一个作为起始顶点,按照如下步骤遍历:
a. 选择起始顶点涂成灰色,表示还未访问
b. 从该顶点的邻接顶点中选择一个,继续这个过程(即再寻找邻接结点的邻接结点),一直深入下去,直到一个顶点没有邻接结点了,涂黑它,表示访问过了
c. 回溯到这个涂黑顶点的上一层顶点,再找这个上一层顶点的其余邻接结点,继续如上操作,如果所有邻接结点往下都访问过了,就把自己涂黑,再回溯到更上一层。
d. 上一层继续做如上操作,知道所有顶点都访问过。
用图可以更清楚的表达这个过程:(注意:图画错了,请将3->4这条路径理解成4->3)
1.初始状态,从顶点1开始
2.依次访问过顶点1,2,3后,终止于顶点3(注意,是4->3)
3.从顶点3回溯到顶点2,继续访问顶点5,并且终止于顶点5
4.从顶点5回溯到顶点2,并且终止于顶点2
5.从顶点2回溯到顶点1,并终止于顶点1
6.从顶点4开始访问,并终止于顶点4
从顶点1开始做深度搜索:
初始状态,从顶点1开始
依次访问过顶点1,2,3后,终止于顶点3(再次提醒,4->3)
从顶点3回溯到顶点2,继续访问顶点5,并且终止于顶点5
从顶点5回溯到顶点2,并且终止于顶点2
从顶点2回溯到顶点1,并终止于顶点1
从顶点4开始访问,并终止于顶点4
数组元素的地址计算公式
(1) 按行优先顺序存储的二维数组Amn地址计算公式
LOC(aij)=LOC(a11)+[(i-1)×n+j-1]×d
其中:
LOC(a11)是开始结点的存放地址(即基地址)
d为每个元素所占的存储单元数 由地址计算公式可得,数组中任一元素可通过地址公式在相同时间内存取。即顺序存储的数组是随机存取结构。
(2) 按列优先顺序存储的二维数组Amn地址计算公式
LOC(aij)=LOC(a11)+[(j-1)×m+i-1]×d
森林二叉树转换
二叉树转换成森林
森林 | 对应 | 二叉树 |
---|---|---|
第一棵树的根 | -> | 二叉树的根 |
第一棵树的子树森林 | -> | 二叉树的左子树 |
森林中其它的树 | -> | 二叉树的右子树 |
森林转化成二叉树
二叉树 | 对应 | 森林 |
---|---|---|
根节点 | -> | 第一棵树根 |
左子树按照同样的转换规则得到的森林 | -> | 第一棵树根的子树森林 |
右子树按照同样的转换规则得到的森林 | -> | 整个森林中第2棵树到其它子树 |
深度优先搜索的生成森林
其实在对无向图进行遍历的时候,遍历过程中所经历过的图中的顶点和边的组合,就是图的生成树或者生成森林。
无向图
连通图的生成森林
例如,图 1 中的无向图是由 V1~V7 的顶点和编号分别为 a~i 的边组成。当使用深度优先搜索算法时,假设 V1 作为遍历的起始点,涉及到的顶点和边的遍历顺序为(不唯一):
此种遍历顺序构建的生成树为:
深度优先生成树
由深度优先搜索得到的树为深度优先生成树
。同理,广度优先搜索生成的树为广度优先生成树
,图 1 无向图以顶点 V1 为起始点进行广度优先搜索遍历得到的树,如图 3 所示:
广度优先生成树
非连通图的生成森林
例如,对图 4 中的非连通图 (a) 采用深度优先搜索算法遍历时,得到的深度优先生成森林(由 3 个深度优先生成树构成)如 (b) 所示(不唯一)。
非连通图在遍历生成森林时,可以采用孩子兄弟表示法将森林转化为一整棵二叉树进行存储。
拿图 4(a)中的非连通图为例,构建的深度优先生成森林,使用孩子兄弟表示法表示为:
图中,3 种颜色的树各代表一棵深度优先生成树,使用孩子兄弟表示法表示,也就是将三棵树的树根相连,第一棵树的树根作为整棵树的树根。
非连通图采用广度优先搜索算法进行遍历时,经过的顶点以及边的集合为该图的广度优先生成森林。
拿图 4(a)中的非连通图为例,通过广度优先搜索得到的广度优先生成森林用孩子兄弟表示法为:
画出带权无向图的邻接矩阵,并按照Dijksta算法求顶点到其他各点的最短路径
图的存储结构主要分两种,一种是邻接矩阵,一种是邻接表。
邻接矩阵
邻接矩阵的存储方式是用两个数组来表示图。一个一维数组储存图中顶点信息,一个二维数组储存图中的边或弧的信息。
无向图
这里右图是把顶点作为矩阵内行和列的标头罗列出来。如果在两个顶点之间存在一条边,那么就把 1 放在这个位置上。如果边不存在,那么就赋值为 0。
从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。
有向图
上图图右是一个有向图。有向图是看入度和出度的(顶点的入边数和出边数),例如V2,就有2个出边。列代表着顶点的入边,列的元素相加代表顶点的入度,而行则代表该顶点所有的出边,元素相加则为顶点的出度。假如V2->V1的这个关系要写到邻接矩阵,首先先找到V1的列,然后在对应V2行的位置写上一个1,就可以了。
网络
网络是一个带权图。邻接矩阵如下图:
有两种表示方法,没有边的两个顶点,可以用0表示,也可以用最大值表示。
邻接表
邻接矩阵是不错的一种图存储结构,但是,对于边数相对顶点较少的图,这种结构存在对存储空间的极大浪费。因此,找到一种数组与链表相结合的存储方法称为邻接表。
邻接表的处理方法是这样的:
1)图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。
2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表(只管顶点的出边不管入边)。
最短路算法
prim算法
prim算法用于寻找图中的最小生成树,总体思路如下:
- 开始时,从图中任取一个节点作为初始点,另作一“距离表”存放其他所有节点到已访问节点的直接距离,初始值即为到初始点的距离。
- 接着找出距离已访问节点集最近的节点,加入已访问节点。此时要用新节点和其他节点的距离来更新“距离表”中已访问节点集和各未访问节点的距离。
- 重复2中操作直到已访问节点集中的节点数量时全部为止。
kruskal算法
kruskal十分好理解,一句话可以概括:在不产生环的前提下,每次找最短的边,将两端节点加入,直到找齐所有的点。
注:prim算法和顶点数目有关,适用于稠密图;而kruskal和边数有关,适用于稀疏图。
Dijkstra算法
Dijkstra算法是用来求单源最短路径的(一个顶点到其他各个顶点的最短距离)。它也有两个“距离表”:G用来存放两两点之间的距离,res存放各个点到起点的距离。
总体思路:
- 每次将距离起点最近的点mini加入已访问节点集v
- 接着再用res[i]+G[i][uu]来更新未访问节点距离起始点的距离。
- 不断循环1、2两步直到找不到新节点为止。
dijksta和prim算法有些类似,但也有明显不同:prim算法中res保存的是未访问点集到已访问点集的距离,而dijkstra中res保存的是未访问点集到起始点的距离。
Floyd算法
对每个点,用它来松弛其他各两点的距离。
哈夫曼树
赫夫曼树(Huffman Tree),又称最优二叉树,是一类带权路径长度最短的树。假设有n个权值{w1,w2,…,wn},如果构造一棵有n个叶子节点的二叉树,而这n个叶子节点的权值是{w1,w2,…,wn},则所构造出的带权路径长度最小的二叉树就被称为赫夫曼树。
特性:
-
对于同一组权值,所能得到的赫夫曼树不一定是唯一的。
-
赫夫曼树的左右子树可以互换,因为这并不影响树的带权路径长度。
-
带权值的节点都是叶子节点,不带权值的节点都是某棵子二叉树的根节点。
-
权值越大的节点越靠近赫夫曼树的根节点,权值越小的节点越远离赫夫曼树的根节点。
-
赫夫曼树中只有叶子节点和度为2的节点,没有度为1的节点。
-
一棵有n个叶子节点的赫夫曼树共有2n-1个节点。
Huffman Tree的构建
赫夫曼树的构建步骤如下:
1、将给定的n个权值看做n棵只有根节点(无左右孩子)的二叉树,组成一个集合HT,每棵树的权值为该节点的权值。
2、从集合HT中选出2棵权值最小的二叉树,组成一棵新的二叉树,其权值为这2棵二叉树的权值之和。
3、将步骤2中选出的2棵二叉树从集合HT中删去,同时将步骤2中新得到的二叉树加入到集合HT中。
4、重复步骤2和步骤3,直到集合HT中只含一棵树,这棵树便是赫夫曼树。
假如给定如下5个权值:
则按照以上步骤,可以构造出如下面左图所示的赫夫曼树,当然也可能构造出如下面右图所示的赫夫曼树,这并不是唯一的。
Huffman编码
左分支编码为字符0,右分支编码为字符1
我们根据上面左图可以得到各叶子节点的赫夫曼编码如下:
权值为5的节点的赫夫曼编码为:11
权值为4的节点的赫夫曼编码为:10
权值为3的节点的赫夫曼编码为:00
权值为2的节点的赫夫曼编码为:011
权值为1的节点的赫夫曼编码为:010
而对于上面右图,则可以得到各叶子节点的赫夫曼编码如下:
权值为5的节点的赫夫曼编码为:00
权值为4的节点的赫夫曼编码为:01
权值为3的节点的赫夫曼编码为:10
权值为2的节点的赫夫曼编码为:110
权值为1的节点的赫夫曼编码为:111
最小带权路径长度(左图):WPL=2 * 3+3 * 1+3 * 2+2 * 4+2 * 5=33
最小带权路径长度(右图):WPL=2 * 5+2 * 4+2 * 3+3 * 2+3 * 1=33
由此可见,不管是左子树权值大于右子树权值还是小于右子树权值的哈夫曼树的最小带权路径长度不变。