最小生成树算法有二,其一prim算法,其二是克鲁斯卡尔算法,两者的时间复杂度分别为o(n^2)、o(mlogn)【n为点数,m为边数】,两个算法在不同的图中表现不同。
先来讲prim算法。
这个算法的核心思想就是把点分为两个集合A、B,A为已选点集合,B为未选集合。
经历n次循环,每次在未选集合中找一个里已选集合所构成的连通图最近的点放入已选集合中,并用这个点更新其他点;
思路和迪杰斯特拉算法很像,下面是代码和注释。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 510;
int g[N][N];//用邻接矩阵存图
int dist[N];//dist[i]表示i点到已选集合点构成的连通块的最短距离
int st[N];//st[i]=1表示i点为集合A,=0为集合B
int n, m;
int prim() {
//初始化每个点到A集合的距离为无穷,出发点为点1,故dist[1]=0;
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
int res = 0;//res为集合A的权重
for (int i = 0; i < n; i++) {
int t = -1;
for (int j = 1; j <= n; j++) {//在集合B中搜索可选点
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
}
st[t] = 1;//标记该点进入集合A
for (int j = 1; j <= n; j++) {//用该点更新其他点
dist[j] = min(dist[j], g[t][j]);
}
res += dist[t];//更新权重
}
if (res >= 0x3f3f3f) return 0;
else return res;
}
int main() {
cin >> n >> m;
memset(g, 0x3f, sizeof g);
for (int i = 0; i < m; i++) {//接收数据
int a, b, c; cin >> a >> b >> c;
if (a != b)
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = prim();
if (!t) cout << "impossible";
else cout << t;
}
然后是克鲁斯卡尔算法,这个算法用到了并查集的知识。
具体思路如下
先将所有边按权重大小排序,1~n依次递增,然后1~n遍历所有边,如果一条边的两个点不属于同一个集合,那么就将两个点所在的集合合并,并计算权重。
因为排序过,所以所得的结果是最小生成树
具体代码和注释如下;
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 200020;
struct edge {//用一个结构体存储所有边
int a, b, w;
bool operator< (const edge W)const {//重载<
return w < W.w;
}
}edges[N];
int n, m;
int p[N];
int find(int x) {//并查集核心代码,不晓得并查集的可以去翻我原先写过的并查集的介绍
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
cin >> n >> m;
for (int i = 0; i < m; i++) {//接收所有边
int a, b, c; scanf("%d%d%d", &a, &b, &c);
edges[i].a = a, edges[i].b = b, edges[i].w = c;
}
sort(edges, edges + m);
for (int i = 1; i <= n; i++) p[i] = i;
int res = 0, cont = 0;//res为所有集合的权重和,cont为所有集合【不包括只含一个点的集合】的点数
for (int i = 0; i < m; i++) {
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
int ka = find(a), kb = find(b);//寻找这两个点的集合
if (ka != kb) {
res += w;
p[ka] = kb;
cont++;
}
}
if (cont < n - 1) cout << "impossible";
else cout << res;
}
然后是二分图的两个算法,一个是染色法,一个是匈牙利算法,前者是判断一个图是否为二分图,另一个算法的作用难以表述,我将用一道题解释。
先说染色法,其核心是dfs算法,思想如下;
在一个联通块中选一个点为起点,然后将其染色成黑色,并找到这个点的子节点,将其全部染色为白色,然后进入子点,寻找子点的所有子点,将其染为黑色,如此重复黑白两色的操作,如果不存在冲突的话则图为二分图,当且仅当图中存在一个边数为基数的环时存在冲突,即当找到一个子点时子点的颜色和父点的颜色相同;
具体代码如下;
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 100010;
vector<vector<int>> ve(N);//用vector存储图
int n, m;
int col[N];//col[i]=1时i点为黑色,col[i]=2时i点为白色,col[i]=0时为无色;
bool dfs(int x, int c) {
col[x] = c;
for (int i = 0; i < ve[x].size(); i++) {//将子点全部染成与父点不同的颜色
if (!col[ve[x][i]]) {
if (!dfs(ve[x][i], 3 - c))
return 0;
}
else if (col[ve[x][i]] == c) return 0;//如果子点有颜色,则判断是否与夫节点同色
}
return 1;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {//接收图
int a, b; scanf("%d%d", &a, &b);
ve[a].push_back(b);
ve[b].push_back(a);
}
int flag = 1;
for (int i = 1; i <= n; i++) {//进行染色
if (!col[i]) {
if (!dfs(i, 1)) {//dfs=0时表明存在冲突,该图不为二分图
flag = 0;
break;
}
}
}
if (!flag) cout << "No";
else cout << "Yes";
}