最小生成树
生成树的定义:在一个图中寻到这样一种子图:去除子图中的一些边,剩下的节点和边可以构成一棵树,那么这课树就是生成树。
最小生成树:如果边上有权值,那么是的权值最小的生成树就叫做最小生成树(MST)。
常用的最小生成树算法为Kruskal算法和Prim算法。
最小生成树示例图:
Prim算法
Prim算法和Dijkstra算法十分相似,都是从某个顶点出发不断添加边的算法。
算法思想:
- 我们选取一个顶点v作为起点树T;
- 然后贪心的选取T和其它顶点之间连接的最小权值的边,并把它加入到T中。
- 不断重复操作2,直到所有的顶点被选择完。
那么如何查找到最小权值的边呢?把已选顶点集合X和顶点V连接的最小权值记为mincost[v]。在向X里添加 顶点u时,只需要查看和u相连的边就可以了。对于每条边,更新mincost[v]=min(mincost[v], 边(u,v)的权值 )即可。
如果每次都遍历未包含在X中的点mincost[v],需要时间复杂度 O ( ∣ V ∣ 2 ) O(|V|_2) O(∣V∣2) 。如果使用堆来维护那么时间复杂度就是 O ( ∣ E ∣ l o g ∣ V ∣ ) O(|E|log|V|) O(∣E∣log∣V∣)。
#include<iostream>
#define MAX_V 1000
#define INF 999999999
using namespace std;
int cost[MAX_V][MAX_V]; //存放边的权值
int mincost[MAX_V]; //从集合X出发的边到每个顶点的最小权值
bool used[MAX_V]; //顶点是否已经包含到X中
int V;
void init(){ //按要求输入数据
}
int prim() {
for (int i = 0; i < V; i++) {
mincost[i] = INF;
used[i] = false;
}
mincost[0] = 0; //选取零号节点作为起始点
int res = 0; //用来记录总权值
while (true) {
int v = -1;
for (int u = 0; u < V; u++) { //选取权值最小的点,初始时会选中0号点
if (!used[u] && (v == -1 || mincost[u] < mincost[v])) v = u;
}
if (v == -1) break; //如果v还是-1说明所有的点都被用过了
used[v] = true; //标记v被使用
res += mincost[v]; //记录总权值,很显然第一次循环时v=0,mincost[0]=0;
for (int u = 0; u < V; u++) {
//更新所有点到X(已选点的集合),因为新加入了节点v,所以将原来的距离和cost[v][u]比较。
// 注意这里, Prim和Dijkstra算法的区别就再这一句上。
mincost[u] = min(mincost[u], cost[v][u]);
}
}
return res;
}
int main() {
return 0;
}
Kruskal算法
算法思路:
- 按照边的权值顺序从小到大选择边,如果选中的边加入到已选边中不会产生圈,那么就把该边加入到已选边中。
- 重复步骤1,直到所有的顶点被选则。
该算法的重点在于如何检查是否存在圈。每次选中边检查该边的两个顶点是否在已选顶点中,如果连个都在,那么一定会产生圈。
该算法主要是在边排序上花费时间,算法的时间复杂度是 O ( ∣ E ∣ l o g ∣ V ∣ ) O(|E|log|V|) O(∣E∣log∣V∣) 。
算法实现如下:
bigncaji.h头文件
//bingcaji.h
// Created by wyc on 18-7-19.
//
#ifndef UNTITLED2_BINGCAJI_H
#define UNTITLED2_BINGCAJI_H
#include <iostream>
#define MAXN 1000
using namespace std;
int par[MAXN], ranks[MAXN]; //par表示parent代表父亲编号,par[x]指的是x的父节点编号
//初始化n个元素
void initUnionFind(int n) {
for (int i = 0; i < n; i++) {
par[i] = i;
ranks[i] = 0;
}
}
//查询树的根
int find(int x) {
if (par[x] == x) {
return x;
} else {
return par[x] = find(par[x]);
}
}
//合并x和y所属的集合
void unite(int x, int y) {
x = find(x);
y = find(y);
if (x == y) return;
if (ranks[x] < ranks[y]) {
par[x] = y;
} else {
par[y] = x;
if (ranks[x] == ranks[y]) ranks[x]++;
}
}
//x和y是否属于同一集合
bool same(int x,int y){
return find(x) ==find(y);
}
#endif //UNTITLED2_BINGCAJI_H
main.cpp文件
#include<iostream>
#include <algorithm>
#include "bingcaji.h"
#define MAX_V 1000
#define INF 999999999
using namespace std;
struct edge{int u,v,cost;};
edge es[MAX_V];
int V,E;
bool comp(const edge & e1, const edge & e2){
return e1.cost <e2.cost;
}
void init(){
cin>>V>>E;
for(int i=0;i<E;i++){
cin>>es[i].u>>es[i].v>>es[i].cost;
}
}
int kruskal(){
sort(es,es+E,comp); //按照从小到达排序
initUnionFind(V);
int res=0;
for(int i=0;i<E;i++){
edge e=es[i];
if(! same(e.u,e.v)){
unite(e.u,e.v);
res+=e.cost;
}
}
return res;
}
int main() {
init();
cout<<kruskal();
return 0;
}
/* 测试数据 ,该数据即为文章开始的图,输入为V E 边(u,v,cost)
8 11
0 2 2
1 2 3
2 3 4
2 5 1
3 4 3
5 6 5
3 6 7
3 7 9
6 7 8
6 4 1
4 7 5
*/