數(shù)據(jù)結(jié)構(gòu)與算法學(xué)習(xí) (13)最小生成樹(shù)

把構(gòu)成連通?的最?代價(jià)的?成樹(shù)稱(chēng)為最??成樹(shù)。
連通圖:在無(wú)向圖中,若任意兩個(gè)頂點(diǎn)vi和vj之間都有路徑想通,則稱(chēng)該無(wú)向圖為連通圖。
連通圖的生成樹(shù):一個(gè)連通圖的生成樹(shù)是指一個(gè)連通子圖,它包含有圖中全部n個(gè)頂點(diǎn),但只有足以構(gòu)成一棵樹(shù)的n-1條邊。一顆有n個(gè)頂點(diǎn)的生成樹(shù)有且僅有n-1條邊,如果生成樹(shù)中再添加一條邊,必定成環(huán)。
最小生成樹(shù):在一給定的無(wú)向圖G=(V,E)中,(u,v)代表連接頂點(diǎn)u與頂點(diǎn)v的邊,而w(u,v)代表邊的權(quán)重,若存在T為E的子集且為無(wú)循環(huán)圖,使得W(T)最小,則此T為G的最小生成樹(shù)。把構(gòu)成連通網(wǎng)的最?代價(jià)的生成樹(shù)稱(chēng)為最?生成樹(shù)Minimum Spanning Tree,MST。

最小生成樹(shù)性質(zhì):設(shè)G=(V,E)是一個(gè)連通網(wǎng)絡(luò),T1是頂點(diǎn)集V的一個(gè)非空真子集,T2是頂點(diǎn)集V-T1后剩余的子集。若(u,v)是G中一條一個(gè)端點(diǎn)在T1中(例如:u∈T1),另一個(gè)端點(diǎn)在T2中的邊(例如:v∈T2),且(u,v)具有最小權(quán)值,則一定存在G的最小生成樹(shù)包括此邊(u,v)。

