生成树:连通图包含全部顶点的一个极小连通子图
这里需要注意的是是一个极小连通子图
上面第一个是一个连通图,右侧两个图是它的生成树,它们包含了全部顶点且是极小连通子图,如果我们把图2中A和D链接起来他就不是一个生成树了,因为他不是极小的,如果我们把图3中的C和D中的边去掉,他也不是生成树了,因为他不是连通的。
如上图,我们把图的边加上权值他就叫做网,我们找出它的两个上生成树,其中图2的所有边的权值的和为:2+2+5+6=15,图3的所有边的权值之和是:1+2+4+5=12,可以看出图3的权值之和最小,则图3就是最小生成树。
最小生成树:对于带权的无向连通图G=(V,E),G的所有生成树当中边的权值之和最小的生成树为G的最小生成树(MST)
注意是带权的无向连通图,因为带权了才有最小的概念,只有无向连通图才有生成树
不一定唯一:最小生成树一定只有一种吗?
如上图中的图2和图3他们都是最小生成树,所以最小生成树不一定唯一。
这是不一定,所有也有唯一的情况,比如:
性质
我们根据贪心算法设计了如下最小生成树算法(贪心:每一步尽量做出最好的选择)
//伪代码GENRIC_MST(G){ T=NULL; while T //未形成一棵生成树 do //找到一条最小代价边(u,v)并且加入T后不会产生回路 T=Tu(u,v);}
两种最小生成树算法:Prim、Kruskal
初始化:向空的结果树T=(Vt,Et)中添加图G=(V,E)的任一顶点u0,使Vt={u0},Et为空集;
循环(直到Vt=V):从图G中选择满足{(u,v)|u∈Vt,v∈V-Vt}且具有最小权值的边(u,v),并置Vt=VtU{v},Et=EtU{(u,v)}
{(u,v)|u∈Vt,v∈V-Vt} 相当于将已经添加到结果集中的结点和未添加进来的结点进行连接,这种方法就避免了生成的生成树出现回路
且具有最小权值的边:这个就是为了我们的最终目的啦,生成树的权值之和最小,即最小生成树
Vt=VtU{v},Et=EtU{(u,v)} 将新添加的顶点添加到结果的顶点集中,边添加到结果的边集中
使用Prim算法生成最小生成树的过程:首先假设我们选择A作为初始顶点,然后我们从剩下的:B、C、E、D中选择一个顶点组成一条边,满足这样的边有三个:AB、AC、AE,并且我们需要选择该权值最小的那个,可以发现是AC边
接着我们继续找,此时可以选择的顶点有三个B、E、D,可以组成的边有:AB、AE、CB、CE、CD,我们任然选择权值最小的那个CB边
我们继续找,此时可以选择的顶点有D和E,可以组成的边有:AE、CE、CD(注意这里没有AB,因为AB边会形成通路,当然我们可选择的顶点也没有B),任然选择权值最小的那个AE
接着我们继续找,此时可以选择顶点只有D,可以组成的边只有CD
此时最小生成树构成成功,Prim算法伪代码,算法思想
void Prim(G,T){ T = 空集;//结果集 U = {w};//将w顶点加入结果的顶点集中 while((V-U)!=空集){ 设(u,v)是使u∈(V-U),且权值最小的边; T=TuP{(u,v)}; U=Uu{V}; }}
我需要两个辅助数组将这个伪代码转成算法代码:min_weight[n],adjvex[n]
我们仍然使用上图作为例子,首先选A作为初始顶点,然后我们要初始化两个数组:
这里有一个无穷大的原因是A到D是没有边的,在实际算法中令这个无穷大为一个比较大的整数就可以了
接着我们同样按照上面的过程找和A顶点组成的边的权值最小的点C,接着我们此时要修改min_weight数组了
我们可以看到将3改成了2,因为在此时结果顶点集中可以存在一条从C到B的边它的权值为2比之前的AB3更小,同样修改∞为5,因为也存一条CE边,它的权值为5,因为CE的权值为6,所以4我们不需要修改,同时我们将1改成了0,因为C顶点已经被取走了,这里其中adjvex数组的值也进行修改了,我们修改adjvex[2]=0(2是c顶点的下标),因为A顶点的下标为0,所以看不出来。
同样我们继续挑选权值最小的边BC,同样修改辅助数组此时我们已经将A、B、C三个顶点选走所数组的前三个值为0,因为B顶点是由C顶点挑选走的,所以修改adjvex=[1]=2(1代表B顶点下标,2代表C顶点下标),由于不存在权值更小的边,所以不修改数组的值
我们继续寻找顶点,找到AE,同样我们修改此时修改min_weight=[4]=0,同时修改adjvex[4]=0,同样我们继续寻找找到CD边,我们修改min_weight[3]=0,修改adjvex[3]=2
最终ming_weight数组中的值全部为0,说明所有顶点都已经被选择
代码实现,时间复杂度O(V^2) ,该算法的时间复杂度和边没有关系,所以他比较适合稠密图
void MST_Prim(Graph G){ //传入图G int min_weight[G.vexnum];//数组大小为顶点的数量 int adjvex[G.vexnum];//数组大小为顶点的数量 for(int i=0; i
初始化:Vt=V,Et=空集,即是每个顶点构成一颗独立的树,T是一个仅含V个顶点的森林;
循环(直到T为树):按图G的边的权值递增的顺序依次从E-Et中选择一条边,若这条边加入后不构成回路,则将其加入Et,否则舍弃。
首先将这个n个顶点放到结果集,这n个顶点构成了n棵树组成的森林,然后我们将图G的边的权值按照递增的顺序排序,接着从头挑选权值最小的那条边AC发现不构成回路,则可以加入Et中,同样我们继续挑选BC这条边不构成回路可以加入,接着是AB这条边,但是它会构成回路,不加入.......最后形成最小生成树
Kruskal主要使用了堆排序对边的权重进行排序,然后使用并查集(数组)这个数据挑选边。代码实现
代码实现,时间复杂度:O(E*logE),适用于稀疏图
typedef struct Edge{ int a,b;//改变两个端点的下标 int weight;//权重};void MST_Kruskal(Graph G,Edge *edges,int *parent){//图G、属于该图的边的集合、并查集辅助变量 heap_sort(edges);//堆排序 Initial(parent);//初始化并查集 for(int i=0;i
我们之前学过无权图单源最短路径问题,那是无权图,我们找最短路径只需找出边最少的就行,但是这里我们讨论的是带权的最短路径,此时就不能只看边了。
最短路径:两个顶点之前带权路径长度最短的路径为最短路径
在带权图中,把一个顶点v到另一个顶点u所经历的权值之和称为,路径的带权路径长度
我们有两种算法:Dijkstra 、Floyd
Dijkstra(迪杰斯特拉):计算带权图单源最短路径。我们需要以下几个辅助数组:
实现过程:
举个栗子:我们描述出下图的Dijstra的算法执行过程
首先我们设置源点为0,接着我们初始化三个辅助数组,其中dist[]存储的是0到每一个顶点的权值因为03不存在边,所以存储∞,s[0]=1表示0这个顶点已经计算完成,path[]初始化代表如果存在顶点0到另一个顶点有一条有向边,则设置值为顶点0的下标为0,否则为-1,初始化完成,开始执行具体的执行过程
第一轮:首先我们挑选dist[]中最小的值,我们知道是3,它对应的顶点为2,所以我们挑选2这个顶点,挑选完成后我们需要修改dist[]数组,挑选2完成之后会带来新的有向边,我知道04的权值是8,但是加入2之后存在024这样的一条有向边它的权值是7,所以我们需要修改dist[4]=7,同时由于2顶点计算过了,所以我们修改s[2]=1,同时由于此时我们是通过2顶点到达的4顶点所以修改paht[4]=2
第二轮:依旧挑选最小的那个值5(0和3值对应的顶点已经计算过了),它是1顶点对应的值,加入1顶点后带来了12和13边,对应的我们要修改dist[]的权值,这里我们只需修改从0到3的权值即可,因为从0-1-2的权值为7,而从0-2的权值为3,7>3不需要修改。同时修改s[1]=1,然后修改paht[3]=1,因为3顶点我们是通过1顶点抵达,权值为下标为1
第三轮:我们挑选到3这个顶点(0、3和5值对应的顶点已经计算过了),3这个顶点加入后会带来32这条边,所以但是2顶点已经计算完成了,所以不需要修改dist[]数组,然后我们修改s[3]=1,同样path[]数组不需要修改
第四轮:只剩下值为7这个顶点4未被挑选了,4顶点加入后为未带来新的边,所以不修改dist[]数组,修改s[4]=1
最后遍历完成,最后我们得到s[]数组值都为1,表示所有顶点计算完成,同时dist[]数组代表顶点0到其他顶点的最短路径长度,比如5代表了0-1的最短路径长度。我们还知道path[]数组保存了最短路径经历的序列,那么我们是怎么通过path[]数组计算路径序列的呢?比如我们计算0->1路径的序列,它的长度为5,序列:首先1对应的值为0,说明它的前驱结点的下标为0,paht[0]=-1,所以结束,序列为:0->1;计算0->2路径的序列,他的长度为3,2对应的值为0,他的前驱结点为下标为0,paht[0]=-1,所以结束,序列为:0->2;计算0->3路径的序列,它的长度为6,3对应的值为1,则说明它的前驱结点的下标为1,path[1]=0,path[0]=-1,所以结束,序列为:0->1->3;......
代码实现,时间复杂度O(V^2)
注意:Dijkstra算法并不适用于含有负权值边的图
Floyd(弗洛伊德算法):计算各个顶点之间的最短路径
也就是求任意两个点之间的最短路径。这个问题这也被称为“多源最短路径”问题。
如上图,我们使用Floyd算法,进行计算各个顶点之间的最最短路径,首先进行初始化,两个顶点之间有路径的值为权值,没有路径的值为∞。
接着我们加入第一个顶点0顶点,我们随意找一个一条边比如A^(-1)[2][1],它的值为∞,表示从2顶点到1顶点没有边,然后加入0顶点后,我我们知道从2顶点到0顶点也没有边值为∞,从0顶点到1顶点有边值为1,∞+1还是∞,所以它的值A^(-1)[2][1]的值不进行更改,按照这样的方法将整个矩阵遍历一遍得到下面的矩阵:
接着我们继续加入顶点,加入顶点1,我们仍然按照上面的方法进行计算,得到A^(1)矩阵:
任然按照上面的方法得到A^(2)和A^(3)矩阵,其中A^(3)矩阵中的值就是各个顶点之间的最短路径
代码实现,时间复杂度:O(V^3)
整个算法的思想也可以参考这个博客 https://www.cnblogs.com/wangyuliang/p/9216365.htm
数据结构相关知识,公众号理木客同步更新中,欢迎关注