圖的基本算法(最小生成樹)
假設以下情景,有一塊木板。板上釘上了一些釘子。這些釘子能夠由一些細繩連接起來。假設每一個釘子能夠通過一根或者多根細繩連接起來。那麽一定存在這種情況,即用最少的細繩把全部釘子連接起來。
更為實際的情景是這種情況。在某地分布著N
個村莊。如今須要在N
個村莊之間修路,每一個村莊之前的距離不同,問怎麽修最短的路,將各個村莊連接起來。
以上這些問題都能夠歸納為最小生成樹問題,用正式的表述方法描寫敘述為:給定一個無方向的帶權圖G=(V, E)
,最小生成樹為集合T
, T
是以最小代價連接V
中全部頂點所用邊E
T
中的邊能夠形成一顆樹。這是由於每一個節點(除了根節點)都能向上找到它的一個父節點。
解決最小生成樹問題已經有前人開道,Prime
算法和Kruskal
算法。分別從點和邊下手攻克了該問題。
Prim算法
Prim
算法是一種產生最小生成樹的算法。該算法於1930
年由捷克數學家沃伊捷赫·亞爾尼克(英語:Vojtěch Jarník
)發現。並在1957
年由美國計算機科學家羅伯特·普裏姆(英語:Robert C. Prim
)獨立發現。1959
年,艾茲格·迪科斯徹再次發現了該算法。
Prim
算法從隨意一個頂點開始,每次選擇一個與當前頂點集近期的一個頂點,並將兩頂點之間的邊增加到樹中。
Prim
算法在找當前近期頂點時使用到了貪婪算法。
算法描寫敘述:
1. 在一個加權連通圖中,頂點集合V
。邊集合為E
2. 隨意選出一個點作為初始頂點,標記為visit
,計算全部與之相連接的點的距離,選擇距離最短的,標記visit
.
3. 反復以下操作,直到全部點都被標記為visit
:
在剩下的點鐘。計算與已標記visit
點距離最小的點,標記visit
,證明增加了最小生成樹。
以下我們來看一個最小生成樹生成的過程:
1 起初,從頂點a
開始生成最小生成樹
2 選擇頂點a
後,頂點啊置成visit
(塗黑),計算周圍與它連接的點的距離:
3 與之相連的點距離分別為7
6
,4
,選擇C
點距離最短,塗黑C
,同一時候將這條邊高亮增加最小生成樹: 4 計算與
a,c
相連的點的距離(已經塗黑的點不計算),由於與a
相連的已經計算過了,僅僅須要計算與c
相連的點,假設一個點與a,c
都相連。那麽它與a
的距離之前已經計算過了,假設它與c的距離更近。則更新距離值。這裏計算的是未塗黑的點距離塗黑的點的近期距離,非常明顯,b
和a
為7
,b
和c
的距離為6
。更新b
和已訪問的點集距離為6
,而f
,e
和c
的距離各自是8
,9
,所以還是塗黑b
,高亮邊bc
: 5 接下來非常明顯。
d
距離b
最短。將d
塗黑。bd
高亮: 6
f
距離d
為7
,距離b
為4
。更新它的最短距離值是4
。所以塗黑f
,高亮bf
: 7 最後僅僅有
e
了: 針對如上的圖,代碼實比例如以下:
#include<iostream>
#define INF 10000
using namespace std;
const int N = 6;
bool visit[N];
int dist[N] = { 0, };
int graph[N][N] = { {INF,7,4,INF,INF,INF}, //INF代表兩點之間不可達
{7,INF,6,2,INF,4},
{4,6,INF,INF,9,8},
{INF,2,INF,INF,INF,7},
{INF,INF,9,INF,INF,1},
{INF,4,8,7,1,INF}
};
int prim(int cur)
{
int index = cur;
int sum = 0;
int i = 0;
int j = 0;
cout << index << " ";
memset(visit, false, sizeof(visit));
visit[cur] = true;
for (i = 0; i < N; i++)
dist[i] = graph[cur][i];//初始化。每一個與a鄰接的點的距離存入dist
for (i = 1; i < N; i++)
{
int minor = INF;
for (j = 0; j < N; j++)
{
if (!visit[j] && dist[j] < minor) //找到未訪問的點中,距離當前最小生成樹距離最小的點
{
minor = dist[j];
index = j;
}
}
visit[index] = true;
cout << index << " ";
sum += minor;
for (j = 0; j < N; j++)
{
if (!visit[j] && dist[j]>graph[index][j]) //運行更新,假設點距離當前點的距離更近,就更新dist
{
dist[j] = graph[index][j];
}
}
}
cout << endl;
return sum; //返回最小生成樹的總路徑值
}
int main()
{
cout << prim(0) << endl;//從頂點a開始
return 0;
}
Kruskal算法
Kruskal是還有一個計算最小生成樹的算法,其算法原理例如以下。首先,將每一個頂點放入其自身的數據集合中。然後,依照權值的升序來選擇邊。當選擇每條邊時,推斷定義邊的頂點是否在不同的數據集中。假設是,將此邊插入最小生成樹的集合中,同一時候。將集合中包括每一個頂點的聯合體取出。假設不是。就移動到下一條邊。反復這個過程直到全部的邊都探查過。
以下還是用一組圖示來表現算法的過程:
1 初始情況,一個聯通圖,定義針對邊的數據結構,包括起點,終點。邊長度:
typedef struct _node{
int val; //長度
int start; //邊的起點
int end; //邊的終點
}Node;
2 在算法中首先取出全部的邊,將邊依照長短排序,然後首先取出最短的邊,將a
,e
放入同一個集合裏,在實現中我們使用到了並查集的概念:
3 繼續找到第二短的邊,將c
, d
再放入同一個集合裏:
4 繼續找。找到第三短的邊ab
,由於a
,e
已經在一個集合裏。再將b
增加:
5 繼續找,找到b
,e
。由於b
,e
已經同屬於一個集合,連起來的話就形成環了。所以邊be
不增加最小生成樹:
6 再找,找到bc
,由於c
,d
是一個集合的。a
,b
,e
是一個集合。所以再合並這兩個集合:
這樣全部的點都歸到一個集合裏,生成了最小生成樹。
依據上圖實現的代碼例如以下:
#include<iostream>
#define N 7
using namespace std;
typedef struct _node{
int val;
int start;
int end;
}Node;
Node V[N];
int cmp(const void *a, const void *b)
{
return (*(Node *)a).val - (*(Node*)b).val;
}
int edge[N][3] = { { 0, 1, 3 },
{ 0, 4, 1 },
{ 1, 2, 5 },
{ 1, 4, 4 },
{ 2, 3, 2 },
{ 2, 4, 6 },
{ 3, 4, 7}
};
int father[N] = { 0, };
int cap[N] = {0,};
void make_set() //初始化集合,讓全部的點都各成一個集合。每一個集合都僅僅包括自己
{
for (int i = 0; i < N; i++)
{
father[i] = i;
cap[i] = 1;
}
}
int find_set(int x) //推斷一個點屬於哪個集合,點假設都有著共同的祖先結點,就能夠說他們屬於一個集合
{
if (x != father[x])
{
father[x] = find_set(father[x]);
}
return father[x];
}
void Union(int x, int y) //將x,y合並到同一個集合
{
x = find_set(x);
y = find_set(y);
if (x == y)
return;
if (cap[x] < cap[y])
father[x] = find_set(y);
else
{
if (cap[x] == cap[y])
cap[x]++;
father[y] = find_set(x);
}
}
int Kruskal(int n)
{
int sum = 0;
make_set();
for (int i = 0; i < N; i++)//將邊的順序按從小到大取出來
{
if (find_set(V[i].start) != find_set(V[i].end)) //假設改變的兩個頂點還不在一個集合中,就並到一個集合裏。生成樹的長度加上這條邊的長度
{
Union(V[i].start, V[i].end); //合並兩個頂點到一個集合
sum += V[i].val;
}
}
return sum;
}
int main()
{
for (int i = 0; i < N; i++) //初始化邊的數據,在實際應用中可依據詳細情況轉換而且讀取數據,這邊僅僅是測試用例
{
V[i].start = edge[i][0];
V[i].end = edge[i][1];
V[i].val = edge[i][2];
}
qsort(V, N, sizeof(V[0]), cmp);
cout << Kruskal(0)<<endl;
return 0;
}
圖的基本算法(最小生成樹)