prim算法

Prim算法的执行非常类似于寻找图的最短通路的Dijkstra算法。Prim算法的特点是集合A中的边总是只形成单棵树。如图5所示,阴影覆盖的边属于正在生成的树,树中的结点为黑色。在算法的每一步,树中的结点确定了图的一个割,并且通过该割的轻边被加进树中。树从任意根结点r开始形成并逐渐生长直至该树跨越了V中的所有结点。在每一步,连接A中某结点到V-A中某结点的轻边被加入到树中,由推论2,该规则仅加大对A安全的边,因此当算法终止时,A中的边就成为一棵最小生成树。因为每次添加到树中的边都是使树的权尽可能小的边,因此上述策略也是贪心的。

有效实现Prim算法的关键是设法较容易地选择一条新的边添加到由A的边所形成的树中,在下面的伪代码中,算法的输入是连通图G和将生成的最小生成树的根r。在算法执行过程中,不在树中的所有结点都驻留于优先级基于key域的队列Q中。对每个结点v,key[v]是连接v到树中结点的边所具有的最小权值;按常规,若不存在这样的边则key[v]=∞。域p[v]说明树中v的“父母”。在算法执行中,GENERIC-MST的集合A隐含地满足:

A={(v,p[v])|vÎV-{r}-Q}

当算法终止时,优先队列Q为空,因此G的最小生成树A满足:

A={(v,p[v])|vÎ V-{r}}

 

(a)

(b)

(c)

(d)

(e)

(f)

(g)

(h)

(i)

 

图5 Prim算法在图1所示的图上的执行流程

 MST-PRIM(G,w,r)
1. Q←V[G]
2. for 每个uÎQ
3.   do key[u]←∞
4. key[r]←0
5. p[r]←NIL
6. while Q≠Æ
7.   do u←EXTRACT-MIN(Q)
8.      for 每个vÎAdj[u]
9.        do if vÎQ and w(u,v)<key[v]
10.            then p[v]←u
11.                 key[v]←w(u,v)

Prim算法的工作流程如图5所示。第1-4行初始化优先队列Q使其包含所有结点,置每个结点的key域为∞(除根r以外),r的key域被置为0。第5行初始化p[r]的值为NIL,这是由于r没有父母。在整个算法中,集合V-Q包含正在生长的树中的结点。第7行识别出与通过割(V-Q,Q)的一条轻边相关联的结点uÎQ(第一次迭代例外,根据第4行这时u=r)。从集合Q中去掉u后把它加入到树的结点集合V-Q中。第8-11行对与u邻接且不在树中的每个结点v的key域和p域进行更新,这样的更新保证key[v]=w(v,p[v])且(v,p[v])是连接v到树中某结点的一条轻边。

Prim算法的性能取决于我们如何实现优先队列Q。若用二叉堆来实现Q,我们可以使用过程BUILD-HEAP来实现第1-4行的初始化部分,其运行时间为O(V)。循环需执行|V|次,且由于每次EXTRACT-MIN操作需要O(㏒V)的时间,所以对EXTRACT-MIN的全部调用所占用的时间为O(V㏒V)。第8-11行的for循环总共要执行O(E)次,这是因为所有邻接表的长度和为2|E|。在for循环内部,第9行对队列Q的成员条件进行测试可以在常数时间内完成,这是由于我们可以为每个结点空出1位(bit)的空间来记录该结点是否在队列Q中,并在该结点被移出队列时随时对该位进行更新。第11行的赋值语句隐含一个对堆进行的DECREASE-KEY操作,该操作在堆上可用0(㏒V)的时间完成。因此,Prim算法的整个运行时间为O(V㏒V+E㏒V)=O(E㏒V),从渐近意义上来说,它和实现Kruskal算法的运行时间相同。

通过使用Fibonacci堆,Prim算法的渐近意义上的运行时间可得到改进。在Fibonacci堆中我们己经说明,如果|V|个元素被组织成Fibonacci堆,可以在O(㏒V)的平摊时间内完成EXTRACT-MIN操作,在O(1)的平摊时间里完成DECREASE-KEY操作(为实现第11行的代码),因此,若我们用Fibonacci堆来实现优先队列Q,Prim算法的运行时间可以改进为O(E+V㏒V)。

 

算法实现如下:

 

/*
    Write By LiveStar (2008.07.23)
*/

#include 
< stdio.h >
// max表示点,边的最大个数
#define  max 1000
// wq表示两点的距离为无穷
#define  wq 9999
// 邻接矩阵存储相应的边的权重
int  mix[max][max];

// 输入并构造邻接矩阵
void  input( int  n, int  m)
{
    
int i,j,sp,ep,wg;
    
//初始化邻接矩阵每个都是无穷大
    for (i=0;i<n;i++)
        
for (j=0;j<n;j++)
            mix[i][j]
=wq;
    printf(
"请输入边的起点、终点、权重(EX:1 2 3):/n");
    
for (i=0;i<m;i++)
    
{
        scanf(
"%d %d %d",&sp,&ep,&wg);
        
//无向连通图
        mix[sp][ep]=wg;
        mix[ep][sp]
=wg;
    }

}


// 显示邻接矩阵
void  output( int  n, int  m)
{
    
int i,j;
    printf(
"/n邻接矩阵如下:/n/n");
    
for (i=0;i<n;i++)
    
{
        
for (j=0;j<n;j++)
            printf(
"%d/t",mix[i][j]);
        printf(
"/n");
    }

}


// prim算法实现
void  prim( int  n, int  r)
{
    
//a[max]用来存放各个点到已经标记点的集合的最短距离
    int a[max];
    
//b[max]用来记录每个点的标记状态,false表示还没标记。
    bool b[max];
    
int i,j,k,min;
    
//初始化从根节点开始
    for (i=0;i<n;i++)
    
{
        a[i]
=mix[r][i];
        b[i]
=false;
    }

    b[r]
=true;
    printf(
"/n依次被标记的点及相应边的权重:/n");
    printf(
"%d/t%d/n",r,0);    
    
for (i=0;i<n-1;i++)//还剩n-1个点
    {
        k
=0;
        min
=wq;
        
for (j=0;j<n;j++)
        
{
            
//第j个点到已经标记点的集合的距离最小且这个点还没有被标记
            
//k记录下一个将被标记的点
            if (a[j]<min&&b[j]==false)
            
{
                min
=a[j];
                k
=j;
            }

        }

        b[k]
=true;//标记节点k
        printf("%d/t%d/n",k,min);
        
//更新a[]里的状态,时刻保持最新的状态
        
//存放各个点到已经标记点的集合的最短距离
        for (j=0;j<n;j++)
        
{
            
if (mix[k][j]<a[j])
            
{
                a[j]
=mix[k][j];
            }

        }

    }

}


int  main()
{
    
//n是点的个数 ,m是边的个数
    int n,m,r;
    printf(
"请输入点的个数和边的个数(EX:1 3):/n");
    scanf(
"%d %d",&n,&m);
    
//输入并构造邻接矩阵
    input(n,m);
    
//显示邻接矩阵
    output(n,m);
    
while (1)
    
{
        printf(
"/n请输入根节点(-1跳出):/n");
        scanf(
"%d",&r);
        
if(r==-1)
            
break;
        
//prim算法实现
        prim(n,r);
        printf(
"/n/n");    
    }

    
return 0;
}

 

测试数据:

9 14


0 1 4
1 2 8
2 3 7
3 4 9
4 5 10
5 6 2
6 7 1
7 8 7
8 2 2
2 5 4
1 7 11
0 7 8
3 5 14
6 8 6

 

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值