由于学的最小生成树比较浅显,所写的文章可能也比较粗略,跪求大佬能指导。
首先先了解生成树的概念,什么是生成树呢?生成树可不是树,而是包含图中全部顶点的极小连通子图罢了,就是长得像棵树,而且在生成树中添加任意一条属于原图中的边必定会产生回路。而最小生成树则需要提到权值,权值是无向图生成树上各边的权值之和称为该生成树的代价,则最小生成树就是代价最小的生成树。如果各边权值不理解的话,咱大概可以理解为两点之间的距离,然后代价最小就是你走过所有点的最短距离之和(类似这种意思)。知道了最小生成树的概念过后,咱就来说说Prim算法吧。
1.Prim算法(此处用到的储存结构为邻接矩阵)
官方的解释大概就是先从一开始的初始状态(即为选中一个顶点O加入到一个点的集合U中),从该顶点O开始,重复执行:1.找到该顶点O所有邻接点中连权值最小(也就是所谓顶点到该邻接点的最短边)的邻接点P 。2.将这个邻接点P归入到U中,且将该邻接点的权值赋为0(也就是O,P现在为一个整体)。3.再同时以O,P为出发点寻找下一个所连权值最小的邻接点。以此类推,直到将全部点都走完(记得看生成树的概念)。
而我大概理解就是先找到一个大哥,由该大哥O去收复地盘,首先先找最弱的,花费的时间和力气也是最少的。然后大哥O的地盘扩大以后,再通过新地盘的小弟P和大哥自己O再去寻找最弱的,慢慢的,大哥O地盘越来越大,直到把所有地盘都占领,而且花费的总时间和力气是最少的(和秦王统一六国有异曲同工吧,只是没小弟hhhh)。下面实现的代码:
我在这里就默认它从O = 0开始了,如果想自己输入,给Prim里参数就行。
void MGraph::Prim()
{
int v = 0;
int i, j, k;
int adj[MaxSize], low[MaxSize]; //邻接点的集合,权值的集合
for(int i = 0;i < vertexNum;i++)
{
low[i] = edge[v][i]; //把之前构造函数中输入的权值给进来,方便下面作比较
adj[i] = v; //将所有点的邻接点都先设为顶点O
}
low[v] = 0; //将顶点O归入到集合U中
for(k = 1;k < vertexNum;k++)
{
j = MinEdge(low, vertexNum); //找到顶点O的邻接点P中权值最小的点,返回该点的下标
cout << vertex[adj[j]] << " "<< vertex[j] << " " << low[j];
low[j] = 0; //将该点P归入到集合U中
for(i = 0;i < vertexNum;i++)
{
if(edge[i][j] < low[i]) // 找到下一个邻接点Q
{
low[i] = edge[i][j];
adj[i] = j;
}
}
}
}
中间有个查找权值最小的算法,我也给出
int MinEdge(int low[], int vertexNum)
{
int Weight = 10000; // hhhh权值只要大点就行了,随便设的
int x = 0;
for(int i = 0;i < vertexNum;i++)
{
if(low[i] < Weight && low[i] != 0) //这if语句块的意思就是找权值最小,然后因为有个for循环,就可以一直找,直到循环结束,找到权值最小,并且返回它的位置
{
Weight = low[i];
x = i;
}
}
return x;
}
分析一下Prim算法,假设连通网有n个顶点,在Prim的那段代码中的第一个循环需要走n次,因为要将每个点的邻接点都设为顶点O;而在第二个循环中只走n-1次,是因为,顶点O本身已经存在于集合中,所以不需要走自己本身。
2.Kruskal算法(此处用到的是边集数组存储)
同样的,官方说法为:1.先初始化数组,将不同的顶点各自构成一个连通分量,然后按照边的权值由小到大排序,然后进行考察。2.若被考察边的两个顶点属于两个不同的连通分量,将这条被考察边加入到集合TE中,然后同时把两个连通分量连接为一个连通分量;若这条被考察边属于同一个连通分量,则舍去此边。步骤2重复执行,来实现Kruskal算法。
鄙人认为啊,K算法主要就是先将各个边权值进行排序,然后点与点之间,按照边权值最小,该边的两个顶点进行相连,则获得一个连通分量,继续该步骤得到第二个连通分量,判断这两个连通分量是否位于同一个集合,如果不是同一集合则合并集合,否则就去除一个连通分量。但是怎么实现呢,此处用到了并查集,所谓并查集书上的意思就是将集合中的元素组织成树的形式输出,即将一个集合的根结点作为另一个集合根结点的孩子。要使得并查集使用方便,则树可才用双亲表示法来储存,因为该方法可以追溯到树中每个结点的根,然后达到合并的效果。也可顺藤摸瓜,来找到它的孩子结点。
1.首先要做的就是先将边集数组根据点与点之间的边权值由小到大进行排序,并存放点与点之间的信息。
2.初始化并查集中双亲表示法存储数组,就相当于初始化各个连通分量。3.找到权值最小的边以及所连的顶点,先找寻所连顶点的根结点,若根结点即为自己本身,则继续步骤3;若有根结点,则返回根结点,进行步骤3。
3.判断两个结点是否位于同一集合,若位于不同集合,则合并两个顶点与同一集合中。否则对其不进行处理。此处不进行处理的意义是舍去一样的连通分量,以免构成回路。
在此说明,这部分代码源于书上!!!!
实现Kruskal算法处的代码如下:
void Kruskal
{
int num = 0;
int i;
int vex1, vex2;
int parent[vertexNum];
for(i = 0;i <vertexNum;i++)
{
parent[i] = -1; //初始化各个连通分量
}
for(num = 0;i = 0;num < vertexNum - 1;i++) //寻找权值最小的边
{
vex1 = FindRoot(parent, edge[i].from);
vex2 = FindRoot(parent, edge[i].to);
if(vex1 != vex2) //比较两个结点是否位于同一集合
{
cout << edge[i].from << " " << edge[i].to << " " << edge[i].weight;
parent[vex2] = vex1; //将vex1的孩子结点变为vex2达到合并的目的
num++;
}
}
}
查找根结点的算法如下:
int FindRoot(int parent[], int v) //寻找顶点的根结点,若无,则返回自己本身;若有,则追溯到最源头的根结点
{
int t = v;
while(parent[t] > -1)
{
t = parent[t];
}
return t;
}
总结:
以上就是对于两种算法的大概理解,肯定是比较浅显的,希望大佬可以私信与我交流(我在这方面真的就是入门级别),我能够学习到更多关于这部分算法的奥秘。总的来说,通过二者所需要的经历的步骤及时间复杂度可看出,P算法适用于求稠密网的最小生成树,而K算法则适用于求简单图稀疏网的最小生成树。应根据实际情况来选取适合的算法来求最小生成树,希望大家能够多多支持小菜鸟!!!有什么问题以及建议可以私信我。