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");
}
MST_PRIM算法学习心得
最新推荐文章于 2022-05-08 22:06:24 发布
