最小树形图


个人觉得这个博客把这个算法说的比较详细了,直接搬过来吧,我再阐述一遍的话没有人家说的好,还容易说错。
========================== 分割线之下摘自Sasuke_SCUT的blog==================================================
最 小树形图,就是给有向带权图中指定一个特殊的点root,求一棵以root为根的有向生成树T,并且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)了。由此可见,如果一开始不除去自环的话,理论复杂度会和自环的数目有关。
======================== 分割线之上摘自Sasuke_SCUT的blog=====================================================

下 面是朱刘算法的构造图


[cpp]  view plain copy
  1. #include <cstdio>  
  2. #include <iostream>  
  3. #include<queue>  
  4. #include<set>  
  5. #include<ctime>  
  6. #include<algorithm>  
  7. #include<cmath>  
  8. #include<vector>  
  9. #include<map>  
  10. #include<cstring>  
  11. using namespace std;  
  12. const double eps=1e-10;  
  13. #define M 109  
  14. #define type double   
  15. const type inf=(1)<<30;  
  16. struct point   
  17. {  
  18.     double x,y;  
  19. }p[M];  
  20. double dis(point a,point b)  
  21. {  
  22.     return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));  
  23. }  
  24. struct Node{  
  25.     int u , v;  
  26.     type cost;  
  27. }E[M*M+5];  
  28. int pre[M],ID[M],vis[M];  
  29. type In[M];  
  30. int n,m;   
  31. type Directed_MST(int root,int NV,int NE) {  
  32.     type ret = 0;  
  33.     while(true) {  
  34.         //1.找最小入边  
  35.         for(int i=0;i<NV;i++) In[i] = inf;  
  36.         for(int i=0;i<NE;i++){  
  37.             int u = E[i].u;  
  38.             int v = E[i].v;  
  39.             if(E[i].cost < In[v] && u != v) {  
  40.                 pre[v] = u;  
  41.                 In[v] = E[i].cost;  
  42.             }  
  43.         }  
  44.         for(int i=0;i<NV;i++) {  
  45.             if(i == root) continue;  
  46.             if(In[i] == inf)    return -1;//除了跟以外有点没有入边,则根无法到达它  
  47.         }  
  48.         //2.找环  
  49.         int cntnode = 0;  
  50.     //  CC(ID,-1);  
  51.     //  CC(vis,-1);  
  52.     memset(ID,-1,sizeof(ID));  
  53.     memset(vis,-1,sizeof(vis));  
  54.         In[root] = 0;  
  55.         for(int i=0;i<NV;i++) {//标记每个环  
  56.             ret += In[i];  
  57.             int v = i;  
  58.             while(vis[v] != i && ID[v] == -1 && v != root) {  
  59.                 vis[v] = i;  
  60.                 v = pre[v];  
  61.             }  
  62.             if(v != root && ID[v] == -1) {  
  63.                 for(int u = pre[v] ; u != v ; u = pre[u]) {  
  64.                     ID[u] = cntnode;  
  65.                 }  
  66.                 ID[v] = cntnode ++;  
  67.             }  
  68.         }  
  69.         if(cntnode == 0)    break;//无环  
  70.         for(int i=0;i<NV;i++) if(ID[i] == -1) {  
  71.             ID[i] = cntnode ++;  
  72.         }  
  73.         //3.缩点,重新标记  
  74.         for(int i=0;i<NE;i++) {  
  75.             int v = E[i].v;  
  76.             E[i].u = ID[E[i].u];  
  77.             E[i].v = ID[E[i].v];  
  78.             if(E[i].u != E[i].v) {  
  79.                 E[i].cost -= In[v];  
  80.             }  
  81.         }  
  82.         NV = cntnode;  
  83.         root = ID[root];  
  84.     }  
  85.     return ret;  
  86. }  
  87.   
  88.   
  89. int main()  
  90. {  
  91.     while(scanf("%d%d",&n,&m)!=EOF)  
  92.     {  
  93.         // memset(pre,0,sizeof(pre));  
  94.         for(int i=0;i<n;i++)  
  95.         scanf("%lf%lf",&p[i].x,&p[i].y);  
  96.         for(int i=0;i<m;i++)  
  97.         {  
  98.         scanf("%d%d",&E[i].u,&E[i].v);  
  99.         E[i].u--;  
  100.         E[i].v--;  
  101.         if(E[i].u!=E[i].v)  
  102.         E[i].cost=dis(p[E[i].u],p[E[i].v]);  
  103.         else E[i].cost=1<<30;  
  104.         }  
  105.         type ans=Directed_MST(0,n,m);  
  106.         if(ans==-1)  
  107.         printf("poor snoopy\n");  
  108.         else   
  109.         printf("%.2f\n",ans);  
  110.     }  
  111.     return 0;  
  112. }  

