基于算法导论图算法-最小生成树(第23章)
- 题目描述
- 问题分析
- 源代码
- 结果截图
题目描述
分别使用Kruskal算法和Prime算法求最小生成树(无向图)
问题分析
源代码
const int MAXN = 110;//最大点数
const int MAXM = 10000;//最大边数
struct Edge {
int u, v, w;
};
//const int INF = 0x3f3f3f3f;//已经在Graph.h中定义
void init_G(Graph G);//将图的链表表示改为矩阵表示
int kruskal(int n);//返回最小生成树的权值,如果不联通返回-1,最小生成树的边集存入到数组vis_edge中
int Prim_matrix(int cost[][MAXN], int n);//图的矩阵形式prime
int Prim(Graph G);//图的链表形式的prime
void print_minTree();
#include<algorithm>
struct Edge graph_edge[MAXM];//存储边的信息,包括起点终点和权值
int Graph_edgeNum;//图的边数,用于排序
struct Edge minT_edge[MAXN];//记录最小生成树的边集
int minT_edgeNum;//最小生成树边数,加边前赋值0
//并查集使用
int father[MAXN];
int _rank[MAXN];
void Make_Set(int n) {//将每个点独立
for (int i = 0; i < n; i++) {
father[i] = i;
_rank[i] = 0;
}
}
int find(int n) {//找根
if (n != father[n]) father[n] = find(father[n]); //路径压缩
return father[n];
}
void Union(int x, int y) {//联合
x = find(x);
y = find(y);
if (_rank[x] > _rank[y]) father[y] = x;
else if (_rank[y] > _rank[x]) father[x] = y;
else {
father[x] = y;
_rank[y]++;
}
}
void addedge(Edge* tempEdge ,int u, int v, int w , int edge_count)//添边
{
tempEdge[edge_count].u = u;
tempEdge[edge_count].v = v;
tempEdge[edge_count].w = w;
}
bool cmp(Edge a, Edge b) {//排序,从小到大按照权值进行排序,用于kruskal
return a.w < b.w;
}
int cost[MAXN][MAXN];//用于prime_matrix
int pre[MAXN];//用于prime_matrix
void init_G(Graph G) {//将图的链表表示改为矩阵表示
//将图的链表表示改为矩阵表示(prime_matrix的初始化)
PtrToNode ptr;
for (int i = 0; i < G->vexnum; i++) {
for (int j = 0; j < G->vexnum; j++) {
cost[i][j] = INF;
}
for (ptr = G->vertices[i].adjto; ptr != NULL; ptr = ptr->next) {
cost[i][ptr->adjvex] = ptr->weight;
}
cost[i][i] = 0;
}
//将无向图转变为无向边的集合,kruskal的初始化
for (int i = 0; i < G->vexnum; i++) {
for (int j = i + 1; j < G->vexnum; j++) {
if (cost[i][j]!=INF) {
addedge(graph_edge, i, j, cost[i][j], Graph_edgeNum++);
}
}
}
}
int kruskal(int n) {
/////////////////////////init();//初始化图的边集(可以通过用户输入的方式)
//n为图顶点数,graph_edge为图的边集,以下进行kruskal
Make_Set(n);
sort(graph_edge, graph_edge+Graph_edgeNum, cmp);
int minT_weight = 0;//权值初始为0
minT_edgeNum = 0;
for (int i = 0; i < Graph_edgeNum; i++) {
int u = graph_edge[i].u;
int v = graph_edge[i].v;
int w = graph_edge[i].w;
int t1 = find(u);
int t2 = find(v);
if (t1 != t2) {
minT_weight += w;
addedge(minT_edge, u, v, w, minT_edgeNum++);
Union(u, v);
}
if (minT_edgeNum == n - 1) break;//如果选择的边够了顶点数-1,则不用再判断其他边
}
if (minT_edgeNum < n - 1) return -1;//如果图不联通,则达不到n-1
else return minT_weight;
}
bool vis[MAXN];//记录顶点是否已经选入最小生成树
int lowWeight[MAXN];//保存正在建立的最小生成树到各顶点的权
int Prim_matrix(int cost[][MAXN], int n)//点是0~n-1
{
//////////////////////////////////init1();//初始化权矩阵,可以用户输入
int minT_weight = 0;
memset(vis, false, sizeof(vis));
vis[0] = true;//选择第一个点为顶点0
printf("矩阵表示图prime选择点的顺序为:0 ");
//刚开始最小生成树仅包含起始顶点0,所以lowWeight[i]即从顶点0到其他顶点的权(除有边连接的顶点外全部为无穷大)
for (int i = 1; i < n; i++) { lowWeight[i] = cost[0][i]; pre[i] = 0; }
minT_edgeNum = 0;
for (int i = 1; i<n; i++)
{
int minWeight = INF;
int nextVertex = -1;
//选择出下一个顶点比较lowWeight,选出最小值(不包括已选择顶点)
for (int j = 0; j<n; j++)
if (!vis[j] && minWeight>lowWeight[j])
{
minWeight = lowWeight[j];
nextVertex = j;
}
if (minWeight == INF)return -1;//原图不连通
minT_weight += minWeight;//最小生成树权值增加
vis[nextVertex] = true;//下一个顶点已选择
printf("%d ", nextVertex);
addedge(minT_edge, pre[nextVertex], nextVertex, minWeight, minT_edgeNum++);//添加选择的边到最小生成树边集
//更新lowWeight[i]和pre[i],只需要比较新加入的顶点相邻的点即可
for (int j = 0; j<n; j++)
if (!vis[j] && lowWeight[j]>cost[nextVertex][j]) {
lowWeight[j] = cost[nextVertex][j];
pre[j] = nextVertex;
}
}
printf("\n");
return minT_weight;
}
struct Qnode {
int v;
int key;
Qnode(int _v = -1, int _key = -1) :v(_v), key(_key) {}
bool operator<(const Qnode& x)const {
return key > x.key;//这样优先队列建立小根堆
}
};
int Prim(Graph G)//点是0~n-1,算法思想与最短路dijkstra一致唯一不同的是松弛操作
{
int s = 0;//第一个选择的点
int minT_weight = 0;//初始化权值为0
//初始化key和color(对应于prime_matrix中的lowWeight和vis数组)
for (int i = 0; i < G->vexnum; i++) {
G->vertices[i].key = INF;
G->vertices[i].color = 0;//白色,未选入最小生成树
}
G->vertices[s].key = 0;
priority_queue<Qnode> Q;
Q.push(Qnode(s, G->vertices[s].key));
Qnode temp;
PtrToNode ptr;
printf("链表表示图prime选择点的顺序为:");
while (!Q.empty()) {
temp = Q.top();
Q.pop();
int u = temp.v;
//下面两行是为了避免已经选入最小生成树的顶点再次进行计算
if (G->vertices[u].color) continue;
G->vertices[u].color = 1;//黑色
minT_weight += G->vertices[u].key;
printf("%d ",u);
for (ptr = G->vertices[u].adjto; ptr!=NULL; ptr = ptr->next) {
//relax松弛操作
if (!G->vertices[ptr->adjvex].color&&G->vertices[ptr->adjvex].key > ptr->weight) {
G->vertices[ptr->adjvex].key = ptr->weight;
G->vertices[ptr->adjvex].pred = u;
Q.push(Qnode(ptr->adjvex, G->vertices[ptr->adjvex].key));
}
}
}
//判断所有顶点是否全部选入最小生成树,如果没有证明不联通
int flag = 0;
for (int i = 0; i < G->vexnum; i++) {
if (!G->vertices[i].color) {
flag = 1;
break;
}
}
printf("\n");
if (flag) return - 1;
return minT_weight;
}
void print_minTree() {
for (int i = 0; i < minT_edgeNum; i++) {
printf("%d-%d 权值为:%d\n", minT_edge[i].u, minT_edge[i].v,minT_edge[i].w);
}
}
int main() {
//无向图的随机生成([图论(一)图的建立](http://blog.csdn.net/deep_kang/article/details/70877468))
CreateRandomUndirectGraph();
Graph G = CreateUndirectGraph();
printf("打印图结构:\n");
print_EdgeWeight(G);
/*printf("\n下面是bfs:\n");
BFS(G, 0);*/
/*printf("\n下面是dfs:\n");
DFS(G);*/
/*printf("拓扑排序:\n");
if (topSort_queue(G)) {
print_order_queue(G);
topSort_dfs(G);
print_order_dfs(G);
}
else {
printf("失败,此图不满足有向无环图。\n");
}*/
//printf("\n求强连通分量:\n");
//strongly_connected_components(G);
init_G(G);//将图的链表表示改为矩阵表示,并将无向边存入边集数组中,用于prime_matrix和kruskal
int sumWeight = kruskal(G->vexnum);
if (sumWeight != -1) {
printf("kruskal最小生成树(边权和为%d):\n", sumWeight);
print_minTree();
}
else {
printf("此图不连通\n");
}
sumWeight = Prim(G);
if (sumWeight != -1) {
printf("Prim最小生成树(边权和为%d):\n", sumWeight);
print_minTree();
}
else {
printf("此图不连通\n");
}
extern int cost[MAXN][MAXN];
sumWeight = Prim_matrix(cost, G->vexnum);
if (sumWeight != -1) {
printf("Prim_matrix最小生成树(边权和为%d):\n", sumWeight);
print_minTree();
}
else {
printf("此图不连通\n");
}
return 0;
}
结果截图
结果展示以9个顶点为例,三种方式结果一致。