我們先把圖轉(zhuǎn)換成鄰接矩陣,(https://juejin.im/post/5eae34e951882560d56d6588)

image

利用兩個(gè)數(shù)組來(lái)輔助我們記錄遍歷頂點(diǎn)過(guò)程中的數(shù)據(jù),兩個(gè)數(shù)組lowcost和arjvex,

  • lowcost用于記錄當(dāng)前頂點(diǎn)與所有與它關(guān)聯(lián)頂點(diǎn)間的權(quán)重值。下標(biāo):頂點(diǎn)下標(biāo),值:與當(dāng)前頂點(diǎn)的權(quán)重。
  • arjvex用于記錄當(dāng)前頂點(diǎn)與哪個(gè)頂點(diǎn)相連接。下標(biāo):當(dāng)前頂點(diǎn)下標(biāo),值:與當(dāng)前頂點(diǎn)相連的前一個(gè)頂點(diǎn)。
  1. 第一次執(zhí)行
    image

    <figcaption></figcaption>

  2. 第二次執(zhí)行
    image

    <figcaption></figcaption>

  3. 第三次執(zhí)行
    image

    <figcaption></figcaption>

  4. 第四次執(zhí)行
    image

    <figcaption></figcaption>

  5. 第五次執(zhí)行
    image

Prim算法
思路

定義兩個(gè)數(shù)組lowcost和adjvex。其中:

lowcost保存相關(guān)頂點(diǎn)間邊的權(quán)值,lowcost[i]=0代表頂點(diǎn)vi已經(jīng)加入了最小生成子樹(shù)中,lowcost[i]=INFINITYC表示i對(duì)應(yīng)的頂點(diǎn)與當(dāng)前的最小生成樹(shù)中的頂點(diǎn)暫時(shí)不連通,在0<lowcost[i]<INFINITYC的情況中會(huì)生成一個(gè)與當(dāng)前最小生成子樹(shù)的各個(gè)頂點(diǎn)之間權(quán)值最小的頂點(diǎn)加入到最小生成子樹(shù)當(dāng)中。
adjvex保存相關(guān)頂點(diǎn)下標(biāo) adjvex[i]默認(rèn)=-1.表示i對(duì)應(yīng)的結(jié)點(diǎn)和最小生成子樹(shù)暫時(shí)不連通,adjvex[i] >-1 表示i對(duì)應(yīng)的結(jié)點(diǎn)和最小生成樹(shù)相連的頂點(diǎn)為adjvex[i]

初始化上述數(shù)組,默認(rèn)將v0加入到最小子樹(shù)中,并更新數(shù)組中對(duì)應(yīng)的值
以v0為最小子樹(shù),尋找剩余頂點(diǎn)中與v0路徑最短的頂點(diǎn)加入v0當(dāng)中,組成新的最小子樹(shù),并更新兩個(gè)數(shù)組的值。
以新的最小子樹(shù)開(kāi)始,重復(fù)步驟3,直到找到最后一個(gè)頂點(diǎn)。

void MiniSpanTree_Prim_1(MGraph G) {
    int lowcost[MAXVEX];
    int adjvex[MAXVEX];
    
    //初始化兩個(gè)數(shù)組,即默認(rèn)將v0放入最小生成子樹(shù)中
    adjvex[0] = 0;
    lowcost[0] = 0;
    int I;
    //將v0對(duì)應(yīng)的連通頂點(diǎn)信息存入adjvex,將連通頂點(diǎn)的權(quán)值存入lowcost
    for(i = 1; i < G.numVertexes; i++) {
        if(G.arc[0][i] < INFINITYC) {
            adjvex[i] = 0;
        }else {
            adjvex[i] = -1;
        }
        lowcost[i] = G.arc[0][I];
    }
    
    int k = 0;
    int j = 0;
    int sum = 0;
    for(i = 1; i < G.numVertexes; i++) {
        int min = INFINITYC;
        //第一次遍歷找到lowcost中除去0以外最小的頂點(diǎn),作為下一個(gè)頂點(diǎn)加入最小生成樹(shù)
        for(j = 1; j < G.numVertexes; j++) {
            if(lowcost[j] != 0 && lowcost[j] < min) {
                min = lowcost[j];
                k = j;
            }
        }
//        printf("lowcost為:\n");
//        for(int temp = 0; temp < G.numVertexes; temp++) {
//            printf("%d ",lowcost[temp]);
//        }
//        printf("\n");
//        printf("adjvex為:\n");
//        for(int temp = 0; temp < G.numVertexes; temp++) {
//            printf("%d ",adjvex[temp]);
//        }
//        printf("\n");
        
        /* 打印當(dāng)前頂點(diǎn)邊中權(quán)值最小的邊 */
        printf("(V%d, V%d)=%d\n", adjvex[k], k ,G.arc[adjvex[k]][k]);
        sum += G.arc[adjvex[k]][k];
        lowcost[k] = 0;
        //將與新加入頂點(diǎn)連通的其他頂點(diǎn)加入lowcost中并更新對(duì)應(yīng)的lowcost以及adjvex的值
        for(j = 0; j < G.numVertexes; j++) {
            if(lowcost[j] != 0 && G.arc[k][j] < lowcost[j]) {
                lowcost[j] = G.arc[k][j];
                adjvex[j] = k;
            }
        }
    }
    printf("sum = %d\n",sum);
}

克魯斯卡爾算法
思路:

T是邊的集合,初始狀態(tài)為空
將圖中的所有邊以權(quán)值從小到大的順序進(jìn)行排序,預(yù)定排序后邊的起點(diǎn)永遠(yuǎn)為比較小的頂點(diǎn)
取出權(quán)值最小的邊,查看它與T中的邊是否造成環(huán)路,
如果未構(gòu)成環(huán)路,則加入T中;否則,拋棄改邊
如果還有剩余邊,則返回3中,否則程序結(jié)束

證明:
分兩步來(lái)證明Kruskal算法得到的是最小生成樹(shù)

Kruskal得到的是一棵生成樹(shù)
Kruskal得到的生成樹(shù)代價(jià)最小

假設(shè)改算法得到的不是生成樹(shù),那么有兩種情況,第一種是得到的圖是有環(huán)的,第二種情況是得到的圖不連通。由于算法要求,每次加入的邊都不能形成環(huán),因此第一種情形不存在。第二種情況,首先我們知道原圖是屬于連通圖,根據(jù)算法的要求,取出權(quán)值最小的邊,如果未構(gòu)成環(huán)路,則加入T中,因?yàn)樵瓐D是連通的,必然會(huì)存在可以連接所有頂點(diǎn)又不構(gòu)成環(huán)路的路徑,可以得到Kruskal得到的圖肯定也是連通圖,因此第二種情況也不存在。

