次小生成树(Prim + Kruaskal)
问题引入:
我们先来回想一下生成树是如何定义的,生成树就是用n - 1条边将图中的所有n个顶点都连通为一个连通分量,这样的边连成子树称为生成树。
最小生成树很明显就是生成树中权值最小的生成树,那么我们即将要学的次小生成树或者K小生成树是怎么定义的呢,很明显就是生成树中权值第k小的生成树。
下面给出刘老师书中对次小生成树的定义,我是用自己的话描述的。
对于一个无向图G(V, E),其定义了边权为W(u, v),若T为他的一颗最小生成树,那么我们假设存在一颗生成树T1,不存在任意一颗G的生成树T2满足W(T) <= W(T2) < W(T1),那么我们就称T1为
G的次小生成树。
非严格次小生成树的求解:
我们知道最小生成树我们是通过Prim和Kruskal这样的贪心算法求得的,那么次小生成树我们只是对这两种算法进行我们需要的修改就可以进行次小生成树的求解。
我们很容易可以想到最小生成树和次小生成树应该是有联系的,那么是如何联系的呢?次小生成树就是图G的所有生成树中权值第二小的生成树,也就是说我们只需要替换最小生成树的一条边(u, v)
就可以得到次小生成树,显然这条边肯定不可以属于原最小生成树,如果我们将一条不属于原最小生成树的边(u, v)加入T,那么此时T中就形成了一个环,我们在环中选一个除(u, v)权值最大的边进行删除
(想一下为什么是选择权值最大的那条边),得到的树依然是一颗图G的生成树,我们将所有边逐个加入原最小生成树T,得出并记录所有的生成树的权值T1,那么最后T1中最小的那个值即是次小生成树的
权值。
下面只对与求解最小生成树中不同的部分进行说明:
Prim:
我们知道Prim算法是以给定的任意点作为起始点运用一定的方法对所有点进行贪心处理,缩点从而生成一颗最小生成树,那我们只需要用数组用来描述最小生成树中每条边的访问情况以及最小
生成树中每两个顶点之间的最大边权还需要保存最小生成树中每个顶点的父亲顶点,从而就可以方便用于计算次小生成树。
具体操作:
初始化:初始化所有点( i )距离最小生成树子树的距离为cost[source][ i ],所有边初始化为未访问,所有顶点之间的最大边权初始化为0。
加边:每次加入一条安全边(这里不对安全边进行解释,有不了解的可以查阅博主的上一篇博客),并将最小生成子树中顶点之间的最大边权进行更新,接着更新lowc即可。
求解次小生成树:我们逐一枚举出所有不属于最小生成树的边(u, v),并且用w(u, v)来替代最大边权和Max(u, v),怎么个替代法?
SecondMST = min(MST + w(u, v) - Max(u, v)) ((u, v) not belong to MST)。
OK?
参考代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std; const int maxn = + , INF = 0x3f3f3f3f;
int n, m, lowc[maxn], pre[maxn], Max[maxn][maxn], cost[maxn][maxn];
bool vis[maxn], used[maxn][maxn]; int Prim() {
int ans = ;
memset(vis, false, sizeof vis);
memset(Max, , sizeof Max);
memset(used, false, sizeof used);
vis[] = true;
pre[] = -;
for(int i = ; i <= n; i ++) {
lowc[i] = cost[][i];
pre[i] = ;
}
lowc[] = ;
for(int i = ; i <= n; i ++) {
int MIN = INF, p = -;
for(int j = ; j <= n; j ++) {
if(!vis[j] && MIN > lowc[j]) {
MIN = lowc[j];
p = j;
}
}
if(MIN == INF) return -;
ans += MIN;
vis[p] = true;
used[p][pre[p]] = used[pre[p]][p] = true;
for(int j = ; j <= n; j ++) {
if(vis[j] && j != p) Max[j][p] = Max[p][j] = max(Max[j][pre[p]], lowc[p]);
if(!vis[j] && lowc[j] > cost[p][j]) {
lowc[j] = cost[p][j];
pre[j] = p;
}
}
}
return ans;
} int Second_Prim(int MST) {
int ans = INF;
for(int i = ; i <= n; i ++)
for(int j = i + ; j <= n; j ++)
if(!used[i][j] && cost[i][j] != INF) ans = min(ans, MST - Max[i][j] + cost[i][j]);
return ans;
} int main() {
int t, a, b, c;
scanf("%d", &t);
while(t --) {
scanf("%d %d", &n, &m);
for(int i = ; i <= n; i ++)
for(int j = ; j <= n; j ++)
cost[i][j] = INF;
for(int i = ; i <= m; i ++) {
scanf("%d %d %d", &a, &b, &c);
cost[a][b] = cost[b][a] = c;
}
int MST = Prim();
int Second_MST = Second_Prim(MST);
printf("%d\n", Second_MST);
}
return ;
}
Kruskal:
Kruskal算法是将图G的所有边进行排序,从小到大满足边的两个顶点有一个不在subMST中就将其加入MST,在求解次小生成树问题时我们也需要记录MST中结点的连接情况,以及MST中两个顶点
间的最大边权。
具体操作:
初始化:初始化并查集,初始化在subMST中每个结点 i 直接或者间接相连的边为i。
加边:每次加入一条边时,我们更新subMST中所有与u, v相连的结点间的最大边权,接着将所有与结点v相连的边都与结点u也连起来就行了(前提是在合并时head[ head[ v ] ] = head[ u ])。
求解次小生成树:
SecondMST = min(MST + w(u, v) - Max(u, v)) ((u, v) not belong to MST)。滑稽.jpg
参考代码:
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std; const int maxn = + , maxe = * / + , INF = 0x3f3f3f3f;
int n, m, pre[maxn], head[maxn], Max[maxn][maxn];
struct Edge {
int u, v, w;
bool vis;
}edge[maxe];
vector<int> G[maxn]; bool cmp(const Edge &a, const Edge &b) {
return a.w < b.w;
} void Init() {
for(int i = ; i <= n; i ++) {
G[i].clear();
G[i].push_back(i);
head[i] = i;
}
} int Find(int x) {
if(head[x] == x) return x;
return head[x] = Find(head[x]);
} int Kruskal() {
sort(edge + , edge + + m, cmp);
Init();
int ans = , cnt = ;
for(int i = ; i <= m; i ++) {
if(cnt == n - ) break;
int fx = Find(edge[i].u), fy = Find(edge[i].v);
if(fx != fy) {
cnt ++;
edge[i].vis = true;
ans += edge[i].w;
int len_fx = G[fx].size(), len_fy = G[fy].size();
for(int j = ; j < len_fx; j ++)
for(int k = ; k < len_fy; k ++)
Max[G[fx][j]][G[fy][k]] = Max[G[fy][k]][G[fx][j]] = edge[i].w;
head[fx] = fy;
for(int j = ; j < len_fx; j ++)
G[fy].push_back(G[fx][j]);
}
}
return ans;
} int Second_Kruskal(int MST) {
int ans = INF;
for(int i = ; i <= m; i ++)
if(!edge[i].vis)
ans = min(ans, MST + edge[i].w - Max[edge[i].u][edge[i].v]);
return ans;
} int main() {
int t;
scanf("%d", &t);
while(t --) {
scanf("%d %d", &n, &m);
for(int i = ; i <= m; i ++) {
scanf("%d %d %d", &edge[i].u, &edge[i].v, &edge[i].w);
edge[i].vis = false;
}
int MST = Kruskal();
int Second_MST = Second_Kruskal(MST);
printf("%d\n", Second_MST );
}
return ;
}
次小生成树(Prim + Kruaskal)的更多相关文章
- UVA 10462 Is There A Second Way Left?(次小生成树&Prim&Kruskal)题解
思路: Prim: 这道题目中有重边 Prim可以先加一个sec数组来保存重边的次小边,这样不会影响到最小生成树,在算次小生成树时要同时判断次小边(不需判断是否在MST中) Kruskal: Krus ...
- The Unique MST POJ - 1679 次小生成树prim
求次小生成树思路: 先把最小生成树求出来 用一个Max[i][j] 数组把 i点到j 点的道路中 权值最大的那个记录下来 used数组记录该条边有没有被最小生成树使用过 把没有使用过的一条边加 ...
- hdu 4081 Qin Shi Huang's National Road System(次小生成树prim)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4081 题意:有n个城市,秦始皇要修用n-1条路把它们连起来,要求从任一点出发,都可以到达其它的任意点. ...
- HDOJ-4081(次小生成树+Prim算法)
Qin Shi Huang's National Road System HDOJ-4081 本题考查的是次小生成树的问题,这里的解决方法就是先使用Prim算法求解最小生成树. 在求解最小生成树的时候 ...
- poj1789--最小生成树(prim)
水题... 题目大意: 用一个7位的字符串代表一个编号,两个编号之间的distance代表这两个编号之间不同字母的个数.一个编号只能由另一个编号“衍生”出来,代价是这两个编号之间相应的distance ...
- ZOJ-1586 QS Network---最小生成树Prim
题目链接: https://vjudge.net/problem/ZOJ-1586 题目大意: 首先给一个t,代表t个测试样例,再给一个n,表示有n个QS装置,接下来一行是n个QS装置的成本.接下来是 ...
- POJ-1789 Truck History---最小生成树Prim算法
题目链接: https://vjudge.net/problem/POJ-1789 题目大意: 用一个7位的string代表一个编号,两个编号之间的distance代表这两个编号之间不同字母的个数.一 ...
- 10-最小生成树-Prim算法
#include <iostream> #include <cstring> #include <cstdio> using namespace std; #def ...
- 【次小生成树】【Kruskal】【prim】【转】
原博客出处:https://blog.csdn.net/yasola/article/details/74276255 通常次小生成树是使用Prim算法进行实现的,因为可以在Prim算法松弛的同时求得 ...
随机推荐
- win32 socket 编程(三)——TCP/IP
一.TCP/IP解析 TCP/IP协议的核心部分是传输层协议(TCP.UDP),网络层协议(IP)和物理接口层,这三层通常是在操作系统内核中实现.因此用户一般不涉及.编程时,编程界面有两种形式: 1. ...
- 20191125PHP抽象类、接口和魔术方法
抽象类 不能被实例化,用于其他类的继承.使用abstract(抽象).抽象方法一定是抽象类,抽象类不一定有抽象方法. 接口interface是特殊的抽象类. eg: <?php //抽象类 ab ...
- express通过生成器
express通过生成器 [ 脚手架 ] 1. 作用:可以帮助快速构建一个express项目 2. 脚手架的安装 全局安装 [可以使用npm cnpm] $ cnpm i express-genera ...
- Sql Server 压缩数据库占用空间
1.删除数据库库中不必要的数据2. 在数据库上右击,任务,收缩,文件,在收缩操作上选择在未使用的空间前重新组织页,将文件收缩到的最后一行为最小为XXM,在前面的输入框中填入该值,然后点击确定3.分离该 ...
- js中跳出forEach循环
缘由:近期在项目中使用lodash.js中的_.foreach方法处理数据,需要在满足条件时结束循环并不执行后面的js代码. 因为foreach中默认没有break方法.在尝试中使用了return f ...
- 日志处理--高效Linux命令整理
序 在学习使用python处理日志开始阶段,对我阻力最大的莫过于对linux的不熟悉了,有种寸步难行的感觉. 在之后乱学一通之后,发现有点对我颇有益处: 学<鸟哥linux私房菜基础学习篇> ...
- git 操作遇到的问题与解决方法
一.使用git在本地创建一个项目的过程,Git 上传本地文件到github $ makdir ~/hello-world //创建一个项目hello-world $ cd ~/hello-world ...
- 2018-10-01-weekly
Algorithm 77. 组合 What 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合. How 利用递归的思想,当凑够k个数时,就回退回去,remove掉一个数,在 ...
- Flutter-網絡請求
Flutter 请求网络的三种方式 flutter 请求网络的方式有三种,分别是 Dart 原生的网络请求 HttpClient.第三方网络请求 http以及 Flutter 中的 Dio.我们可以比 ...
- vue子传父、父传子
子传父 vue子传父使用$emit传值 子组件: <template> <div> <button @click="toParent">点击传到 ...