prim算法--prim算法求次小生成树--prim算法求限制K度生成树

在正常求次小生成树的时候相信大家都喜欢kruskal,毕竟因为太大的图,尤其是稀疏图,prim算法并不实用,而且占内存太多,有可能不让开。但是在解决次小生成树和限制度生成树时候prim算法不失为一种很好的方法,因为这两种情况空间复杂度,尤其是图的大小都并不太大。

从一开始学了prim算法,再到拿prim算法解决次小生成树的变形,直到今天彻底搞懂prim算法求解限制k度最小生成树的问题。我感觉无论如何边,唯一不变的一点就是:

把贪心贯彻到底!!!

最小生成树:朴素prim 与dijkstra相同,一直找最小,一直更新插入

次小生成树:在求解完最小生成树后,找一个未被使用的最小边进行判断

限制K度生成树: 在分块求解完最小生成树后,进行每一次从所有树中找一个差值最大的,重新建树,松弛更新

一:

先说一下普通的最小生成树的题,prim算法 的不优化和优化的两种算法。

1.不优化-详细请点http://blog.csdn.net/qq_33951440/article/details/52640783

以POJ 1258 Agri-Net题为例

void prim(int cur)  
{  
    int sum=0;  
    for(int i=1;i<=n;i++)  
    {  
        dis[i]=mp[cur][i];  
    }  
    vis[cur]=1;   
      
    for(int i=1;i=mp[index][j])  
                {  
                    dis[j]=mp[index][j];  
                }  
            }  
        }  
    }  

之后就是优化后的prim算法了,用优先队列来搞定,复杂度降低成为了(nlogn),然而空间复杂度仍然是一个问题

priority_queueq;
    while(!q.empty())
        q.pop();
    node fir;
    fir.v=cur,fir.val=0;
    q.push(fir);
    while(!q.empty())
    {
        node tp=q.top();
        q.pop();
        int u=tp.v;
        if(!tree[u])
        {
            ans+=tp.val;
            tree[u]=id;
            for(int i=1;imp[u][i])
                {
                    pre[i]=u;       
                    dis[i]=mp[u][i]; 
                    node temp;
                    temp.val=dis[i];
                    temp.v=i;
                    q.push(temp);
                }
            }
        }
    }
    return ans;

二.次小生成树

题意:在最小生成树的基础上,询问是否可以不经过最小生成树构造时候经过的边,重新更改一条或者多条边,使得权值和仍然与最小生成树的权值和相同。

思路:在构造最小生成树时候,把用到的边进行全部标记掉,然后再从未被标记的边中找出一条边进行更改。

注意:这个时候prim算法就会看出好处了,在空间允许的情况下我们可以直接用二维数组 类似于mp[i][j]=0 的形式直接把边标记点,而不非要同kruskal算法一样重新构造

具体的点击   

次小生成树模板-prim算法


三.限制K度最小生成树

题意:在普通最小生成树的基础上,题意允许你再多加K条边,构造一个 K度的最小生成树。

大体意思就是:

当把最小生成树都构造好(注意有可能不只是一棵树)之后再进行加入m条边,或者少于m条边,使得权值更小。

我不感觉我可以讲理论讲得很好,所以具体的请看大神写得国家队论文,http://wenku.baidu.com/view/8abefb175f0e7cd1842536aa.html

思路:在不加入总目标的那个点之前,把所有的点都尽可能能连起来,也就是构造尽量少的最小生成树,之后再把每一棵树中找到一个最短的边,把树与总目标点相连,如果还有多余的度可以让我们插入,我们就在所有点中找到一个可以松弛最大的,注意不是最小的,而是相比之下松弛更大的。

我在这里只是把代码一步步的注释,记录下自己一步步调试的发现Orz

以题POJ1639为例

#include
#include
#include
#include
#include
#include
using namespace std;
const int N=30;
struct node
{
    int v,cap;
    bool friend operator < ( node a, node b)
    {
        return a.cap>b.cap;
    }
};
map mp;
int g[N][N],dis[N],clo[N],pre[N],fst[N],max_side[N];
int n,m,k;
int Prim(int cur,int id)
{
    int ans=0;
    priority_queueq;
    node fir;
    fir.v=cur,fir.cap=0;
    q.push(fir);
    while(!q.empty())
    {
        node tp=q.top();
        q.pop();
        int u=tp.v;
        if(!clo[u])
        {
            ans+=tp.cap;
            clo[u]=id;
            for(int i=1;ig[u][i])
                {
                    pre[i]=u;       //在point.v( u ) 起点的基础上进行了松弛,被松弛的边的父亲进行修改
                    dis[i]=g[u][i]; //修改当前树到达i点的最小权值
                    node temp;
                    temp.cap=dis[i];
                    temp.v=i;
                    q.push(temp);
                }
            }
        }
    }
    return ans;

} 

void update(int cur,int last,int maxside) //这一步的DFS是一个关键,因为之前我们已经知道了当前树如果与总根0相连,可以花费的最小权值,以及花费最小全知道的那个点fst[i]
{
    //因此,这个函数的根本意义是在于把当前树所有的点与fst[i]相连成为最小生成树的花费更新出来
    max_side[cur]=maxside>g[cur][last]?maxside:g[cur][last];  //max_side在这棵树上,从cur点开始他自己一直到最上层0中的最大的一条权值边的记录
    for(int i=1; i>m;
    mp.clear();
    mp["Park"]=0;
    n=1;
    while(m--)
    {
        int d;
        cin>>s1>>s2>>d;
        if(mp.count(s1)==0)
        {
            mp[s1]=n++;
        }
        if(mp.count(s2)==0)
        {
            mp[s2]=n++;
        }
        int u=mp[s1],v=mp[s2];
        if(g[u][v]==0||g[u][v]>d)
        {
            g[u][v]=d;
            g[v][u]=d;
        }
    }
    cin>>k;
    solve();
    return 0;
}

/*.
10
Alphonzo Bernardo 32
Alphonzo Park 57
Alphonzo Eduardo 45
Bernardo Park 19
Bernardo Clemenzi 89
Clemenzi Park 65
Clemenzi Herb 90
Clemenzi Eduardo 30
Park Herb 24
Herb Eduardo 79
3
*/





你可能感兴趣的:(最小生成树,acm)