对Prim算法有了新的理解,现在此总结一下。我们现在主要讲的是实现部分。
Prim算法是基于MST性质所构造的算法。MST就是最小生成树一定包含U和V-U之间的轻边。(参见上一篇日志),所以Prim算法的核心还是设计一种程序,使得能够不断的找出最小生成树点集和未加入最小生成树点集之间的轻边。这个按照我们人的思维的话确实很好做。我们来举一个例子吧。看下面的图:
我们的原点是0,那么现在U集合中只有0,v-u中和0相邻接的顶点中,最小轻边是0和2之间的那条,所以选择2,现在U为0和2,之后我们继续寻找,通过穷举法我们找到了5,然后找到了3,然后找到了1,然后找到了4,最后程序结束。
按照人的思维很简单,但是按照计算机的思维该去如何设计这个程序呢?下面我简单介绍一下陈惠南编著的算法设计教材中的实现方法:
首先介绍一下顶点的数据结构,如下:
template<class T>
struct ENode{
int adjVex;
T w;
ENode* nextArc;
}
按照我的理解,其实是这样的,我们的图中节点都是按照顺序从0到n的,这个n便是这个图的size。那么现在这个图可以用存储指向这些节点的指针的一维数组来表示了,如ENode** a,第二个*表示的是指向数组的指针。是数组的首地址,并不是指向指针的指针。那么a【0】便表示0节点,其实里面的东西则是和0节点相邻接的第一个节点,adjVex就表示该节点的标号,w表示边的权,而和0相邻接的所有点我们已经用链表联系起来,通过newxArc指针我们可以继续查看下一个和0节点相邻接的那个点。那么这样的话我们可以求出和0节点相邻接的所有节点,如果我们每要加入一个顶点,都计算出和其相邻接的顶点之间的权,而前面已经加入的顶点的相邻边的权早已求出,那么我们求得这所有权的最小权,那不就是最小边了吗?想到这里我们其实已经找到了答案。
看主要算法:
好,那么现在来看一下按照我们设计的程序上面的过程是怎么走的:
template<class T>
void Prim(int k,int* nearest,T* lowcost)
{
bool * mark = new bool[n];
ENode<T>* p;
if(k<0||k>n-1) throw OutOfBounds;
for(int i=0;i<n;i++){
nearest[i] = -1;mark[i] = false;
lowcost[i] = INFTY;
}
lowcost[k] = 0;nearest[k]=k;mark[k]=true;
for(i=1;i<n;i++){//每循环一次加入一个顶点,所以这里要循环n-1次
for(p=a[k];p;p=p->nextArc){//k的值每次都在变化,他总是是上次加入的那个节点。这一步我们要把与该k节点相邻接的所有顶点的权都算出来并且加到相应的数组中
int j=p->adjVex;
if(!mark[j] && lowcost[j]>p->w){
lowcost[j]=p->w;nearest[j]=k;
}
}
T min=INFTY;
for(int j=0;j<n;j++){//从已经算出的权值中取出最小的权值,并且将相应的点加入U集合中来
if(!mark[j]&&lowcost[j]<min){
min=lowcost[j];
k=j;
}
}
mark[k] = true;
}
}
首先是和0相邻接的顶点的权都被加入数组,标号分别是123,权值分别是615,然后通过遍历找到最小的权值1,所以标号为2 的节点被加入数组中来。并且标号为2的节点的mark被设置为true,k值被设置为2,第一次循环结束
第二次循环所做的过程是一样的,1345,5754权值被加入数组,这其中还进行了比较和修改,如节点1这个时候由于和2节点的权值被计算出来,所以6权值被舍弃,取较小的权值为lowcost。然后选取轻边,k被设置为5,第二次循环结束
第三次循环便是对3的操作,过程一样,k被设置为3
第四次循环便是体现该算法出彩的地方了。这个时候和3节点相邻接的顶点的权都被计算出来了,而且也都mark 了,所以这个时候第二步的过程被省略了。第三步直接选取以前计算出来的权的最小值,所以节点1被加入。
最后节点4被加入,过程一样。算法结束
综上,Prim算法确实生动精妙啊 啊啊啊