最小生成樹---普里姆演算法(Prim演算法)和克魯斯卡爾演算法(Kruskal演算法)

鹹魚的習慣發表於2021-02-21

      最小生成樹的性質:MST性質(假設N=(V,{E})是一個連通網,U是頂點集V的一個非空子集,如果(u,v)是一條具有最小權值的邊,其中u屬於U,v屬於V-U,則必定存在一顆包含邊(u,v)的最小生成樹)

普里姆演算法(Prim演算法)

思路:以點為目標構建最小生成樹
1.將初始點頂點u加入U中,初始化集合V-U中各頂點到初始頂點u的權值;
2.根據最小生成樹的定義:從n個頂點中,找出 n - 1條連線,使得各邊權值最小。迴圈n-1次如下操作:
      (1)從陣列lowcost[k]中找到vk到集合U的最小權值邊,並從陣列arjvex[k] = j中找到該邊在集合U中的頂點下標
      (2)列印此邊,並將vk加入U中。
      (3)通過查詢鄰接矩陣Vk行的各個權值,即vk點到V-U中各頂點的權值,與lowcost的對應值進行比較,若更小則更新lowcost,並將k存入arjvex陣列中

以下圖為例

#include<bits/stdc++.h>
using namespace std;
#define MAXVEX 100
#define INF 65535
typedef char VertexType;
typedef int EdgeType;
typedef struct {
	VertexType vexs[MAXVEX];
	EdgeType arc[MAXVEX][MAXVEX];
	int numVertexes, numEdges;
}MGraph;

void CreateMGraph(MGraph *G) {
	int m, n, w;	//vm-vn的權重w 	
	scanf("%d %d", &G->numVertexes, &G->numEdges);
	
	for(int i = 0; i < G->numVertexes; i++) {
		getchar();
		scanf("%c", &G->vexs[i]);
	}

	for(int i = 0; i < G->numVertexes; i++) {
		for(int j = 0; j < G->numVertexes; j++) {
			if(i == j)	G->arc[i][j] = 0;
			else	G->arc[i][j] = INF;
		}
	}		
	for(int k = 0; k < G->numEdges; k++) {
		scanf("%d %d %d", &m, &n, &w);
		G->arc[m][n] = w; 
		G->arc[n][m] = G->arc[m][n];
	}
}

void MiniSpanTree_Prim(MGraph G) {
	int min, j, k;
	int arjvex[MAXVEX];		//最小邊在 U集合中的那個頂點的下標 
	int lowcost[MAXVEX];	// 最小邊上的權值
	//初始化,從點 V0開始找最小生成樹T 
	arjvex[0] = 0;		//arjvex[i] = j表示 V-U中集合中的 Vi點的最小邊在U集合中的點為 Vj 
	lowcost[0] = 0;		//lowcost[i] = 0表示將點Vi納入集合 U ,lowcost[i] = w表示 V-U中 Vi點到 U的最小權值 
	for(int i = 1; i < G.numVertexes; i++) {
		lowcost[i] = G.arc[0][i];
		arjvex[i] = 0;
	} 
	//根據最小生成樹的定義:從n個頂點中,找出 n - 1條連線,使得各邊權值最小
	for(int i = 1; i < G.numVertexes; i++) {
		min = INF, j = 1, k = 0;
		//尋找  V-U到 U的最小權值min 
		for(j; j < G.numVertexes; j++) {
			// lowcost[j] != 0保證頂點在 V-U中,用k記錄此時的最小權值邊在 V-U中頂點的下標 
			if(lowcost[j] != 0 && lowcost[j] < min) {
				min = lowcost[j];
				k = j;
			}
		}
	} 
	printf("V[%d]-V[%d] weight = %d\n", arjvex[k], k, min); 
	lowcost[k] = 0;		//表示將Vk納入 U
	//查詢鄰接矩陣Vk行的各個權值,與lowcost的對應值進行比較,若更小則更新lowcost,並將k存入arjvex陣列中
	for(int i = 1; i < G.numVertexes; i++) {
		if(lowcost[i] != 0 && G.arc[k][i] < lowcost[i]) {
			lowcost[i] = G.arc[k][i];
			arjvex[i] = k;
		}
	} 
	
}

int main() {
	MGraph *G = (MGraph *)malloc(sizeof(MGraph));
	CreateMGraph(G);
	MiniSpanTree_Prim(*G);
} 
 
/*
input:
	4 5	
	a
	b
	c
	d
	0 1 2
	0 2 2
	0 3 7
	1 2 4
	2 3 8

output:
	V[0]-V[1] weight = 2
	V[0]-V[2] weight = 2
	V[0]-V[3] weight = 7
	最小總權值: 11
*/

