最小生成樹——Prim演算法和Kruscal演算法
1、最小生成樹的定義
一個連通圖的生成樹是該連通圖的一個極小連通子圖,它含有圖中全部頂點,但只構成一棵樹的(n-1)條邊。對於一個帶權連通無向圖G的不同生成樹,各棵樹的邊上的權值之和可能不同,邊上的權值之和最小的樹稱為該圖的最小生成樹。
2、最小生成樹的應用
比如要在n個城市之間鋪設光纜,主要目標是要使這 n 個城市的任意兩個之間都可以通訊,但鋪設光纜的費用很高,且各個城市之間鋪設光纜的費用不同,因此另一個目標是要使鋪設光纜的總費用最低。這就需要找到帶權的最小生成樹。
3、最小生成樹的構造演算法
圖的鄰接矩陣型別定義如下:
#define N 100
typedef char ElemType;
//adjacency matrix graph
typedef struct MGraph
{
ElemType vertexes[N];
int edges[N][N];
int n;
}MGraph;
(1)普里姆(Prim)演算法
Prim演算法的核心思想是,從一個頂點開始,加入頂點集合,將頂點集合中所有頂點到其他頂點的邊作為候選邊,每次從候選邊中挑選最小權值的邊作為生成樹的邊,然後更新候選邊,直到圖的所有頂點都被加入為止。其步驟如下:
①初始化頂點集合為傳入的那個頂點,初始化候選邊為該頂點到其他頂點的所有邊
②重複步驟③,直到圖中所有頂點都被加入為止
③從候選邊中挑選最小權值的邊作為生成樹的邊,此時因為加入了新的頂點,所以需要更新候選邊
程式碼實現:
void Prim(MGraph& g, int k)
{
int* w = new int[g.n]; //the least edge weight
int* v = new int[g.n]; //vertexes of the least weight
for (int i = 0; i < g.n; i++)
{
w[i] = g.edges[k][i];
v[i] = k;
}
w[k] = 0;
for (int cnt = 1; cnt < g.n; cnt++)
{
//find the min val of w
int imin = -1;
for (int i = 0; i < g.n; i++)
{
if (w[i] != 0 && (imin == -1 || w[i] < w[imin]))
imin = i;
}
//print
cout << "(" << g.vertexes[v[imin]] << "," << g.vertexes[imin] << ")";
//update the least weight
w[imin] = 0;
for (int i = 0; i < g.n; i++)
{
if (g.edges[imin][i] != 0 && g.edges[imin][i] < w[i])
{
w[i] = g.edges[imin][i];
v[i] = imin;
}
}
}
delete[] w, v;
}
Prim演算法包含兩重for迴圈,其時間複雜度為O(n²)。
(2)克魯斯卡爾(Kruscal)演算法
Kruscal演算法的基本思想是,將圖的所有邊按權值遞增的順序進行排序,然後每次依次從小到大選擇一條邊加入到生成樹中,但加入的前提是加入這條邊後不會構成迴路。其步驟如下:
①將圖的所有邊按權值遞增的順序進行排序
②重複步驟③,直到圖中所有頂點都被加入為止
③按照從小到大的順序選擇一條邊,如果這條邊未使生成樹形成迴路,就把這條邊加入到生成樹中
程式碼實現如下:
void Kruscal(MGraph& g)
{
//define the Edge
typedef struct Edge
{
int u, v;
int w;
Edge(int _u, int _v, int _w) :u(_u), v(_v), w(_w) {}
}Edge;
//init the edges
vector<Edge> v;
for (int i = 0; i < g.n; i++)
{
for (int j = 0; j < g.n; j++)
{
auto w = g.edges[i][j];
if (w != INT16_MAX && w != 0)
v.push_back(Edge(i, j, w));
}
}
//sort by weight increment
sort(v.begin(), v.end(), [](Edge u, Edge v)->int {
return u.w < v.w;
});
//add all edges if it does not constitute a loop
int* set = new int[g.n];
for (int i = 0; i < g.n; i++)
set[i] = i;
vector<Edge>::iterator iter = v.begin();
for (int cnt = 0; cnt < g.n - 1; )
{
if (set[iter->u] != set[iter->v])
{
cout << "(" << g.vertexes[iter->u] << "," << g.vertexes[iter->v] << ")";
cnt++;
for (int i = 0; i < g.n; i++)
{
if (set[i] == set[iter->v])
set[i] = set[iter->u];
}
}
iter++;
}
delete[] set;
}
Kruscal演算法同樣包含兩重for迴圈,其時間複雜度為O(n²),但可以對Kruscal演算法做兩方面的優化,一是將邊集排序改為堆排序,二是用並查集來判斷新加入邊是否構成迴路,優化後Kruscal演算法的時間複雜度為O(elog₂e),e為圖的所有邊。
4、測試
求出給定無向帶權圖的最小生成樹。圖的定點為字元型,權值為不超過100的整形。
輸入
第一行為圖的頂點個數n 第二行為圖的邊的條數e 接著e行為依附於一條邊的兩個頂點和邊上的權值
輸出
最小生成樹中的邊。
樣例輸入
6 10 ABCDEF A B 6 A C 1 A D 5 B C 5 C D 5 B E 3 E C 6 C F 4 F D 2 E F 6
樣例輸出
(A,C)(C,F)(F,D)(C,B)(B,E) (A,C)(D,F)(B,E)(C,F)(B,C)
程式碼如下:
#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
using namespace std;
#define N 100
typedef char ElemType;
//adjacency matrix graph
typedef struct MGraph
{
ElemType vertexes[N];
int edges[N][N];
int n;
}MGraph;
void Prim(MGraph& g, int k)
{
int* w = new int[g.n]; //the least edge weight
int* v = new int[g.n]; //vertexes of the least weight
for (int i = 0; i < g.n; i++)
{
w[i] = g.edges[k][i];
v[i] = k;
}
w[k] = 0;
for (int cnt = 1; cnt < g.n; cnt++)
{
//find the min val of w
int imin = -1;
for (int i = 0; i < g.n; i++)
{
if (w[i] != 0 && (imin == -1 || w[i] < w[imin]))
imin = i;
}
//print
cout << "(" << g.vertexes[v[imin]] << "," << g.vertexes[imin] << ")";
//update the least weight
w[imin] = 0;
for (int i = 0; i < g.n; i++)
{
if (g.edges[imin][i] != 0 && g.edges[imin][i] < w[i])
{
w[i] = g.edges[imin][i];
v[i] = imin;
}
}
}
delete[] w, v;
}
void Kruscal(MGraph& g)
{
//define the Edge
typedef struct Edge
{
int u, v;
int w;
Edge(int _u, int _v, int _w) :u(_u), v(_v), w(_w) {}
}Edge;
//init the edges
vector<Edge> v;
for (int i = 0; i < g.n; i++)
{
for (int j = 0; j < g.n; j++)
{
auto w = g.edges[i][j];
if (w != INT16_MAX && w != 0)
v.push_back(Edge(i, j, w));
}
}
//sort by weight increment
sort(v.begin(), v.end(), [](Edge u, Edge v)->int {
return u.w < v.w;
});
//add all edges if it does not constitute a loop
int* set = new int[g.n];
for (int i = 0; i < g.n; i++)
set[i] = i;
vector<Edge>::iterator iter = v.begin();
for (int cnt = 0; cnt < g.n - 1; )
{
if (set[iter->u] != set[iter->v])
{
cout << "(" << g.vertexes[iter->u] << "," << g.vertexes[iter->v] << ")";
cnt++;
for (int i = 0; i < g.n; i++)
{
if (set[i] == set[iter->v])
set[i] = set[iter->u];
}
}
iter++;
}
delete[] set;
}
int main()
{
MGraph g;
while(cin >> g.n)
{
//input
int e;
cin >> e;
map<char, int> m;
for (int i = 0; i < g.n; i++)
{
cin >> g.vertexes[i];
m[g.vertexes[i]] = i;
}
for (int i = 0; i < g.n; i++)
for (int j = 0; j < g.n; j++)
g.edges[i][j] = INT16_MAX;
char u, v;
int w;
for (int cnt = 0; cnt < e; cnt++)
{
cin >> u >> v >> w;
g.edges[m[u]][m[v]] = g.edges[m[v]][m[u]] = w;
}
//excute
Prim(g, 0); cout << endl; //Prim algorithm
Kruscal(g); cout << endl; //Kruscal algorithm
}
return 0;
}
參考文獻
[1] 李春葆.資料結構教程.清華大學出版社,2013.
[2] 最小生成樹.百度百科[引用日期2018-04-29].相關文章
- 最小生成樹__Prim演算法演算法
- 最小生成樹-Prim演算法和Kruskal演算法演算法
- 最小生成樹:Kruskal演算法和Prim演算法演算法
- 最小生成樹之 Prim 演算法演算法
- 最小生成樹prim普里姆演算法演算法
- 【JAVA演算法】圖論演算法 --求最小生成樹Prim演算法Java演算法圖論
- 最小生成樹---普里姆演算法(Prim演算法)和克魯斯卡爾演算法(Kruskal演算法)演算法
- Prim 最小生成樹 圖解圖解
- prim 樸素 最小生成樹
- 最小生成樹的性質與prim演算法(C++實現)演算法C++
- 最小生成樹,Prim演算法與Kruskal演算法,408方向,思路與實現分析演算法
- 最小生成樹的演算法演算法
- 最小生成樹__Kurskal演算法演算法
- 演算法-圖論-最小生成樹演算法圖論
- 【演算法學習】最小生成樹演算法
- 資料結構------最短路徑Dijkstra和最小生成樹Prim資料結構
- 最小生成樹(MinSpanTree)的Kruskal演算法演算法
- 圖論之帶權圖「最小生成樹之Prim」圖論
- Boruvka求最小生成樹(菠蘿演算法)演算法
- 圖論中的最小生成樹演算法圖論演算法
- 演算法基礎 第5版 第四章 最小生成樹Prim方法的Ruby實現演算法
- 前端必會演算法 - 最小生成樹問題前端演算法
- 演算法 最小高度樹演算法
- 圖論之帶權圖「最小生成樹prim的優化」圖論優化
- 如何在 Java 中實現最小生成樹演算法Java演算法
- 圖論之帶權圖「最小生成樹prim的最佳化」圖論
- 生成樹演算法演算法
- Dijkstra演算法和Prim演算法有什麼區別?演算法
- 最小樹形圖(朱劉演算法)演算法
- 資料結構與演算法之最好學的最小生成樹資料結構演算法
- 最小生成樹(克魯斯卡爾演算法)資料結構演算法資料結構
- 關於圖的最小生成路徑——Kruskal演算法和Prime演算法演算法
- 最小生成樹
- 普里姆(Prim)演算法演算法
- 分享一種最小 Perfect Hash 生成演算法演算法
- 【模板】最小生成樹
- 最小度限制生成樹
- 演算法與資料結構之帶權圖與圖最小生成樹演算法資料結構