最小生成树
MSTMSTMST是图GGG中最小代价连通子图。
MSTMSTMST具有以下的性质:
- 最小生成树不唯一,但是它们的边权和相同
- 最小生成树的边数等于顶点数减一(树的性质)
构造最小生成树的算法:
有两种经典的算法primprimprim算法和kruskalkruskalkruskal算法。它们都基于贪心策略实现。primprimprim是对点进行贪心,所以适用于稠密图。而kruskalkruskalkruskal是对边进行贪心,适用于稀疏图。以下是两种算法的代码实现:
- primprimprim
primprimprim与课本上dijkstradijkstradijkstra算法的实现几乎一样。唯一的区别在于更新disdisdis数组的方式。primprimprim是更新加入MST的最小代价,而dijkstradijkstradijkstra是更新路径长度。
int prim(){
int res=0,cnt=0; //记录路径长度和边的数量
bool vis[maxn];
int dis[maxn];
dis[1] = 0;
for(int i=0;i<n;i++){
int mx = inf, index = -1;
for(int j=1;j<=n;j++){ //标记下一个加入MST的点
if(!vis[j]&&mx>dis[j])
mx = dis[j] , index = j;
}
if(index==-1) break; //如果不存在,跳出循环
for(int j=1;j<=n;j++){ //用标记过的点更新其他没有加入MST的点
if(!vis[j]){ //邻接矩阵存图
dis[j] = min(g[index][j],dis[j]); //更新加入MST的最小代价
}
}
vis[index] = true;
res += dis[index];
cnt++;
}
if(cnt!=n-1){ //如果边数不是n-1,则不能构成MST
cout<<"不能构成MST"<<endl;
return -1;
}
else
return res;
}
时间复杂度分析:一共要贪心∣V∣−1|V|-1∣V∣−1个点所以需要∣V∣−1|V|-1∣V∣−1次循环。在循环内部,需要对disdisdis数组进行遍历O(∣V∣)O(|V|)O(∣V∣),标记最小没有加入集合的点。所以时间复杂度为O(∣V∣2)O(|V|^2)O(∣V∣2)。
- kruskalkruskalkruskal
struct Edge {
int u,v,w;
};
Edge e[200005]; //存边
int fa[5005],n,m,ans,eu,ev,cnt;
bool cmp(const Edge& a, const Edge& b){
return a.w<b.w;
}
int find(int x){ //并查集
while(x!=fa[x]) x=fa[x]=fa[fa[x]];
return x;
}
void kruskal(){
sort(e,e+m,cmp); //对边排序,便于后面贪心
for(int i=0;i<m;i++) {
eu = find(e[i].u),ev = find(e[i].v);
if(eu==ev) continue;
//如果都在一个集合里面不需要合并(不一定是在MST中,有可能存在多个集合还没有合并)
ans+=e[i].w;
fa[eu]=ev; //合并集合
if(++cnt==n-1) break;
}
}
时间复杂度分析:首先要对∣E∣|E|∣E∣条边排序O(∣E∣log∣E∣)O(|E|\log{|E|})O(∣E∣log∣E∣)。然后需要贪心选择∣E∣−1|E|-1∣E∣−1条边,其中带路径在压缩的并查集时间复杂度近似为O(1)O(1)O(1)。因为两种操作时并行的。所以选择较大的。所以时间复杂度为O(∣E∣log(∣E∣))O(|E|\log(|E|))O(∣E∣log(∣E∣))
最小瓶颈路
def
给定一个加权无向图两个节点uuu和vvv,求uuu到vvv的一条路径,使得路径上边的最大权值最小。
首先,任意两个节点的最小瓶颈路一定在最小生成树上。所以我们可以先求出最小生成树。然后从uuu点DFS到vvv点,这样实现的时间复杂度是O(logn)O(\log{n})O(logn)。显然会TLE。
所以我们考虑在查询之前进行预处理,将所有节点对的最长边保存在一个maxcostmaxcostmaxcost数组中,然后查询只要KaTeX parse error: Expected 'EOF', got '}' at position 4: O(1}̲)。递推公式如下:
maxcost[j][u]=max(maxcost[j][fa[u]],maxcost[j][u])maxcost[j][u] = max(maxcost[j][fa[u]],maxcost[j][u])maxcost[j][u]=max(maxcost[j][fa[u]],maxcost[j][u])
更新jjj到uuu的最小瓶颈路是jjj到fa[u]fa[u]fa[u]或jjj到uuu最小瓶颈路的最大值。
实现方法
基于prime算法
在求解最小生成树的过程中将有根树建立起来,同时求出所有节点的maxcostmaxcostmaxcost
int prime(){
int res = 0;
memset(maxcost,0,sizeof(maxcost));
for(int i=1;i<=n;i++){
vis[i]=0 , d[i] = INF , pre[i] = i;
}
d[s] = 0; //预先选中s
for(int i=0;i<n;i++){
int mx = INF , index = -1;
for(int j=1;j<=n;j++){
if(!vis[j]&&d[j]<mx)
mx = d[index=j]; //选中d最小的点
}
if(index==-1) break;
for(int j=1;j<=n;j++) //j->index = j->fa->index
if(vis[j])
maxcost[index][j] = maxcost[j][index] =
max(maxcost[pre[index]][j],mx);
res += mx;
vis[index] = 1;
for(int j=1;j<=n;j++){ //用选中的index更新其他节点
if(!vis[j]&&g[index][j]<d[j]){
d[j] = g[index][j];
pre[j] = index;
}
}
}
return res;
}