上一期介绍到了kruskal算法,这个算法诞生于1956年,重难点就是如何判断是否形成回路,此处要用到并查集,不会用当然会觉得难,今天介绍的prim算法在kruskal算法之后一年(即1957年)诞生,长江后浪推前浪,前浪死在沙滩上,既然后发明,那一定有很多优点吧?具体又如何用代码实现?比kruskal算法好在哪里?不用担心,小编会一一解答。
prim算法的思想:
prim算法的主要思想就是让一棵小树不断得到新的树边,直到长成所有节点都在树集合中的大树。具体做法如下:首先先得来思考,最初的小树是什么样子的?当然是初始化成一个根节点。那么这棵小树又如何长大?此时所有的节点就只有两种情况—要么已经在集合中,要么还等待选择。无论是kruskal算法,还是prim算法,都用到了贪心算法,既然是最小生成树,那就要选择最小的边,prim算法不太一样,它所选择的是已经在集合中的节点的连向未选节点所形成的边中最小权重的边,将其未选节点添加进树边集合中。明确了这两个问题,就可以想出以下步骤:
1)构造最初的待选边表:这个表包含三种元素分别是已选节点,未选节点和相连边的权值,在最初只有一个根节点,在程序实现上,多把0节点作为根节点,所以每一个已选节点赋初值为0,那么未选节点分别为1,2,3,4…,n-1,n,假设存储图的矩阵的是二维数组map,那么权值部分则是map[0][1],map[0][2],map[0][3]…map[0][n]。
2)不断循环,直到未选节点只剩最后一个:从待选边表找到一条最短边,并加入树边集合;然后修改待选边表,在树边集合中不停找出新的节点和当前的未选节点形成连边,如果这条边比原有最小值少就修改这条边。
这样最小生成树就构建完成了,接着我们来说一说代码实现方法。
代码实现:
#include<iostream> #include<cmath> using namespace std; int n,map[1000][1000],k; struct tree{ //三元结构体 int yx; int dx; int cost; }; void prim() { int v; tree wait[1000];//待选边表 for(int i=0;i<=n-1;i++) { wait[i].yx=0; wait[i].dx=i+1; wait[i].cost=map[0][i+1];//初始化 } for(int i=0;i<=n-2;i++)//不断找到树边 { k=i; for(int j=i+1;j<n-1;j++) if(wait[j].cost<wait[k].cost) k=j;//找到最短边 swap(wait[i],wait[k]);//交换 v=wait[i].dx;//新节点 for(int j=i+1;j<n-1;j++) { if(wait[j].cost<map[v][wait[j].dx])//修改待选边表 { wait[j].cost=map[v][wait[j].dx]; wait[j].yx=v; } } } for(int i=1;i<=n;i++) { cout<<wait[i].yx<<" "<<wait[i].dx<<" "<<wait[i].cost; cout<<endl; } } int main() { cin>>n; for(int i=0;i<=n-1;i++) for(int j=0;j<=n-1;j++) cin>>map[i][j]; //do something prim(); return 0; }
正常样例图示:
//此图是从mooc上copy来的
一般情况下待选边表长这样。
Prim算法的优点:
比起kruskal算法来讲,prim算法可以不用判断是否产生回路,因为在待选边表中不停计算的过程中,可以有效避免产生回路的情况,可以不学并查集(有时还要学习堆),快速恶补,并且适用于稠密图,其时间复杂度只与节点数量有关,在特定情况下可以更快的执行完程序。
Prim算法 的缺点:
prim算法适用于稠密图,但是稀疏图还是kruskal算法更胜一筹,并且prim算法更难理解,小编现在还有点懵,写两道题就好了,下棋将会带来最小生成树例题题解。
欲知后事如何,且听下回分解……
专栏: