最小樹形圖(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

發佈了80 篇原創文章 · 獲贊 14 · 訪問量 67萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章