Lazy Prim介绍
0.前言
关于MST大家都知道Prim,但是Prim也有多种类别。这里介绍一下Lazy Prim。
因为前段时间有个项目需求就是这个。
1.切分定理
1.定义:在一给定的无向连通图G = (V, E) 中,找到一颗生成树,使得这V-1条边的权值之和最小,这样的生成树就是最小生成树。 而所谓的生成树,简单的说就是在所有的边之间找到V-1条边,使得所有顶点连通且没有形成环。 最小生成树其实是最小权重生成树的简称。 那么如何找到这样的最小生成树呢?这里,就可以用prim算法。这里,我们先介绍lazy prim算法。 在介绍lazy prim算法之前,我们先来介绍一个重要的定理——切分定理。它是prim算法的核心。 我们以一个实例作为讲解。
这样的一个图表示的各个边的权值如下:
好了,到底什么是切分定理呢?这里我们先介绍几个定义:
切分:把图中的节点分为两部分,称为一个切分(Cut)
横切边:如果一个边的两个端点,属于切分(Cut)不同的两边,这个边称为横切边(Crossing Edge);
切分定理:给定任意切分,横切边中权值最小的边必然属于最小生成树。
如果文字描述不是太理解,小伙伴们可以看下面这张图。
我们看到这个图有两个部分,蓝色部分和红色部分。而所有绿色的边,就是所谓的横切边,因为绿色的边的两个端点属于属于不同的部分。 那么根据切分定理,我们知道0~7的这条边就是最小生成树的一条边,因为其权值最小。 那么问题来了,为什么这条边属于最小生成树? 这里我们给出证明:
我们看上面这个图,我们可以把这个图分为两个部分。由于图的连通性,那么在这两部分中必然有横切边,并且我们假定绿色的边权值最小。因为要保持连通性,在这三条边中必然要选择一条边使得这两部分连通。如果我们假定左右两部分都已经是最小生成树了,那么从三条边中选择,为了保持总权值最小,必然是选择绿色的边。这就是切分定理。 如果按照这个定理,聪明的小伙伴们可能对这个lazy prim算法有了眉目。既然给定任意切分,横切边中权值最小的边必然属于最小生成树,那么我们完全可以从第0个顶点开始,第0个顶点与其他部分切分,找到最小权值边,然后向下一个顶点扩散。再把这个顶点与第0个顶点当作同一部分与剩下的顶点切分,再找到下一个权值最小边,依此类推。当然,这里我们需要注意不能让这个树变成了环。也就是同一部分的顶点之间的边不能选择。 下面我们用几个图解释一下。 将 0 作为起始点,开始切分,逐步将蓝色阵营的顶点 转换到红色阵营中 。
具体的模拟过程,可以参考这篇博客。
https://blog.csdn.net/IMDASHUAI/article/details/105199649
2.思路
标记一个起点。
- 每次把横切边都丢到优先队列
- 取最小的加入生成树,然后标记这两个点。
- 直到没有横切边。
这种算法时对应我们下面那个代码的,不过这个写法不是最优的。
代码见第一个java。
更体现lazy的一种:
- 选取起点,加入与他相连的边到优先队列,标记起点。
- 每次取出队首,如果不是横切边,pop掉之后不管。
- 否则访问未标记点,然后加边到队列,标记点。更新MST的weight
代码见第二个cpp。
3.代码
代码用到了jar包,见注释。
需要每次遍历全图,时间不优。
In in = new In(args[0]);
EdgeWeightedGraph G = new EdgeWeightedGraph(in);
boolean[] marked = new boolean[G.V()];
Queue<Edge> mst = new Queue<Edge>();
double weight = 0.0;
marked[0] = true;
while (true) {
MinPQ<Edge> minpq = new MinPQ<Edge>();
for (Edge e : G.edges()) {
int u = e.either();
int v = e.other(u);
if (marked[u] != marked[v])
minpq.insert(e);
}
if (minpq.isEmpty()) {
break;
}
Edge cross_edge = minpq.delMin();
mst.enqueue(cross_edge);
int u = cross_edge.either();
int v = cross_edge.other(u);
weight += cross_edge.weight();
if (!marked[v]) marked[v] = true;
if (!marked[u]) marked[u] = true;
}
for (Edge e : mst) {
StdOut.println(e);
}
StdOut.printf("%.5f\n", weight);
更快点的做法。
#include<iostream>
#include<queue>
#define INF 1e4
using namespace std;
double G[8][8] =
{
{INF,INF,0.26,INF,0.38,INF,0.58,0.16},
{INF,INF,0.36,0.29,INF,0.32,INF,0.19},
{0.26,0.36,INF,0.17,INF,INF,0.40,0.34},
{INF,0.29,0.17,INF,INF,INF,0.52,INF},
{0.38,INF,INF,INF,INF,0.35,0.93,0.37},
{INF,0.32,INF,INF,0.35,INF,INF,0.28},
{0.58,INF,0.40,0.52,0.93,INF,INF,INF},
{0.16,0.19,0.34,INF,0.37,0.28,INF,INF}
};
struct Edge {
int v;
int u;
double w;
bool operator < (const Edge& a) const {
return w > a.w;
}
};
bool marked[8];
priority_queue<Edge,vector<Edge>,less<Edge> > w;
void vis(int x) {
marked[x] = 1;
for (int i = 0; i < 8; i++) {
if (G[x][i] == INF)
continue;
w.push({ x,i,G[x][i] });
}
}
int main()
{
vis(0);
double minw = 0;
while (!w.empty()) {
Edge e = w.top();
w.pop();
if (marked[e.u] == marked[e.v])
continue;
vis(e.u);
minw += e.w;
}
cout << minw;
return 0;
}
|