接下來(lái)證明Kruskal得到的生成樹(shù)代價(jià)最小。假設(shè)圖有n個(gè)頂點(diǎn),則生成樹(shù)一定有n-1條邊,最少存在一個(gè)最小生成樹(shù)U。假設(shè):

Kruskal得到的生成樹(shù)為T(mén)。
U和T中不同的邊數(shù)為k,其他n-1-k條邊相同,相同的邊構(gòu)成集合E
在T中而不在U中的邊按代價(jià)從小到大一次為a1<a2<..<ak
在U中而不在T中的邊按代價(jià)從小到大一次為x1<x2<..<xk

我們通過(guò)把T中的邊依次移動(dòng)到U中來(lái)證明U和T是等價(jià)的。
首先將a1移動(dòng)到U中,由于U是一棵生成樹(shù),加入一條新邊后會(huì)行程環(huán)路,且這條回路一定會(huì)包括x1,x2...,xk中的邊,否則a1和E中的邊形成環(huán)路,則T不是生成樹(shù),產(chǎn)生矛盾。
在回路中刪除屬于x1,x2...,xk且代價(jià)最大的邊xi構(gòu)成一棵新的生成樹(shù)V。
假設(shè)a1<xi,則w(V)<w(U),與最小生成樹(shù)定義矛盾。
假設(shè)a1>xi, 則xi小于a1,a2...,ak,根據(jù)Kruskal算法,在考慮最小邊的時(shí)候會(huì)優(yōu)先考慮xi,而且xi和E也不會(huì)構(gòu)成環(huán)路,但是T中并沒(méi)有xi,說(shuō)明a1也不會(huì)大于xi,即a1 = xi,新得到的w(V)=w(U)。
同理,我們將a2...,ak依次加入到U中,最終得到w(U)=w(T)。證明完畢。
判斷是否生成環(huán)路

171f3fbbb3f1eeb8.png

上圖中,邊(v0,v1)在一條環(huán)路中,我們分別從v0和v1開(kāi)始,因?yàn)槭黔h(huán)路,我們通過(guò)某種規(guī)則,最終會(huì)達(dá)到同樣的終點(diǎn)。
判斷環(huán)路需要一個(gè)輔助數(shù)組parent[]={0},存儲(chǔ)新加入頂點(diǎn)的路徑關(guān)系,值為0代表沒(méi)有新加入的頂點(diǎn)

新增加一個(gè)邊的結(jié)構(gòu)體來(lái)保存每一條邊的信息
/* 對(duì)邊集數(shù)組Edge結(jié)構(gòu)的定義 */
typedef struct
{
    int begin;
    int end;
    int weight;
}Edge;

/* 查找連線頂點(diǎn)的尾部下標(biāo) */
//根據(jù)頂點(diǎn)f以及parent 數(shù)組,可以找到當(dāng)前頂點(diǎn)的尾部下標(biāo); 幫助我們判斷2點(diǎn)之間是否存在閉環(huán)問(wèn)題;
int Find(int *parent, int f)
{
    while ( parent[f] > 0)
    {
        f = parent[f];
    }
    return f;
}

