求最小樹形圖。
前 幾天爲了UVa的一道題不得不重寫了一個最小樹形圖O(VE)的模板,原先的是鄰接矩陣版,所以複雜度是O(V^3)的。我的計劃就是學/複習一個算法就 寫一個總結上來,最後能逐漸的把我的學習經歷記錄一下。當然把其中一些理解寫出來的話也會更加的深刻,或許還能幫到某些人,不是麼,呵呵。
最小樹形圖,就是給有向帶權圖中指定一個特殊的點v,求一棵有向生成樹T,使得該有向樹的根爲v,並且T中所有邊的總權值最小。最小樹形圖的第一個算法是1965年朱永津和劉振宏提出的複雜度爲O(VE)的算法。
在所有操作開始之前,我們需要把圖中所有的自環全都清除。很明顯,自環是不可能在任何一個樹形圖上的。只有進行了這步操作,總算法複雜度才真正能保證是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