Fork me on GitHub

次小生成树的两种算法

一、“换边”算法

用Kruskal求最小生成树,标记用过的边。求次小生成树时,依次枚举用过的边,将其去除后再求最小生成树,得出所有情况下的最小的生成树就是次小的生成树。可以证明:最小生成树与次小生成树之间仅有一条边不同。

这样相当于运行m次Kruskal算法。

复杂度O(m^2)

示例代码:

int Kruskal_MinTree()
{
    int u,v;
    init();
    int i,flag,cnt;
    minedge = 0;
    flag = cnt = 0;
    int tmp = 0;
    for(i=0;i<m;i++)
    {
        u = edge[i].s;
        v = edge[i].t;
        int fx = findset(u);
        int fy = findset(v);
        if(fx != fy)
        {
            use[minedge++] = i;
            tmp += edge[i].w;
            fa[fx] = fy;
            cnt++;
        }
        if(cnt == n-1)
        {
            flag = 1;   //找到最小生成树
            break;
        }
    }
    if(flag)
        return tmp;
    return -1;   //不存在最小生成树,返回-1
}

int Sec_MinTree()
{
    int i,j,mini,tmp;
    int cnt,flag,u,v;
    mini = Mod;
    for(i=0;i<minedge;i++)   //枚举最小生成树中的每条边,将其去除
    {
        init();
        flag = cnt = tmp = 0;
        for(j=0;j<m;j++)
        {
            if(j != use[i])  //去除边
            {
                u = edge[j].s;
                v = edge[j].t;
                int fx = findset(u);
                int fy = findset(v);
                if(fx != fy)
                {
                    cnt++;
                    tmp += edge[j].w;
                    fa[fx] = fy;
                }
                if(cnt == n-1)
                {
                    flag = 1;
                    break;
                }
            }
        }
        if(flag && tmp < mini)
            mini = tmp;
    }
    if(mini == Mod)
        mini = -1;
    return mini;
}
View Code

 

二、“最长边”法

算法流程:先加入(x,y),对于一棵树,加入(x,y)后一定会形成环,如果删去环上除(x,y)外最大的一条边,会得到加入(x,y)时权值最小的一棵树,如果能快速计算最小生成树中点x与点y之间路径中最长边的长度,即可快速解决。

复杂度O(mlogm+n^2)

示例代码:

int fa[N];
const int M = 100010
struct Edge
{
    int u,v,w;
    bool chose;
}edge[M];

struct Adge
{
    int v,next;
}G[N];        //边数组

int tot,n,m;
int first[N];  //邻接表头节点位置
int end[N];    //邻接表尾节点位置
int mp[N][N];  //任意两点在最小生成树路径上的最长边长


int findset(int x)
{
    if(x != fa[x])
        fa[x] = findset(fa[x]);
    return fa[x];
}

int cmp(Edge ka,Edge kb)
{
    if(ka.w != kb.w)
        return ka.w < kb.w;
    if(ka.u != kb.u)
        return ka.u < kb.u;
    return ka.v < kb.v;
}

void Kruskal(Edge *edge)
{
    int k = 0;
    int i,w,v;
    //初始化邻接表,对于每个节点添加一条指向其自身的边,表示以i为代表元的集合只有点i
    for(tot=0;tot<n;tot++)
    {
        G[tot].v = tot+1;
        G[tot].next = first[tot+1];
        end[tot+1] = tot;
        first[tot+1] = tot;
    }
    sort(edge+1,edge+m+1,cmp);
    for(i=1;i<=m;i++)
    {
        if(k == n-1)
            break;
        if(edge[i].w < 0)
            continue;
        int fx = findset(edge[i].u)
        int fy = findset(edge[i].v);
        if(fx != fy)   //修改部分,遍历两个点所在集合
        {
            for(w=first[fx];w!=-1;w=G[w].next)
            {
                for(v=first[fy];v!=-1;v=G[v].next)
                {
                    //每次合并两个等价类的时候,分别属于两个等价类的两点间的最长边一定是当前加入的边
                    mp[G[w].v][G[v].v] = mp[G[v].v][G[w].v] = edge[i].w;
                }
            }
            //合并两个邻接链表
            G[end[fy]].next = first[fx];
            end[fy] = end[fx];
            fa[fx] = fy;
            k++;
            edge[i].chose = 1;
        }
    }
}

int main()
{
    //初始化建图
    int mst,secmst;
    Kruskal(edge);
    mst = 0;
    for(i=1;i<=m;i++)
        if(edge[i].chose)
            mst += edge[i].w;
    secmst = INF;
    for(i=1;i<=m;i++)
    {
        if(!edge[i].chose)
        {
            secmst = min(secmst,mst+edge[i].w-mp[edge[i].u][edge[i].v]);
        }
    }
    return 0;
}
View Code

 

 

posted @ 2014-06-07 19:31  whatbeg  阅读(1649)  评论(0编辑  收藏  举报