/* 交換權(quán)值以及頭和尾 */
void Swapn(Edge *edges,int i, int j)
{
    int tempValue;
    
    //交換edges[i].begin 和 edges[j].begin 的值
    tempValue = edges[i].begin;
    edges[i].begin = edges[j].begin;
    edges[j].begin = tempValue;
    
    //交換edges[i].end 和 edges[j].end 的值
    tempValue = edges[i].end;
    edges[i].end = edges[j].end;
    edges[j].end = tempValue;
    
    //交換edges[i].weight 和 edges[j].weight 的值
    tempValue = edges[i].weight;
    edges[i].weight = edges[j].weight;
    edges[j].weight = tempValue;
}

/* 對(duì)權(quán)值進(jìn)行排序 */
void sort(Edge edges[],MGraph *G)
{
    //對(duì)權(quán)值進(jìn)行排序(從小到大)
    int i, j;
    for ( i = 0; i < G->numEdges; i++)
    {
        for ( j = i + 1; j < G->numEdges; j++)
        {
            if (edges[i].weight > edges[j].weight)
            {
                Swapn(edges, i, j);
            }
        }
    }
    
    printf("邊集數(shù)組根據(jù)權(quán)值排序之后的為:\n");
    for (i = 0; i < G->numEdges; i++)
    {
        printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
    }
    
}

/* 生成最小生成樹(shù) */
void MiniSpanTree_Kruskal(MGraph G)
{
    int i, j, n, m;
    int sum = 0;
    int k = 0;
    /* 定義一數(shù)組用來(lái)判斷邊與邊是否形成環(huán)路
     用來(lái)記錄頂點(diǎn)間的連接關(guān)系. 通過(guò)它來(lái)防止最小生成樹(shù)產(chǎn)生閉環(huán);*/
    
    int parent[MAXVEX];
    /* 定義邊集數(shù)組,edge的結(jié)構(gòu)為begin,end,weight,均為整型 */
    Edge edges[MAXEDGE];
    
    /*1. 用來(lái)構(gòu)建邊集數(shù)組*/
    for ( i = 0; i < G.numVertexes-1; i++)
    {
        for (j = i + 1; j < G.numVertexes; j++)
        {
            //如果當(dāng)前路徑權(quán)值 != ∞
            if (G.arc[i][j]<INFINITYC)
            {
                //將路徑對(duì)應(yīng)的begin,end,weight 存儲(chǔ)到edges 邊集數(shù)組中.
                edges[k].begin = i;
                edges[k].end = j;
                edges[k].weight = G.arc[i][j];
                
                //邊集數(shù)組計(jì)算器k++;
                k++;
            }
        }
    }
    
    //2. 對(duì)邊集數(shù)組排序
    sort(edges, &G);
    
    
    //3.初始化parent 數(shù)組為0. 9個(gè)頂點(diǎn);
    // for (i = 0; i < G.numVertexes; i++)
    for (i = 0; i < MAXVEX; i++)
        parent[i] = 0;
    
    //4. 計(jì)算最小生成樹(shù)
    printf("打印最小生成樹(shù):\n");
    /* 循環(huán)每一條邊 G.numEdges 有15條邊*/
    for (i = 0; i < G.numEdges; i++)
    {
        //獲取begin,end 在parent 數(shù)組中的信息;
        //如果n = m ,將begin 和 end 連接,就會(huì)產(chǎn)生閉合的環(huán).
        n = Find(parent,edges[i].begin);
        m = Find(parent,edges[i].end);
        //printf("n = %d,m = %d\n",n,m);
        
        /* 假如n與m不等,說(shuō)明此邊沒(méi)有與現(xiàn)有的生成樹(shù)形成環(huán)路 */
        if (n != m)
        {
            /* 將此邊的結(jié)尾頂點(diǎn)放入下標(biāo)為起點(diǎn)的parent中。 */
            /* 表示此頂點(diǎn)已經(jīng)在生成樹(shù)集合中 */
            parent[n] = m;
            
            /*打印最小生成樹(shù)路徑*/
            printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
            sum += edges[i].weight;
        }
    }
    
    printf("sum = %d\n",sum);
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容