[cpp]  view plain copy
  1. #include <cstdio>  
  2. #include <iostream>  
  3. #include<queue>  
  4. #include<set>  
  5. #include<ctime>  
  6. #include<algorithm>  
  7. #include<cmath>  
  8. #include<vector>  
  9. #include<map>  
  10. #include<cstring>  
  11. using namespace std;  
  12. const double eps=1e-10;  
  13. #define M 109  
  14. #define type int  
  15. const type inf=(1)<<30;  
  16. struct point   
  17. {  
  18.     double x,y;  
  19. }p[M];  
  20. double dis(point a,point b)  
  21. {  
  22.     return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));  
  23. }  
  24. struct Node{  
  25.     int u , v;  
  26.     type cost;  
  27. }E[M*M+5];  
  28. int pre[M],ID[M],vis[M];  
  29. type In[M];  
  30. int n,m;   
  31. type Directed_MST(int root,int NV,int NE) {  
  32.     type ret = 0;  
  33.     while(true) {  
  34.         //1.找最小入边  
  35.         for(int i=0;i<NV;i++) In[i] = inf;  
  36.         for(int i=0;i<NE;i++){  
  37.             int u = E[i].u;  
  38.             int v = E[i].v;  
  39.             if(E[i].cost < In[v] && u != v) {  
  40.                 pre[v] = u;  
  41.                 In[v] = E[i].cost;  
  42.             }  
  43.         }  
  44.         for(int i=0;i<NV;i++) {  
  45.             if(i == root) continue;  
  46.             if(In[i] == inf)    return -1;//除了跟以外有点没有入边,则根无法到达它  
  47.         }  
  48.         //2.找环  
  49.         int cntnode = 0;  
  50.     memset(ID,-1,sizeof(ID));  
  51.     memset(vis,-1,sizeof(vis));  
  52.         In[root] = 0;  
  53.         for(int i=0;i<NV;i++) {//标记每个环  
  54.             ret += In[i];  
  55.             int v = i;  
  56.             while(vis[v] != i && ID[v] == -1 && v != root) {  
  57.                 vis[v] = i;  
  58.                 v = pre[v];  
  59.             }  
  60.             if(v != root && ID[v] == -1) {  
  61.                 for(int u = pre[v] ; u != v ; u = pre[u]) {  
  62.                     ID[u] = cntnode;  
  63.                 }  
  64.                 ID[v] = cntnode ++;  
  65.             }  
  66.         }  
  67.         if(cntnode == 0)    break;//无环  
  68.         for(int i=0;i<NV;i++) if(ID[i] == -1) {  
  69.             ID[i] = cntnode ++;  
  70.         }  
  71.         //3.缩点,重新标记  
  72.         for(int i=0;i<NE;i++) {  
  73.             int v = E[i].v;  
  74.             E[i].u = ID[E[i].u];  
  75.             E[i].v = ID[E[i].v];  
  76.             if(E[i].u != E[i].v) {  
  77.                 E[i].cost -= In[v];  
  78.             }  
  79.         }  
  80.         NV = cntnode;  
  81.         root = ID[root];  
  82.     }  
  83.     return ret;  
  84. }  
  85.   
  86.   
  87. int main()  
  88. {  
  89.     while(scanf("%d%d",&n,&m),n+m)  
  90.     {  
  91.         for(int i=0;i<m;i++)  
  92.         {  
  93.         scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].cost);  
  94.         E[i].u--;  
  95.         E[i].v--;  
  96.         }  
  97.         type ans=Directed_MST(0,n,m);  
  98.         if(ans==-1)  
  99.         printf("impossible\n");  
  100.         else   
  101.         printf("%d\n",ans);  
  102.     }  
  103.     return 0;  
  104. }  



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值