1. 程式人生 > 實用技巧 >最小生成樹

最小生成樹

  最小生成樹的定義:一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的所有 n 個結點,並且有保持圖連通的最少的邊。最小生成樹可以用kruskal(克魯斯卡爾)演算法或prim(普里姆)演算法求出。

  定理:任意一顆最小生成樹一定包含無向圖中權值最小的邊。

  在一給定的無向圖G = (V, E) 中,(u, v) 代表連線頂點 u 與頂點 v 的邊(即),而 w(u, v) 代表此的權重,若存在 T 為 E 的子集(即)且為無迴圈圖,使得的 w(T) 最小,則此 T 為 G 的最小生成樹。         最小生成樹其實是最小權重生成樹的簡稱。   下面介紹兩種演算法:Kruskal和Prim。
 Kruskal是維護無向圖的最小生成森林。Prim是維護最小生成樹的一部分。   Kruskal:   1.建立並查集,以每個店為一個集合。   2.邊權從小到大排序,掃描每條邊(橫座標,縱座標,邊權)(下文用x,y,z分別表示)。   3.若x,y屬於同一集合(就是所謂的聯通),跳過,下一條。   4.如果不是,將其合併(這時候就用到了並查集),把z累加到答案中。   5.處理完所有邊後,上一步處理的邊就是最小生成樹。   複雜度(o(m log m));   核心程式碼:   
const int maxn=1e5;

struct rec
{
    int x,y,z;
}edge[maxn];

int fa[maxn],n,m,ans; bool operator <(rec a,rec b) { return a.z<b.z; } int get(int x) { if(x==fa[x]) return x; return fa[x]=get(fa[x]); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].z); sort(edge
+1,edge+n+1);//按邊權排序 for(int i=1;i<=n;i++) fa[i]=i;//並查集初始化; for(int i=1;i<=n;i++) { int x=get(edge[i].x); int y=get(edge[i].y); if(x==y) continue; fa[x]=y; ans+=edge[i].x; }//求最小生成樹 printf("%d\n",ans); }

  Prim:

  最初,僅先確定1號節點屬於最小生成樹。任意時刻假設已經確定屬於最小生成樹的節點集合為T,剩餘節點集合為S。Prim演算法找到,即兩個端點分別屬於集合S,T的權值最小的邊,然後把點x從集合S中刪除,加入到集合T,並把z累加到答案中。

  具體一點,維護陣列d:若x∈S,則d[x]表示節點x與集合T的節點之間權值最小的邊的權值。若x∈T,則d[x]就等於x被加入T時選出的最小值的權值。

const int maxn=1e5;

int a[manx][maxn],d[maxn],n,m,ans;
bool v[maxn];

void prim()
{
    memset(d,0x3f,sizeof(d));
    memset(v,0,sizeof(v));
    d[1]=0;
    for(int i=1;i<n;i++)
    {
        int x=0;
        for(int j=1;j<=n;j++)
            if(!v[j]&&(x==0||d[j]<d[x])) x=j;
        v[x]=1;
        for(int y=1;y<=n;y++)
            if(!v[y]) d[y]=min(d[y],a[x][y]);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    memset(a,0x3f,sizeof((a)));
    for(int i=1;i<=n;i++) a[i][i]=0;
    for(int i=1;i<=n;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        a[y][x]=a[x][y]=min(a[x][y],z);
    }//構建鄰接矩陣
    prim();
    for(int i=2;i,=n;i++) ans+=d[i];//求最小生成樹 
    printf("%d\n",ans);
}

兩種方法不做比較,因題和個人而定。