什么是最小生成树
最小生成树是一个图的极小连通子图,它包含原图的所有顶点,并且所有边的权值之和尽可能小。
Prim算法就是图的最小生成树算法之一,Prim 算法是一种求解加权无向连通图的 MST 问题的贪心算法。它能找出一个边的子集,使得其构成的树包含图中所有顶点,且边的权值之和最小。
Prim算法以图的顶点为基础,从首个初始顶点,寻找到达其他顶点权值最小的边,并把该顶点加入到“已到达顶点”的集合中,此时,这个集合就是这个图的最小生成树。
一般用一维数组比较方便表达最小生成树,数组下标所对应的元素,代表该顶点在最小生成树当中的父亲节点。
假设有这样一个带权图:
它所有权值最小的边形成的集合如下:
去掉多余的边,就是该图实例的最小生成树:
Prim算法详细步骤
以下算法详细步骤图,均来自公众号”程序员小灰”的文章——《漫画:什么是最小生成树?》
以图的第一个顶点作为初始顶点,加入到“已到达顶点”的集合中。
第一个顶点总是MST(最小生成树)的根节点,但是因为根节点没有父亲节点,所以根节点的元素值是-1。
此时从“已到达顶点”的集合出发,发现0到2的权值最小,把顶点2加入到“已到达顶点”的集合中,因为它的父亲节点是根节点,所以它的元素值是0。
继续从“已到达顶点”的集合出发,发现2到4的权值最小,把顶点4加入到“已到达顶点”的集合中,因为它的父亲节点是2,所以它的元素值是2。
继续从“已到达顶点”的集合出发,发现0到1的权值最小,把顶点1加入到“已到达顶点”的集合中,因为它的父亲节点是0,所以它的元素值是0。
继续从“已到达顶点”的集合出发,发现1到3的权值最小,把顶点3加入到“已到达顶点”的集合中,因为它的父亲节点是1,所以它的元素值是1。
此时,“已到达顶点”的集合,就是这个带权图的最小生成树。
Prim算法代码实现
如果仔细观察,可以发现Prim算法和Dijkstra算法特别相似,只有少部分代码有不同。
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
| const INF = Number.MAX_SAFE_INTEGER;
const minKey = (graph, key, visited) => { let min = INF; let minIndex = 0; for (let v = 0; v < graph.length; v++) { if (visited[v] === false && key[v] < min) { min = key[v]; minIndex = v; } } return minIndex; };
export const prim = graph => { const parent = []; const key = []; const visited = []; const { length } = graph; for (let i = 0; i < length; i++) { key[i] = INF; visited[i] = false; } key[0] = 0; parent[0] = -1; for (let i = 0; i < length - 1; i++) { const u = minKey(graph, key, visited); visited[u] = true; for (let v = 0; v < length; v++) { if (graph[u][v] && !visited[v] && graph[u][v] < key[v]) { parent[v] = u; key[v] = graph[u][v]; } } } return parent; };
|
代码实例
如果对以下的图执行Prim算法:
1 2 3 4 5 6 7 8
| let graph = [ [0, 2, 4, 0, 0, 0], [2, 0, 2, 4, 2, 0], [4, 2, 0, 0, 3, 0], [0, 4, 0, 0, 3, 2], [0, 2, 3, 3, 0, 2], [0, 0, 0, 2, 2, 0] ];
|
可以得到如下输出结果