Prim算法
多么痛的领悟!
不要认为Prim不常见就用不到!
和Kruskal一样,Prim算法也是用来求MST的,也是体现了贪心的思想。
不同的是,Kruskal是针对边而言的,Prim是针对点而言的。Kruskal适用于稀疏图,Prim适用于稠密图,更值得一提的是,Prim可以不必保存每条边。
算法思想是,设定两个集合Vnew和Enew,先选中一个起点加入到Vnew中,再从V-Vnew(在V中且不在Vnew中)中选择一个点,使得该点到Vnew中某个点的边权最小,将这条边加入Enew中,将这个点加入Vnew中,不断重复该过程,直到Vnew=V。
因为每次是通过某个点到已建好的树的最小边权将该点加入到生成树中的,所以最终构造出来的一定是最小生成树。
1 vis[1] = 1; //开始先将起点加入到Vnew 2 for (int i = 2; i <= n; ++i) minw[i] = G[i][1]; 3 int index, mmin; 4 for (int i = 1; i < n; ++i) { 5 mmin = 1e5 + 5; 6 for (int j = 1; j <= n; ++j) //取出到Vnew中某个点边权最小的点 7 if (!vis[j] && minw[j] < mmin) index = j, mmin = minw[j]; 8 vis[index] = 1; 9 ans += mmin; 10 for (int j = 1; j <= n; ++j) //用每次新加入的点更新其他点到Vnew中点的最小边权 11 if(!vis[j] && G[j][index] < minw[j]) minw[j] = G[j][index]; 12 }
对于Prim的初始化,还有一种写法。开始时并没有标记起点,而是将起点到Vnew中某个点的最小边权置为0,这样就需要循环n次(相当于需要将n个点放入Vnew中)。
另外,Prim还可以使用堆进行优化。自己尝试写了一个,因为是对于完全图,存图是用邻接矩阵,所以效率并没有明显提高(甚至下降)。另外,不得不说,Prim和Dijkstra太像了!
1 #include <cstring> 2 #include <queue> 3 4 using namespace std; 5 6 int dist[maxn], vis[maxn], ans = 0; 7 8 struct node { 9 int id, dist; 10 node(int i, int d) : id(i), dist(d) {} 11 bool operator < (const node& rhs) const { 12 return dist > rhs.dist; 13 } 14 }; 15 16 priority_queue<node> q; 17 18 memset(dist, 0x3f3f3f3f, sizeof(dist)); 19 dist[1] = 0; 20 q.push(node(1, 0)); 21 while (!q.empty()) { 22 int u = q.top().id; 23 q.pop(); 24 if (vis[u]) continue; 25 vis[u] = 1; 26 ans += dist[u]; 27 for (int v = 1; v <= n; ++v) { //这里是针对完全图,用的邻接矩阵 28 if (v == u) continue; //改成邻接链表也可以 29 if (dist[v] > dis(u, v)) { 30 dist[v] = dis(u, v); 31 q.push(node(v, dist[v])); 32 } 33 } 34 }