最小树形图(Chu&Liu/Edmonds's algorithm)

求最小树形图。


前 几天为了UVa的一道题不得不重写了一个最小树形图O(VE)的模板,原先的是邻接矩阵版,所以复杂度是O(V^3)的。我的计划就是学/复习一个算法就 写一个总结上来,最后能逐渐的把我的学习经历记录一下。当然把其中一些理解写出来的话也会更加的深刻,或许还能帮到某些人,不是么,呵呵。


       最小树形图,就是给有向带权图中指定一个特殊的点v,求一棵有向生成树T,使得该有向树的根为v,并且T中所有边的总权值最小。最小树形图的第一个算法是1965年朱永津和刘振宏提出的复杂度为O(VE)的算法。


       判断是否存在树形图的方法很简单,只需要以v为根作一次图的遍历就可以了,所以下面的算法中不再考虑树形图不存在的情况。


       在所有操作开始之前,我们需要把图中所有的自环全都清除。很明显,自环是不可能在任何一个树形图上的。只有进行了这步操作,总算法复杂度才真正能保证是O(VE)。
       首先为除根之外的每个点选定一条入边,这条入边一定要是所有入边中最小的。现在所有的最小入边都选择出来了,如果这个入边集不存在有向环的话,我们可以 证明这个集合就是该图的最小树形图。这个证明并不是很难。如果存在有向环的话,我们就要将这个有向环所称一个人工顶点,同时改变图中边的权。假设某点u在 该环上,并设这个环中指向u的边权是in[u],那么对于每条从u出发的边(u, i, w),在新图中连接(new, i, w)的边,其中new为新加的人工顶点; 对于每条进入u的边(i, u, w),在新图中建立边(i, new, w-in[u])的边。为什么入边的权要减去in[u],这个后面会解释,在这里先给出算法的步骤。然后可以证明,新图中最小树形图的权加上旧图中被收缩 的那个环的权和,就是原图中最小树形图的权。


       上面结论也不做证明了。现在依据上面的结论,说明一下为什么出边的权不变,入边的权要减去in [u]。对于新图中的最小树形图T,设指向人工节点的边为e。将人工节点展开以后,e指向了一个环。假设原先e是指向u的,这个时候我们将环上指向u的边 in[u]删除,这样就得到了原图中的一个树形图。我们会发现,如果新图中e的权w'(e)是原图中e的权w(e)减去in[u]权的话,那么在我们删除 掉in[u],并且将e恢复为原图状态的时候,这个树形图的权仍然是新图树形图的权加环的权,而这个权值正是最小树形图的权值。所以在展开节点之后,我们 得到的仍然是最小树形图。逐步展开所有的人工节点,就会得到初始图的最小树形图了。


       如果实现得很聪明的话,可以达到找最小入边O(E),找环 O(V),收缩O(E),其中在找环O(V)这里需要一点技巧。这样每次收缩的复杂度是O(E),然后最多会收缩几次呢?由于我们一开始已经拿掉了所有的 自环,我门可以知道每个环至少包含2个点,收缩成1个点之后,总点数减少了至少1。当整个图收缩到只有1个点的时候,最小树形图就不不用求了。所以我们最 多只会进行V-1次的收缩,所以总得复杂度自然是O(VE)了。由此可见,如果一开始不除去自环的话,理论复杂度会和自环的数目有关。

       最小树形图的总结到此为止了。目前为止发现的有OJ可交的最小树形图总共有3道题,分别是UVa 11183 Teen Girl Squad, TJU 2248 Channel Design, PKU 3164 Command Network。其中UVa的题V = 1000, E = 40000,PKU和TJU是V = 100, E = 10000。在PKU/TJU下,由于数据范围的限制,采用O(V^3)的邻接矩阵会比O(VE)的邻接表快,而UVa的题显然只能用O(VE)的树形图算法了。

Source code

 

//最小树形图,源点编号为1
#include <cstdio>
#include 
<cstring>
#include 
<cmath>
using namespace std;
 
struct Point {
    
double _x,_y;
    Point(
double x,double y) :_x(x),_y(y) {}
    Point() {}
};

const unsigned int maxn=128;
const double NOEDGE=99999999;
double G[maxn][maxn];
Point allp[maxn];
int N,M;
double res; 

inline 
double dist(const Point& p1,const Point& p2) {
    
return sqrt((p1._x-p2._x)*(p1._x-p2._x)+(p1._y-p2._y)*(p1._y-p2._y));
}
template 
<class T>
void update(T& o,const T& x){
    
if(o>x)
        o
=x;
}
bool vis[maxn]; 
void dfs(int v){
    vis[v]
=true;
    
for(int i=2;i<=N;++i)
        
if((!vis[i])&&G[v][i]!=NOEDGE)
            dfs(i); 
}
bool possible(){
    memset(vis,
0,sizeof(vis));
    dfs(
1);
    
for(int i=2;i<=N;++i)
        
if(!vis[i])
            
return false;
    
return true;
}
int pre[maxn];
bool del[maxn];
void solve(){
    
int num=N;
    memset(del,
0,sizeof(del));
    
for(;;){
        
int i;
        
for(i=2;i<=N;++i){
            
if(del[i])continue;
            pre[i]
=i;
            G[i][i]
=NOEDGE;
            
for(int j=1;j<=N;++j){
                
if(del[j])continue;
                
if(G[j][i]<G[pre[i]][i])
                    pre[i]
=j;
            }
        }
        
for(i=2;i<=N;++i){
            
if(del[i])continue;
            
int j=i;
            memset(vis,
0,sizeof(vis));
            
while(!vis[j]&&j!=1){
                vis[j]
=true;
                j
=pre[j];
            }
            
if(j==1)continue;
            i
=j;
            res
+=G[pre[i]][i];
            
for(j=pre[i];j!=i;j=pre[j]){
                res
+=G[pre[j]][j];
                del[j]
=true;
            }
            
for(j=1;j<=N;++j){
                
if(del[j])continue;
                
if(G[j][i]!=NOEDGE)
                    G[j][i]
-=G[pre[i]][i];
            }
            
for(j=pre[i];j!=i;j=pre[j]){
                
for(int k=1;k<=N;++k){
                    
if(del[k])continue;
                    update(G[i][k],G[j][k]);
                    
if(G[k][j]!=NOEDGE)
                        update(G[k][i],G[k][j]
-G[pre[j]][j]);
                }
            }
            
for(j=pre[i];j!=i;j=pre[j]){
                del[j]
=true;
            }
            
break;
        }
        
if(i>N){
            
for(int i=2;i<=N;++i){
                
if(del[i])continue;
                res
+=G[pre[i]][i];
            }
            
break;
        }
    }
}
int main(){
    
double x,y;
    
for(;;){
        
if(scanf("%d%d",&N,&M)==EOF) return 0;
        
if(N==0)break;
        
for(int i=0;i<=N;i++)
            
for(int j=0;j<=N;j++)
                G[i][j]
=NOEDGE;
        
for(int i=1;i<=N;i++) {
            scanf(
"%lf %lf",&x,&y);
            allp[i]._x
=x;
            allp[i]._y
=y;
        }
        
for(int i=0;i<M;++i){
            unsigned 
int a,b;
            scanf(
"%u%u",&a,&b);
            update(G[a][b],dist(allp[a],allp[b]));
        }
        
if(!possible()){
            puts(
"poor snoopy"); 
        }
        
else{
            res
=0
            solve();
            printf(
"%.2f/n",res);
        }
    }
}

转自和参考: http://www.cnblogs.com/woodfish1988/archive/2007/08/12/852510.html

                      http://www.cppblog.com/bnugong/default.html?page=4

                      http://hi.baidu.com/wywcgs/blog/item/a1ce10f4a8fa366fdcc47462.html

                      http://hi.baidu.com/bin183/blog/item/45c37950ece4475f1138c273.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值