前几天去忙蓝桥杯省赛了唉,好几天没写博客了。不知道在那个算法群里看到大佬说3个填空题就可以获奖了。。。全程做选择题,结果后面没时间,有一个大题都没来得及看题目,还有一题怂了,明明最近都是写图论却还是怕图论。。。唉,打这种差一点的水漂打得难受。今天把学弟群里的专题拿去刷了,最小生成树。PS:不得不说,学弟真的tql。。。
最小生成树:
在数据结构里学了prime算法(入顶点),kruskal算法(入边),说实话,离散数学里就有这个了,那个时候觉得破圈法真的太简单了(kruskal),但是代码老是有点忘记,没办法秒写。。。
先码出我自己写的kruskal和prime算法的模版:
kruskal算法:
#include<cstdio>
#include<algorithm>
using namespace std;
#define INF 3000
struct Edge //边的结构体
{
int a,b,cost; //(a,b)边的权cost
}p[10000+5]; //边的集合,等会用于排序
int parent[100+5]; //并查集,将每个联通分量都用最上方的根表示
bool com(Edge a,Edge b)
{
return a.cost<b.cost;
}
void init() //初始化parent数组
{
for(int i=1;i<=100+5;i++)
{
parent[i]=i;
}
}
int find(int x)
{
int r=x;
while(r!=parent[r]) //寻找根结点
r=parent[r];
int i=x,j; //i是最底层,j是从i往根处爬,
while(i!=r) //压缩路径 将i结点直接连着根,j为i结点之前的父亲
{
j=parent[i]; //j为i结点之前的父亲
parent[i]=r;
i=j; //重新对i结点(此时为前一个i结点的父亲) 做压缩路劲
} //最终所有的结点都有共同的父亲,共同的根,一个根连着很多直系儿子
return r;
}
int Union(int a,int b) //连接两个连通分量 a和 b
{
int x=find(a);
int y=find(b);
if(x!=y) //如果a和b不是同一个连通分量则把其中一个联通分量的根认另一个联通分量的根为根(老大)
{
parent[x]=y;
return 1;
}
return 0;
}
int main()
{
int n,m,x,y;
while(~scanf("%d",&n)) //输入顶点的数量
{
int ans=0;
if(!n) break;
int k=1,c;
for(int i=1;i<=n;i++) //输入每个边的情况
for(int j=1;j<=n;j++)
{
scanf("%d",&c);
p[k].a=i;
p[k].b=j;
p[k++].cost=c;
}
init();
sort(p+1,p+k,com); //排序边,从小到大
int cont=0;
for(int i=n+1;i<=k;i++)
{
if(Union(p[i].a,p[i].b)) //如果边的两个顶点不是同一个连通分量则为我们选定的边
{
ans+=p[i].cost;
cont++;
}
if(cont==n-1)
break;
}
printf("%d\n",ans);
}
return 0;
}
kruskal算法最关键的就是①判连通②连通两个分量
①判连通的解决方法是用并查集,将同一个连通分量变成一个树,查询的时候一直查到根位置,若根相同,则两个分量是同一个连通分量,若不同则不是。比较难理解的压缩路径(之前老是忽略这一步),是在查询连通分量的时候将根变为最上方的根,一直往上爬,将连通分量中除了根节点外,其余的父亲结点都为根节点。
②连通两个分量的方法简单,只需要将一个分量的树结构的根变成另一个分量的树结构的根的儿子即可。
prime算法:
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define maxn 30
#define inf 100000
int road[maxn][maxn]; //邻接矩阵
int dis[maxn]; //判断行,从中取出最小点加入vis
bool vis[maxn]; //true代表已经纳入,false代表是为纳入
int n;
void prim()
{
int minn, v;
for(int i = 0; i < n; i++)
{
dis[i] = road[0][i]; //每个点与起点的距离
vis[i] = false;
}
for(int i = 1; i <= n; i++)//包括第一个点在内,一共要纳入n个点
{
min = inf;
for(int j = 0; j < n; j++)
{
if(!vis[j] && minn > dis[j]) //每次找出未纳入顶点集与已知顶点集构成的权值最小的一条边
{
v = j;
minn = dis[j];
}
}
vis[v] = true;//把该顶点纳入已知集合 ,v就是未纳入顶点最短距离的点
for(int j = 0; j < n; j++)//更新与未纳入集合中的顶点的边的最小权值
{
if(vis[j]==false && dis[j] > road[v][j]) //未纳入 且 每个点与起点的距离大于新纳入的顶点v的距离
dis[j] = road[v][j]; //更新新的短距离
}
}
int ans;
for(int i = 1; i < n; i++)
ans += dis[i]; //将每个点(除起点)的距离累加
printf("%d\n",ans);
}
int main()
{
int n;
while(~scanf("%d",&n)&&n!=0)
{
for(int i = 0; i < n; i++) //初始化邻接矩阵
road[i][i] = 0;
for(int i = 1; i < n; i++) //构造邻接矩阵
{
for(int j=1;j<=n;j++)
scanf("%d",&road[i][j]);
}
/*for(int i=0;i<n;i++)
{
for(int j =0;j<n;j++)
printf("%d ",road[i][j]);
printf("\n");
} */
prim();
}
return 0;
}
最小生成树专题训练:https://vjudge.net/contest/216338