1. 程式人生 > >資料結構學習筆記(20)---圖的應用(生成樹與最小生成樹)

資料結構學習筆記(20)---圖的應用(生成樹與最小生成樹)

上一篇部落格寫了圖的基本儲存於遍歷,在此基礎上,此篇部落格將會介紹圖的主要應用—–生成樹與最小生成樹。

(一)生成樹

定義:我總感覺書上定義比較繁瑣,因此就自己簡單定義了一下(可能不對哦),生成樹其實就是:對於一棵樹G,若頂點數為n,則在原來圖的基礎上把邊刪除到n-1條邊且能連通各點就是生成樹。注意生成樹不唯一
例如:

利用遍歷方法可以求得生成樹,以鄰接矩陣為儲存方式程式碼如下:

int visit[MAXSIZE];//標記頂點是否訪問
void DFSGraph(Graph* g, int n)
{

    visit[n] = 1;//若訪問過就標記為1
for (size_t i = 0; i < g->n; i++) { if ( !visit[i]&& g->edge[n][i] == 1 ) { cout << g->vertex[n] << " "<<i<<endl;//輸出生成樹的邊,其實就是對遍歷演算法改了輸出的位置 DFS(g, i); } } } void DFSTraveseGraph(Graph *g) { for
(size_t i = 0; i < g->n; i++) { visit[i] = 0; } for (size_t i = 0; i < g->n; i++) { if (!visit[i]) { DFS(g, i);//防止連通圖 } } }

(二)最小生成樹

定義:我還是按照我的理解敘述吧,書上真的講的太拖拉,對於一棵樹G,若頂點數為n,則在原來圖的基礎上把邊刪除到n-1條邊且能連通各點但權值和最小就是最小生成樹。(如果錯了望指教).
為了求一棵樹的最小生成樹,有兩位比較牛逼的人物給出了兩種不同的演算法—–PRIM演算法與KRUSKAL演算法。

(1)PRIM演算法:

思想:
1. 首先選取一個起始頂點V0
2. 檢視該頂點的所有相鄰點之間的邊,選取權值最小的一個邊,此時該邊就是最小生成樹的一個邊。該邊的兩個頂點分別為v0 v1
3. 這時檢視與v0 v1 兩個頂點相連的邊,選取權值最小的(但是不能有環出現或者不能重複加入已有的頂點),又得到一棵邊,把該邊的端點加入進來,此時端點集合為v0 v1 v2
4. 重新按上述步驟添加個點與邊直到結束。
其實上述是我自己總結的,下面是官方語言:
1. 首先將初始頂點u加入到U中,對其餘的每一個頂點i,將closedge[i]均初始化為到u的邊資訊。
2. 迴圈n-1次,做如下處理:
a.從各組最小邊closedge[v]中選出最小的最小邊closedge[k0];(v,k0屬於V-U)
b.將k0加入U中;
c.更新剩餘的每組最小邊資訊closedge[v]對於以v為中序的那組邊,新增加了一條從k0到v的邊,如果新邊的權值比closedge[v].lowcost小,則將closedge[v].lowcost更新為新邊的權值。
例如:

其演化過程如下:

程式碼如下:

void Prim(AdjMatrix gn,VertexData u)
/*從頂點u出發,按普里姆演算法構造連通網gn 的最小生成樹,並輸出生成樹的每條邊*/
{
    k=LocateVertex(gn, u);
    closedge[k].lowcost=0;   /*初始化,U={u} */
    for(i=0;i<gn.vexnum;i++)    
        if (i!=k)    /*對V-U中的頂點i,初始化closedge[i]*/
            {
            closedge[i].adjvex=u; 
            closedge[i].lowcost=gn.arcs[k][i].adj;
        }
    for(e=1;e<=gn.vexnum-1;e++)    /*找n-1條邊(n= gn.vexnum) */
    {
        k0=Minium(closedge);     /* closedge[k0]中存有當前最小邊(u0,v0)的資訊*/
        u0=closedge[k0].adjvex;   /* u0∈U*/
        v0=gn.vexs[k0];          /* v0∈V-U*/
            printf("%d,%d",u0, v0);    /*輸出生成樹的當前最小邊(u0,v0)*/
        closedge[k0].lowcost=0;     /*將頂點v0納入U集合*/
        for(i=0;i<vexnum;i++)    /*在頂點v0併入U之後,更新closedge[i]*/
            if(gn.arcs[k0][i].adj<closedge[i].lowcost)
                                          { 
                closedge[i].lowcost=gn.arcs[k0][i].adj;
                closedge[i].adjvex=v0;
            }  
    }
}
(2)KRUSKAL演算法

思想:
1. 先構造一個只含 n 個頂點、而邊集為空的子圖,把子圖中各個頂點看成各棵樹上的根結點
2. 從網的邊集 E 中選取一條權值最小的邊,若該條邊的兩個頂點分屬不同的樹,則將其加入子圖,即把兩棵樹合成一棵樹
3. 若該條邊的兩個頂點已落在同一棵樹上,則不可取,而應該取下一條權值最小的邊再試之
4. 依次類推,直到森林中只有一棵樹,也即子圖中含有 n-1 條邊為止。
例如:

程式碼如下:

typedef struct Edge
{
    int v;
    int u;
    int w;
}edge;

typedef struct 
{
    edge *e;
    int num;  //邊數
    int nv;   //定點數
}Graph;
int cmp(const void *a,const void *b) //按升序排列  
{  
    edge * x = (edge *)a;
    edge * y = (edge *)b;
    return (x->w)-(y->w);  
}  
void kruskal(Graph *g)
{
    int *x=(int *)malloc(sizeof(int)*g->num);//點所在集合
    int i,m,n,c=0;
    for(i=1;i<=g->nv;i++)
    {
        x[i]=i;
    }a
    qsort(g->e,g->num,sizeof(edge),cmp);
    for(i=0;i<g->num&&c<g->nv;i++)
    {
            //判斷邊的兩點是否在同一個集合
        for(m=g->e[i].v;m!=x[m];m=x[m])
            x[m]=x[x[m]];
        for(n=g->e[i].u;n!=x[n];n=x[n])
            x[n]=x[x[n]];
        if(m!=n)
        {
            x[m]=n;
            c++;
            printf("(%d,%d)->%d\n",g->e[i].v,g->e[i].u,g->e[i].w);  
        }
    }
}
小結:其實兩種演算法只是出發點相同,但是思想我個人認為還是有些共同之處的。