(C/C++)-最小生成樹算法(Prim&Kruskal)和單源最短路徑算法(Dijkstra)
1、什麼是最小生成樹
對於一個帶權連通無向圖G=(V,E),圖G的不同的生成樹,其所對應的生成樹的權值可能不同。設R是G所有生成樹的集合,T是R中權值最小的那棵生成樹,則稱T爲G的最小生成樹
最小生成樹的性質
- 最小生成樹可能不唯一,但其對應的邊的權值之和總是唯一的,且是最小的;
- 最小生成樹的邊數爲頂點數-1;
2、Prim算法的實現(選點)
圖G=(V,E),其中V爲所有結點的集合,E爲圖G的邊的集合
用輔助集合s,來描述被選中的頂點
思路:
- 首先將第一個頂點放入集合s中,即s={1};
- 做如下貪心選擇,當s是v的真子集時,選取i屬於s,j屬於V-S,且c[i][j]是最小的邊,並將頂點j加入到集合s中;
- 當S=V時選取結束,此時就生成了一棵最小生成樹;
如圖所示,描述了Prim算法選取頂點的過程:
具體實現過程:
void prim(int c[N][N], int n)
{
int lowcost[N];//存儲非集合s頂點到集合s中頂點最小邊的權值
int closest[N];//存儲非集合s頂點到集合s中權值最小邊的頂點
int s[N];
s[1] = 1;
printf("1 ");
//lowcost[]和closest[]的初始化
for (int i = 2; i < N; i++)
{
closest[i] = 1;
lowcost[i] = c[1][i];
s[i] = 0;
}
//每次循環箱集合s中添加一個頂點
for (int i = 2; i < N; i++)
{
int min = maxint;
int temp = 1;//用來記錄選擇的頂點
//做貪心選擇,選擇非集合s中頂點到s中頂點最小的權值邊的頂點
for (int j = 2; j < N; j++)
if (!s[j] && min > lowcost[j])
{
//更新值,並記錄
min = lowcost[j];
temp = j;
}
s[temp] = 1;//頂點加入到集合s
printf("%d ", temp);
//更新lowcost[]和closest[]
for(int j=2;j<N;j++)
if (!s[j] && lowcost[j] > c[j][temp])
{
lowcost[j] = c[j][temp];
closest[j] = temp;
}
}
}
在main()函數中測試一下Prim算法:
int main()
{
int c[N][N] = {
//0號下標不用
{
maxint,maxint,maxint,maxint,maxint,maxint,maxint},//0
{
maxint,maxint,6,1,5,maxint,maxint},//1
{
maxint,6,maxint,5,maxint,3,maxint},//2
{
maxint,1,5,maxint,5,6,4},//3
{
maxint,5,maxint,5,maxint,maxint,2},//4
{
maxint,maxint,3,6,maxint,maxint,6},//5
{
maxint,maxint,maxint,4,2,6,maxint}//6
};
printf("Prim點選取順序爲:\n");
prim(c, N);
return 0;
}
3、Kruskal算法的實現(選邊)
圖G=(V,E),其中V爲所有結點的集合,E爲圖G的邊的集合
採用Kruskal算法時,採用了並查集作爲輔助的數據結構
並查集中兩個重要的實現函數:
- int find_root(int x):查找以結點x的根結點;
- int union_set(int x,int y):合併x,y所在的兩個子集,若兩個子集存在迴路返回0,不存在迴路返回1;
思路:
- 將圖G的邊集按照權值大大小升序排列
- 每次選取未被選取過,且權值最小的邊,若選取該邊構成迴路,則捨棄該邊,選擇下一條權值最小的邊;
- 直到圖G所有的頂點在一個連通分量上;
如圖所示,描述了Kruskal算法選取頂點的過程:
具體實現過程:
邊的結構體
typedef struct edge
{
int x;
int y;
int weight;
char name;
int selected;
}Edge;
並查集的函數
int parent[N];//記錄頂點的父結點
int rank[N];//記錄以頂點爲根的樹的深度(壓縮路徑)
//初始化parent[]和rank[]
void init()
{
for (int i = 1; i < N; i++)
{
parent[i] = -1;//初始時,每個頂點的父結點默認爲-1
rank[i] = 1;
}
}
//查找頂點x的根結點
int find_root(int x)
{
int x_root = x;
while (parent[x_root] != -1)
x_root = parent[x_root];
return x_root;
}
//合併兩個頂點所在的子集,合併成功返回1,失敗返回0
int union_set(int i, int j)
{
int i_root = find_root(i);
int j_root = find_root(j);
if (i_root == j_root)
return 0;//i,j在同一個子集中,合併失敗
//i,j不在同一個子集中,合併兩個子集
if (rank[i_root] > rank[j_root])
parent[j_root] = i_root;
else if (rank[i_root] < rank[j_root])
parent[i_root] = parent[j_root];
else
{
//i,j深度相同的情況下
parent[i_root] = j_root;
rank[j_root]++;
}
return 1;
}
Kruskal實現
//n是邊的個數
void kruskal(Edge e[], int n)
{
init();
qsort(e, 11, sizeof(e[1]), cmp);//升序
for (int i = 1; i <= n; i++)
{
int x = find_root(e[i].x);
int y = find_root(e[i].y);
if (x != y)
{
//無迴路,選中此邊
e[i].selected = 1;
union_set(e[i].x, e[i].y);
printf("%c ", e[i].name);
}
}
/*
for (int i = 1; i <= n; i++)
if (e[i].selected)
printf("%c ", e[i].name);*/
}
測試函數
int main()
{
Edge e[11] = {
//10條邊
{
0},//此條數據不用
{
1,4,5,'a',0},{
4,6,2,'b',0},{
6,5,6,'c',0},{
5,2,3,'d',0},{
2,1,6,'e',0},
{
1,3,1,'f',0},{
3,4,5,'g',0},{
3,6,4,'h',0},{
3,5,6,'i',0},{
3,2,5,'j',0}
};
int n = 10;
kruskal(e, n);
return 0;
}
4、Dijkstra算法的實現(選點)
圖G=(V,E),其中V爲所有結點的集合,E爲圖G的邊的集合
採用輔助數組dist[i],來存儲從源點到i的最短距離
思路:
- 從集合V-S中選取頂點j,滿足dist[j]=min{dist[i]|i屬於V-S},並將頂點j加入到集合s中
- 集合s擴充後,修改源點出發到V-S上任一頂點k的最短路徑,dist[j]+c[j][k]<dist[k](這裏是Dijkstra區別於Prim的最重要一點)
- 重複上述步驟,知道所有頂點都在集合S中
如圖示
具體實現:
//dist[i]存儲從源點到i的最短路徑
void dijkstra(int c[][N], int dist[], int start)
{
int s[N];
//初始化參數
for (int i = 1; i < N; i++)
{
dist[i] = c[start][i];
s[i] = 0;
}
s[start] = 1;
//每次循環箱集合s中添加一個頂點
for (int i = 2; i < N; i++)
{
int tmp = maxint;
int u = start;
//做貪心選擇,選擇非集合s中頂點到源點最小的權值邊的頂點
for (int j = 1; j < N; j++)
{
if (!s[j] && dist[j] < tmp)
{
//記錄
u = j;
tmp = dist[j];
}
}
s[u] = 1;
//更新dist[]的值
for (int j = 1; j < N; j++)
if (!s[j] && (dist[u] + c[u][j] < dist[j]))
dist[j] = dist[u] + c[u][j];
}
}
main函數中的測試:
int main()
{
int c[N][N] = {
{
0},//0號下標不使用
{
0,0,10,maxint,maxint,5},
{
0,maxint,0,1,maxint,2},
{
0,maxint,maxint,0,4,maxint},
{
0,7,maxint,6,0,maxint},
{
0,maxint,3,9,2,0}
};//5個頂點,0號下標不使用
int dist[N], start = 1;
dijkstra(c, dist, start);
for (int i = 1; i < N; i++)
printf("dist[%d]=%d\t", i, dist[i]);
printf("\n");
return 0;
}