最小生成树的实质
最小生成树是用求将所有的节点都连在一起,所需要的的最小价值
Kruskal算法 复杂度(??(?? log??? )) //K算法就是用并查集,不断地将当前最小的路判断是否需要建立关系
N个点则要连N-1条边
1.把给出的m条边按其花费大小从小到大排序
2.给每个点设定一个标记
3.按从小到大的顺序选择边,如果这条边的两个点都没被标记或者有一个点没被标记的话,标记这没被标记的点,并加入连接这条路的花费;如果都被标记的话,则说明以有更小代价的路被加入。
4.重复操作直到所有点被标记。
//简单来说,就是找当前最小的路,判断是否需要建立,若需要则进行建立,求和
这里是以边作为对象进行存储的,所以我们不需要二维数组,直接使用结构体进行循环即可
永远在加入当其所有边里面最小的边,并且做标记然后再次寻找当前没有加入的最小对边(没有加入是指一条边的一个点或者两个点没有被标记???但是我们应用了并查集的思想,可以看一下这个有没有成功连接到一起),每一次都是寻找的最小的边,那么最后的边权值之和自然就是最小的
Prime算法
1、从任意一点开始,加入集合S,然后找到与集合S相连的最短的边,
2、更新所有点,将其dis变为到集合S最短的距离
//注意,找最短的边,好找 但是我们怎么更新所有点,找到与集合S最短的距离呢,难不成把这个点与集合S中所有的点
//都进行比较吗?不,我们每次更新都把每一个点到更新的点的距离和之前那个点的距离作比较,更新为较短的,那么由此类推,我们每一次都是讲这个点更新为短的一个,那么自然就是到这个集合中最短的一个
//对比dij算法,dij是每一次压入集合S和都只需要比较每个点直接到起点和通过新点到起点的距离
进行建立关系,并标记那个点//把该点的所有连接线加入??????
3、重复操作1和2,直至所有的点都加入了集合S
我们在与集合S的最短边的过程中,是将每一次压入集合S的点与原来的点到其他的点的距离作比较,就能不断地更新每一次的当前到集合S最短的点
类似于dij,但是dij算法是对于点的讨论,不断地更新每一个点到起点的最短距离,
而在树里面,我们要的是不断地进行更新其他点到这个集合里面的最小距离
我们以一道典型的模板题为例
POJ constructing road
首先说一下克鲁斯卡尔算法
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<iomanip>
#include<string>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<stack>
#include<utility>
#define PI acos(-1.0)
using namespace std;
const int N = 105;
int pre[N],g[N][N];
struct node{
int u;
int v;
int w;
};
node road[N * N];
inline void init(){
for(int i = 1;i <= N;i++) pre[i] = i;
}
inline int sFind(int x){
if(x == pre[x]) return x;
return pre[x] = sFind(pre[x]);
}
inline void join(int x,int y){
int fx = sFind(x),fy = sFind(y);
if(fx != fy){
pre[fx] = fy;//这一步每一次都搞不明白,要进行理解
//将前一个的父节点的父节点改为另一个
}
}
inline bool cmp(node x,node y){
return x.w < y.w;
}
int main(){
init();
int n;
cin >> n;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
scanf("%d",&g[i][j]);
}
}
int m,m1,m2;
cin >> m;
while(m--){
scanf("%d%d",&m1,&m2);
join(m1,m2);
}
int t = 1;
for(int i = 1;i <= n;i++){
for(int j = i+1;j <= n;j++){
road[t].u = i;
road[t].v = j;
road[t].w = g[i][j];
t ++;
}
}
long long sum = 0;
sort(road,road + t,cmp);
int fx,fy;
for(int i = 1;i < t;i++){
fx = sFind(road[i].u);
fy = sFind(road[i].v);
if(fx != fy){
pre[fx] = fy;
sum += road[i].w;
}
}
cout << sum <<endl;
return 0;
}
当时不敢自己写,直接抄的自己之前做的答案,但是AC之后,自己靠着自己的理解写了出来,一遍AC!!!,我就是神哈哈哈哈
说正事(咳咳咳!!!)
这个算法的核心在于哪里呢,我们以路为研究对象,把路的端点u,v,以及权重w全部存入,然后开始从小到大分析,只要这条路的两端点,还没有联通,那么我们就进行联通,其中怎么判断两条路有没有联通,判断路的连通性,那肯定是并查集最简单了!!
下面说一下prime算法
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<iomanip>
#include<string>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<stack>
#include<utility>
#define PI acos(-1.0)
using namespace std;
const int N = 105;
int g[N][N];
int dis[N],n,vis[N];
int sum;
int prime(int x){
memset(vis,0,sizeof(vis));
vis[x] = 1;
for(int i = 1;i <= n;i++){
dis[i] = g[x][i];
}
int minn;
int mm;
//取图,准备压入每一个点
for(int i = 1;i < n;i++){//对其他所有的路都进行压入操作
minn = 0x3f3f3f3f;//禁止把minn设置为第一个点这样的东西,因为我们是在一个循环里面进行操作,每一次都在改变
for(int j = 1;j <= n;j++){
if(!vis[j] && minn > dis[j]){
minn = dis[j];
mm = j;
}
}//找到离自己最短的
vis[mm] = 1;
sum += dis[mm];
//进行松弛,更新
for(int j = 1;j <= n;j++){
if(!vis[j] && dis[j] > g[mm][j]){
dis[j] = g[mm][j];
}
}
//更新为到这个集合的最小距离
}
return sum;
}
int main(){
cin >> n;
memset(g,0x3f3f3f3f,sizeof(g));
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
scanf("%d",&g[i][j]);
}
}
int m,m1,m2;
cin >> m;
while(m --){
cin >> m1 >> m2;
g[m1][m2] = g[m2][m1] = 0;
}
cout << prime(1);
return 0;
}
用prime算法就是从一点出发,不断地找最短的路,如果某个点还没有联通,就找一个最近的路进行联通,通过所有点寻找最短路
dij算法,是从一点出发,不断地看当前离集合S最短的点,然后知道了一条到达某个点的最短路径,通过所有点进行松弛