標籤:c 資料結構 最小產生樹 圖
/*
1.一個有n個頂點的連通圖的產生樹是原圖的極小連通圖,它包含原圖中的所有n個頂點,並且具有保持圖連通的最小的邊。
顯然有如下推論:
a.若刪除產生樹中的一條邊,就會使該產生樹因變成非連通圖而不再滿足產生樹的定義;
b.若在產生樹中增加一條邊,就會使該產生樹因存在迴路而不再滿足產生樹的定義。
c.一個連通圖的產生樹可能有許多,使用不同尋找方法可以得到不同的產生樹。
2.對於有n個頂點的無向圖,無論它的產生樹的形狀如何,一定有n個頂點,有且只有n-1條邊
3.如果無向連通圖是一個帶權圖,那麼它的所有產生樹中必有一棵邊的權值總和最小的產生樹,
我們稱這棵產生樹為最小代價產生樹,簡稱為最小產生樹。
4.構造有n個頂點的無向連通帶權圖的最小產生樹必須滿足以下三個要求:
a.構造的最小產生樹必須包括n個頂點;
b.構造的最小產生樹中有且只有n-1條邊
c.構造的最小產生樹中部存在迴路
5.構造最小產生樹的典型方法有2種:一種稱作普裡姆(Prim)演算法,一種稱作克魯斯卡爾(Kruskal)演算法
6.普裡姆演算法思想:
假設G=(V,E)為一個帶權圖,其中V為帶權圖中頂點的集合,E為帶權圖中邊的權值機會。
設定兩個新的集合U和T,其中U用於存放帶權圖G的最小產生樹的頂點的機會,T用於存放帶權圖G的最小產生樹
的權值的集合。
普裡姆演算法的思想是:令集合U的初值為U={u0},即假設構造最小產生樹時從頂點u0開始,集合T的初值為T={}。
從所有頂點u屬於U和頂點v屬於V-U的帶權邊中,選出具有最小權值的邊(u,v),將頂點v加入集合U中,將邊(u,v)
加入集合T中。如此不斷重複,當U=V時,最小產生樹構造完畢。此時,集合U中存放著最小產生樹頂點的集合,集合T中存放著最小
產生樹邊的權值的集合。
7.克魯斯卡爾演算法:
設無向連通帶權圖G=(V,E),其中V為頂點集合,E為邊的集合。克魯斯卡爾演算法的思想是:
設帶權圖G的最小產生樹T由頂點集合和邊的集合構成,其初值為T=(V,{}),即初始時最小產生樹T只有帶權圖G中的頂點集合組成,
各頂點之間沒有一條邊。這樣,最小產生樹T中的各個頂點各自構成一個連通分量。然後,按照邊的權值遞增的順序考察帶權圖G中
的邊集合E中的各條邊,若被考察的邊的兩個頂點屬於T的2個不同的連通分量,則將此邊加入到最小產生樹T中,同時把兩個連通分量串連
為一個連通分量;若被考察的邊的兩個頂點屬於T的同一個連通分量,則將此邊捨去,當T中的連通分量個數為1時,T中的該連通分量即為帶權圖
G的一棵最小產生樹。
克魯斯卡爾演算法主要包括2個部分:首先是帶權圖G中e條邊的權值的排序,其次是判斷選取的邊的2個頂點是否屬於同一個連通分量。
8.克魯斯卡爾演算法的時間複雜主要由排序方法決定,而克魯斯卡爾演算法的排序演算法只與邊的個數有關,與圖頂點的個數無關。當使用時間複雜度為
O(elbe)的排序演算法時,克魯斯卡爾演算法得時間複雜度即為O(elbe)。因此,當帶權圖的頂點個數較多,而邊的個數較少時,使用克魯斯卡爾演算法
構造最小產生樹的時間效率較好。
*/
#include<stdio.h>#include<malloc.h>#define MaxSize 10 //定義元素的大小typedef char DataType; //定義一個類型#define MaxVertices 10 //定義頂點的最大值#define MaxWeight 10000 //定義無窮大的具體值typedef char VerT;typedef struct{ //定義一個結構體 DataType list[MaxSize]; int size; //結構體元素的大小}SeqList; //結構體的對象typedef struct{SeqList Vertices;//存放頂點的順序表int edge[MaxVertices][MaxVertices];//存放邊的鄰接矩陣int numOfEdges;//邊的條數}AdjMGraph;typedef struct{int row;//行下標int col;//列下標int weight;//權值}RowColWeight;//邊資訊結構體typedef struct{VerT vertex;//儲存最小產生樹每條邊的弧頭頂點資料int weight;//儲存最小產生樹的相應邊的權值}MinSpanTree;//初始化void initiate(SeqList *L){ L->size=0;//定義初始化元素個數}//求當前元素的個數int getLength(SeqList L){return L.size;//返回長度}//插入資料元素int insertData(SeqList *L,int i,DataType x){//在順序表L的第i(0<=i<=size)個位置前插入資料元素x//插入成功返回1,出人失敗返回0 int j; if(L->size>=MaxSize){ printf("順序表已滿,無法插入!!\n"); return 0; }else if(i<0||i>L->size){ printf("插入的位置不合法,不在指定的範圍,參數i不合法!\n"); return 0; }else{ //從後向前一致移動資料,為插入做準備 for(j=L->size;j>i;j--){ L->list[j]=L->list[j-1]; } L->list[i]=x; L->size++; return 1; }}//刪除資料int deleteData(SeqList *L,int i,DataType *x){ //刪除順序表中位置為i的資料i>=0&&i<=size-1,把資料儲存到x中//刪除成功返回1,否則返回0int j;if(L->size<=0){ printf("順序表已空無資料元素可刪!\n");return 0;}else if(i<0||i>L->size-1){ printf("參數i不合法,不能刪除!\n");return 0;}else{*x=L->list[i];for(j=i+1;j<=L->size-1;j++){//從前往後一次前移 L->list[j-1]=L->list[j];}L->size--;//資料元素減一return 1;}}//取出資料元素int getData(SeqList L,int i,DataType *x){ if(i<0||i>L.size-1){printf("參數i不合法,不能刪除!\n");return 0;}else{ *x=L.list[i];return 1;}}//初始化有n個頂點的順序表和鄰接矩陣void InitiateG(AdjMGraph *g,int n){//初始化int i,j;for(i=0;i<n;i++){for(j=0;j<n;j++){if(i==j){g->edge[i][j]=0;}else{g->edge[i][j]=MaxWeight;//MaxWeight表示無窮大}}}g->numOfEdges=0;//邊的條數置為0initiate(&g->Vertices);//順序表初始化}//插入頂點void InsertVertex(AdjMGraph *g,DataType vertex){//在圖G中插入頂點vertexinsertData(&g->Vertices,g->Vertices.size,vertex);//順序表尾插入}//插入邊void InsertEdge(AdjMGraph *g,int v1,int v2,int weight){//在圖中插入邊<v1,v2>,邊<v1,v2>的權為weightif(v1<0||v1>=g->Vertices.size||v2<0||v2>=g->Vertices.size){printf("參數v1或v2越界出錯!!!\n");return ;}g->edge[v1][v2]=weight;g->numOfEdges++;}//刪除邊void DeleteEdge(AdjMGraph *g,int v1,int v2){//在G圖中刪除邊<v1,v2>if(v1<0||v1>=g->Vertices.size||v2<0||v2>=g->Vertices.size){printf("參數v1或v2越界出錯!!!\n");return ;}if(g->edge[v1][v2]==MaxWeight||v1==v2){printf("該邊不存在!!!\n");return;}g->edge[v1][v2]=MaxWeight;g->numOfEdges--;}//取第一個鄰接頂點int GetFirstVex(AdjMGraph g,int v){//在圖G中尋找序號為v的頂點的第一個鄰接頂點//如果這樣的頂點存在,則返回該鄰接頂點的序號,否則返回-1int col;if(v<0||v>=g.Vertices.size){printf("參數v1越界出錯!!!\n");return -1;}for(col=0;col<g.Vertices.size;col++){if(g.edge[v][col]>0&&g.edge[v][col]<MaxWeight){return col;}}return -1;}//取下一個鄰接頂點int GetNextVex(AdjMGraph g,int v1,int v2){//在圖中尋找v1頂點的鄰接頂點v2的下一個鄰接頂點//如果這樣的鄰接頂點存在,則返回該鄰接頂點的序號;否則返回-1//v1和v2都是相應的頂點的序號int col;if(v1<0||v1>g.Vertices.size||v2<0||v2>=g.Vertices.size){printf("參數v1或v2越界出錯!!!\n");return -1;}for(col=v2+1;col<g.Vertices.size;col++){if(g.edge[v1][col]>0&&g.edge[v1][col]<MaxWeight){return col;}}return -1;}void CreatGraph(AdjMGraph *g,DataType V[],int n,RowColWeight E[],int e){//在圖中插入n個頂點資訊V和e條邊資訊Eint i,k;InitiateG(g,n);//d頂點順序表初始化for(i=0;i<n;i++){InsertVertex(g,V[i]);//插入頂點}for(k=0;k<e;k++){InsertEdge(g,E[k].row,E[k].col,E[k].weight);//插入邊}}//普裡姆函數設計//參數g為鄰接矩陣儲存結構的圖//closeVertex為通過函數得到的最小產生樹的頂點和相應頂點邊的權值資料void Prim(AdjMGraph g,MinSpanTree closeVertex[]){//用普裡姆演算法建立帶權圖G的最小產生樹closeVertexVerT x;int n=g.Vertices.size,minCost;int *lowCost=(int *)malloc(sizeof(int)*n);//儲存集合U中頂點ui與集合V-U中頂點vj的所有邊中當前具有最小權值的邊(u,v)int i,j,k;for(i=1;i<n;i++){//初始化//lowCost的初始值為鄰接矩陣數組中第0行的值lowCost[i]=g.edge[0][i];//存放了從集合U中頂點0到集合V-U中各個頂點的權值}//從頂點0出發構造最小產生樹getData(g.Vertices,0,&x);//取頂點0closeVertex[0].vertex=x;//儲存頂點lowCost[0]=-1;//標記頂點for(i=1;i<n;i++){//尋找當前最小權值的邊對應的弧頭頂點kminCost=MaxWeight;//MaxWeight為定義的最大值for(j=1;j<n;j++){if(lowCost[j]<minCost&&lowCost[j]>0){minCost=lowCost[j];k=j;}}getData(g.Vertices,k,&x);//取弧頭頂點kcloseVertex[i].vertex=x;//儲存弧頭頂點k的資料closeVertex[i].weight=minCost;//儲存相應的權值lowCost[k]=-1;//標誌頂點k//根據加入集合U的頂點k修改lowCost中的數值for(j=1;j<n;j++){if(g.edge[k][j]<lowCost[j]){lowCost[j]=g.edge[k][j];}}}}void main(){AdjMGraph g;DataType a[]={'A','B','C','D','E','F','G'};RowColWeight rcw[]={{0,1,50},{1,0,50},{0,2,60},{2,0,60},{1,3,65},{3,1,65},{1,4,40},{4,1,40},{2,3,52},{3,2,52},{2,6,45},{6,2,45},{3,4,50},{4,3,50},{3,5,30},{5,3,30},{3,6,42},{6,3,42},{6,2,45},{4,5,70},{5,4,70}};int n=7,e=20;int i,j; MinSpanTree closeVertex[7];//定義儲存最小產生樹的數組CreatGraph(&g,a,n,rcw,e);//建立圖 Prim(g,closeVertex);//調用Prim函數//輸出Prim函數得到最小產生樹的頂點序列和權值printf("初始頂點=%c\n",closeVertex[0].vertex); for(i=1;i<n;i++){printf("頂點=%c 邊的權值=%d\n",closeVertex[i].vertex,closeVertex[i].weight);}}
結果輸出為:
最小產生樹