- 是一棵树
- 是生成树
- 包含全部顶点
- v-1 条边都在图里
- 向生成树中加入一条边都一定构成回路
- 边权重和最小
最小生成树问题的相关算法都基本按照贪心算法来实现
Prim算法
形象地来说,此算法就是让一棵小树长大——在图中任意选取一结点作为树根,然后继续在图中选取结点让这棵树长大
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| Vertex FindMinDist (MGraph graph, WeightType dist[]){ Vertex minV, v; WeightType minDist = INFINITY; for (v = 0; v < graph->nv; ++v) { if (dist[v] != 0 && dist[v] < minDist){ minDist = dist[v]; minV = v; } } if (minDist < INFINITY) return minV; else return ERROR; }
int Prim(MGraph graph, MGraph MST){ WeightType dist[MAXSIZE], totalWeight; Vertex parent[MAXSIZE], v, w; int vCount; Edge e; for (v = 0; v < graph->nv; ++v) { dist[v] = graph->g[0][v]; parent[v] = 0; } totalWeight = 0; vCount = 0; MST = CreateGraph(graph->nv); e = (Edge)malloc(sizeof(struct ENode)); dist[0] = 0; vCount++; parent[0] = -1; while (1){ v = FindMinDist(graph, dist); if (v == ERROR) break; e->v1 = parent[v]; e->v2 = v; e->weight = dist[v]; InsertEdge(MST, e); totalWeight += dist[v]; dist[v] = 0; vCount++; for (w = 0; w < graph->nv; ++w) if (dist[w] != 0 && graph->g[v][w] < INFINITY){ if (graph->g[v][w] < dist[w]){ dist[w] = graph->g[v][w]; parent[w] = v; } } } if (vCount < graph->nv) totalWeight = ERROR; return totalWeight; }
|
代码分析:
- 首先对 dist[] 数组进行初始化,使数组值下标所在结点与初始结点的权重,parent[] 数组全部初始化为0,建立只有结点的邻接表存储的图MST来保存最小生成树,建立空结点e
- 将初始点0收录进MST,即将 dist[0] 的值设为0,收录的顶点数+1,根节点的父节点设为-1
- 进入循环,直至所有可以收录的结点都被收录
- 调用 FindMinDist() 函数,找到未收录结点中 dist 的最小者 v
- 将 v 结点加入到树中,即边的一端接 v 的父结点,一端接 v 结点,将边插入到MST树中
- 调整总权重,收录的顶点数加一
- 如有其他结点到 v 结点比已知路径断,对 dist 和 parent 数组进行更新
- 判断是否有未访问结点,如有,则说明图有独立结点,返回-1;否则,返回总权重
Kruskal算法
Kruskal算法的主要思想是让森林合并成一棵树——也就是从整个图中从权值最小的边开始,依次挑选,判断是否构成回路,一条边及其两端的两个结点可看作一棵树,挑选完n-1条边之后,形成了n-1棵树,将之合并为一棵生成树
那么应该如何选取最小权重的边呢?
——利用最小堆
如何判断找出来的边在已有的树中不构成回路呢?
——利用并查集
最小堆的定义
最小堆的调整:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void PercDown(Edge Eset, int p, int n){ int parent, child; struct ENode x; x = Eset[p]; for (parent = p; (parent*2) + 1 < n; parent = child) { child = parent*2 + 1; if ((child != n-1) && (Eset[child].weight > Eset[child+1].weight)) child++; if (x.weight <= Eset[child].weight) break; else Eset[parent] = Eset[child]; } Eset[parent] = x; }
|
最小堆的初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void InitializeESet(LGraph graph, Edge eSet){ Vertex v; PtrToAdjVNode w; int eCount; eCount = 0; for (v = 0; v < graph->nv; ++v) for (w = graph->g[v].firstEdge; w; w = w->next) if (v < w->adjv){ eSet[eCount].v1 = v; eSet[eCount].v2 = w->adjv; eSet[eCount++].weight = w->weight; } for (eCount = graph->ne/2; eCount >= 0; --eCount) PercDown(eSet, eCount, graph->ne); }
|
1 2 3 4 5 6
| int GetEdge(Edge eSet, int currentSize){ Swap(&eSet[0], &eSet[currentSize-1]); PercDown(eSet, 0, currentSize-1); return currentSize-1; }
|
顶点并查集的定义
开始之前:
1 2 3
| typedef Vertex ElementType; typedef Vertex SetName; typedef ElementType SetType[MAXSIZE];
|
初始化并查集:
1 2 3 4 5
| void InitializeVSet(SetType s, int n){ ElementType x; for (x = 0; x < n; ++x) s[x] = -1; }
|
并集运算:
1 2 3 4 5 6 7 8 9 10
| void Union(SetType s, SetName root1, SetName root2){ if (s[root1] < s[root2]){ s[root2] += s[root1]; s[root1] = root2; } else { s[root1] += s[root2]; s[root2] = root1; } }
|
查找:
1 2 3 4 5 6
| SetName Find(SetType s, ElementType x){ if (s[x] < 0) return x; else return s[x] = Find(s, s[x]); }
|
检查找出的边是否在已有的子集中构成回路:
1 2 3 4 5 6 7 8 9 10 11 12
| bool CheckCycle(SetType vSet, Vertex v1, Vertex v2){ Vertex root1, root2; root1 = Find(vSet, v1); root2 = Find(vSet, v2); if (root1 == root2) return false; else { Union(vSet, root1, root2); return true; } }
|
Kruskal算法实现
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| int Kruskal(LGraph graph, LGraph MST){ WeightType totalWeight; int eCount, nextEdge; SetType vSet; Edge eSet; InitializeVSet(vSet, graph->nv); eSet = (Edge)malloc(sizeof(struct ENode) * graph->ne); InsertEdge(graph, eSet); MST = CreateGrape(graph->nv); totalWeight = 0; eCount = 0; nextEdge = graph->ne;
while (eCount < graph->nv-1){ nextEdge = GetEdge(eSet, nextEdge); if (nextEdge < 0) break; if (CheckCycle(vSet, eSet[nextEdge].v1, eSet[nextEdge].v2) == true){ InsertEdge(MST, eSet+nextEdge); totalWeight += eSet[nextEdge].weight; eCount++; } }
if (eCount < graph->nv-1) totalWeight = -1; return totalWeight; }
|
代码说明:
- 初始化顶点并查集以及边的最小堆
- 初始化目标图MST
- 循环寻找最小边(直至收录了graph->nv-1条边或者所有边都被收录),判断此边是否可以收录-
- 如可以,插入图MST,并将相应的变量进行更新
- 不可以,继续寻找最小边