MST_PRIM算法学习心得

时间:2022-11-25 16:45:55
MST_PRIM算法学习心得 2010-06-08 11:48 数据结构书中有一个很经典的问题,就是求n个城市之间以最节省经费建立通信网问题,它属于构造最小生成树的问题,构造最小生成树算法属Prim(普利姆)算法和Kruskal(克鲁斯卡尔)算法最为经典,当然,还有很多其它算法构造最小生成树,这里不多提了,待自己去啃资料了。 Prim算法,它的基本思想是这样的: 一个集合U表结点全集,定义两个集合V和S,S代表最小生成树结点集,V代表S的补集,从起始节点k开始访问,把k放入S中,然后取连接在集S和集V中各找出一结点组成的边,一一比较它们的大小,找出具最小权值的边,然后把该边对应的在补集V中的节点v放入S中,循环执行找最小边过程,直到最小生成树集S=U时退出循环,在这个过程当中所找出来的都是满足连接两集合的最小边,这样就保证了构成的是一棵最小生成树。 在对Prim算法基本思想粗略理解的基础上,小弟想出了一个比较直接表达该思想的算法,时间复杂度为O(n^3),算法描述如下: 1. 定义一个辅助数组flag[],用来标记该点是否存入最小生成树集S中,如果flag[i]=1,说明第i个节点已经并入S中,反之则反。 2. 算法过程函数MST_PRIM(MGraph, int),传入两个参数,第一个是用邻接矩阵表示的图MG,第二个参数表示起始节点的数据u; 3. 首先找出起始节点的编号赋予k(k=LocateVex(MG, u)),把该节点放入S中(即flag[k]=1); 4. 比较所有连接S和V的边的权值,找出具最小权值的边,(此处反映出Prim算法思想)记录该边对应集V(非最小生成树集)中结点v的编号j,把节点v放入S中; 5. 重复步骤4,直到S=U退出循环。 代码如下: //图的邻接矩阵表示结构体 typedef struct MG{ int vexs[20][2]; int adj[20][20]; int vexnum, arcnum; }MGraph; MGraph MG1; int LocateVex(MGraph G, int u) { int i; for(i=1; i<=G.vexnum; i++) { if(u==G.vexs[i][0]) return i; } return -1; } void MST_PRIM(MGraph G, int u) { int k, i, j, count=1, min,flag[20], h, l; for(i=0; i<20; i++) flag[i]=0; k = LocateVex(G, u); flag[k]=1; while(count++G.adj[h][i] && G.adj[h][i]>0 && i!=h) { min=G.adj[h][i]; j=i; l=h; } } } } printf("(%d, %d) ", l, j); flag[j]=1; } printf("/n"); } int main() { int u, i; MG1.vexnum=6; for(i=0; i<20; i++) { closedge[i].lowcost = 0; } for(i=1; i<=MG1.vexnum; i++) { MG1.vexs[i][0]=i; MG1.vexs[i][1]=0; } //十条边 MG1.arcnum=10; //给边赋权值 MG1.adj[1][2]=6; MG1.adj[2][1]=6; MG1.adj[1][3]=1; MG1.adj[3][1]=1; MG1.adj[1][4]=5; MG1.adj[4][1]=5; MG1.adj[2][3]=5; MG1.adj[3][2]=5; MG1.adj[2][5]=3; MG1.adj[5][2]=3; MG1.adj[3][5]=6; MG1.adj[5][3]=6; MG1.adj[3][6]=4; MG1.adj[6][3]=4; MG1.adj[3][4]=5; MG1.adj[4][3]=5; MG1.adj[4][6]=2; MG1.adj[6][4]=2; MG1.adj[5][6]=6; MG1.adj[6][5]=6; //从第几个点开始 printf("Enter u: "); scanf("%d", &u); MST_PRIM(MG1, u); system("pause"); return 0; } 当对上面这个程序进行调整后,可以优化成一个时间复杂度为O(n^2)的算法,变化如下: 1. 添加两个辅助数组prevVex[]和LowestC[]; 2. prevVex存储V中结点v找到最小边时对应S中的结点u(prevVex[v]=u,u为v的前驱,即u->v),lowestC存储从源结点到对应V中结点v的最小权值(lowestC[v]=adj[u][v]); 3. 如果表lowestC[]原来存在有对应的值,当进行下一轮搜索最小边时当前边权值adj[a][b]就先和原来存在的lowestC[b]值比较,如果小于原lowestC[b]值时用adj[a][b]进行覆盖,否则不改变该lowestC[b]值,并且对应结点b的prevVex[b]指向的源结点也不改变; 4. 从起始结点开始遍历,循环查找直到S=U退出循环。 代码如下: void MST_PRIM_O_2(MGraph G, int u) { int k, i, j, min, flag[20]={0}, lowestC[20]={0}, prevVex[20]={0}; //定位起始结点 k = LocateVex(G, u); flag[k]=1; //用表存起始结点所有的邻边权值 for(i=1; i<=G.vexnum; i++) { if(i!=k && G.adj[k][i]>0) { prevVex[i]=k; lowestC[i]=G.adj[k][i]; } } //从起始结点开始的最小边搜索 for(j=2; j<=G.vexnum; j++) { min=1000; //找出权值最小边,记录其编号 for(i=1; i<=G.vexnum; i++) { if(lowestC[i]>0 && min>lowestC[i] && !flag[i]) { min=lowestC[i]; k=i; } } printf("(%d, %d)", prevVex[k], G.vexs[k]); flag[k]=1; //用表存k结点的邻接边的权值 for(i=1; i<=G.vexnum; i++) { //满足三个条件: //1.结点未被访问; //2.原表中值大于对应边(k,i)的权值或者原表中值为空; //3.边存在(即权值大于0)。 if(!flag[i] && (G.adj[k][i]0) { lowestC[i]=G.adj[k][i]; prevVex[i]=k; } } } printf("/n"); } 总结: 时间复杂度为O(n^3)的算法直接对边的权值进行比较,找出满足条件的具最小权值的边,输出该边; 时间复杂度为O(n^2)的算法用一表暂存每个结点对应的经处理后的邻边的权值序列,具最小权值的边必在这个序列中,找出并输出该边。 附上数据结构课本Prim算法的代码: void MiniSpanTree_PRIM(MGraph G, int u) { int i, j, k; k = LocateVex(G, u); for(j=1; j<=G.vexnum; ++j) if(j!=k) { closedge[j].adjvex = u; closedge[j].lowcost = G.adj[k][j]; } closedge[k].lowcost = -1; for(i=2; i<=G.vexnum; i++){ k = minimum(closedge); printf("(%d, %d) ", closedge[k].adjvex, G.vexs[k]); closedge[k].lowcost = -1; for(j=1; j<=G.vexnum; j++) if(closedge[j].lowcost>=0 && G.adj[k][j]>0 && j!=k && (G.adj[k][j] < closedge[j].lowcost || closedge[j].lowcost==0)) { closedge[j].adjvex = G.vexs[k]; closedge[j].lowcost = G.adj[k][j]; } } printf("/n"); }