時間複雜度O(n^2)

克魯斯卡爾演算法(Kruskal演算法)

思路:以邊為目標進行構建最小生成樹
      在邊集中依次尋找最小權值邊,若構建是不形成環路(利用parent陣列記錄各點的連通分量),則將其新增到最小生成樹中。
判斷是否形成環路思路:
      初始化parent陣列為各個頂點的下標,以此表示每個頂點為不同的連通分量,尋找最小權值邊時,判斷該邊起點和終點是否屬於同一連通分量,如果不屬於則將該邊加入最小生成樹中,並將該邊的起點和終點的連通分量更改為已形成的最小生成樹起始點的連通分量。

#include<bits/stdc++.h>
using namespace std;
#define MAXVEX 100
#define MAXEDGE 100
#define INF 65535
typedef char VertexType;
typedef int EdgeType;
//圖的鄰接矩陣儲存結構的定義 
typedef struct {
	VertexType vexs[MAXVEX];
	EdgeType arc[MAXVEX][MAXVEX];
	int numVertexes, numEdges;
}MGraph;

//邊集陣列Edge結構的定義 
typedef struct {
	int begin;
	int end;
	int weight;
}Edge;

bool vis[100][100];

void CreateMGraph(MGraph *G) {
	int m, n, w;	//vm-vn的權重w 	
	scanf("%d %d", &G->numVertexes, &G->numEdges);
	
	for(int i = 0; i < G->numVertexes; i++) {
		getchar();
		scanf("%c", &G->vexs[i]);
	}

	for(int i = 0; i < G->numVertexes; i++) {
		for(int j = 0; j < G->numVertexes; j++) {
			if(i == j)	G->arc[i][j] = 0;
			else	G->arc[i][j] = INF;
		}
	}		
	for(int k = 0; k < G->numEdges; k++) {
		scanf("%d %d %d", &m, &n, &w);
		G->arc[m][n] = w; 
		G->arc[n][m] = G->arc[m][n];
	}
}

void MiniSpanTree_Kruskal(MGraph G) {
	int v1, v2, vs1, vs2;
	Edge edges[MAXEDGE];
	int parent[MAXVEX]; //標記各頂點所屬的連通分量,用於判斷邊與邊是否形成環路 
	
	//將鄰接矩陣轉換成按權值從小到大排序的邊集陣列 
	/*
	
	*/	
	int tmp = 0, m, n, ans;
	ans = (G.numVertexes*G.numVertexes) / 2 - G.numVertexes / 2;	
	for(int k = 0; k < ans; k++) {
		int min = INF, i, j;
		for(i = 0; i < G.numVertexes; i++) {
			for(j = 0; j < G.numVertexes; j++) {
				if(!vis[i][j] && i < j && min > G.arc[i][j]) {
					min = G.arc[i][j];
					m = i;
					n = j;			
				}
			}
		}	
		if(G.arc[i][j] == INF)
			continue;			
		edges[tmp].begin = m;
		edges[tmp].end = n;
		edges[tmp].weight = min;
		vis[m][n] = true;
		tmp++;	
	}	
	 
	
	//初始化為各頂點各自為一個連通分量 
	for(int i = 0; i < G.numVertexes; i++)
		parent[i] = i;
		
	for(int i = 0; i < G.numEdges; i++) {
		//起點終點下標 
		v1 = edges[i].begin;
		v2 = edges[i].end;
		//起點終點連通分量 
		vs1 = parent[v1];
		vs2 = parent[v2];
		//邊的兩個頂點屬於不同的連通分量,列印,將新來的連通分量更改為起始點的連通分量 
		if(vs1 != vs2) {
			printf("V[%d]-V[%d] weight:%d\n", edges[i].begin, edges[i].end, edges[i].weight);
			for(int j = 0; j < G.numVertexes; j++) {
				if(parent[j] == vs2)	parent[j] = vs1;
			}
		}
	} 
}

int main() {
	MGraph *G = (MGraph *)malloc(sizeof(MGraph));
	CreateMGraph(G);
	MiniSpanTree_Kruskal(*G);
} 

/*
input:
	4 5	
	a
	b
	c
	d
	0 1 2
	0 2 2
	0 3 7
	1 2 4
	2 3 8

output:
	V[0]-V[1] weight:2
	V[0]-V[2] weight:2
	V[0]-V[3] weight:7

*/

時間複雜度O(elog2e) e為邊數

相關文章