两个经常忘记的问题
1)过程如何保证图连通
2)为什么是贪心算法而不是动态规划
参考自
笔记:边多prim,点多kruskal
prim算法:
理解:
初始化:设置所有点的dis[i]无限大,更新从起点能达到的所有点的距离dis[i],并设置起点已经被访问。
过程:每次寻找dis最小且未被访问的点,并将这个dis值加到sum,更新从当前起点能达到的所有点的距离dis[i],最终sum的值就是最小生成树的值。
问题(1)过程如何保证图连通(2)为什么是贪心算法。更新从当前起点能达到的所有点的距离dis[i],这一步其实也保存了从当前起点能达到的所有点,可能当前起点只会选择一个点到达,或者一个都不选。是按照dis最小来选择的,也就是说从访问过的点,能达到的所有点中挑一个花费最小的(这一步保证了图连通,也说明了为什么是贪心而不是动规,备注),作为下一次的当前起点。
备注:图连通这一过程不具备“传递”这一特点,不具备动态规划的各种特点,所以为贪心。Prim算法能记录路径。
代码:
int prim(int start)
{
int i,j,now,sum=0;
for(i=1;i<=n;i++){//初始化
dis[i]=max1;
vis[i]=false;
}
for(i=1;i<=n;i++){//先选定1为起点,
dis[i]=graph[1][i];
}
dis[1]=0;
vis[1]=true;
for(i=1;i<n;i++){//循环n-1次数,寻找最小边
now=max1;
int min1=max;
for(j=1;j<=n;j++){//寻找最小边
if(vis[j]==false&&dis[j]<min1){
now=j;
min1=dis[j];
}
}
if(now==max1) break;//如果不成图,
vis[now]=true;
sum+=min1;
for(j=1;j<=n;j++){//添加新的结点后更新距离
if(vis[j]==false&&dis[j]>graph[now][j]){
dis[j]=graph[now][j];
}
}
}
if(i<n) printf("????\n");
else printf("%d\n",sum);
}
kruskal算法:
理解:有了前面Prim算法的理解,Kruskal算法理解起来也会更容易一些。
初始化:首先按照边的值大小进行排序。并查集初始化,所有点指向自己f[i] = i。
过程:每次挑选边最小的,且未加入集合的(就是未访问),当挑选的边达到n-1说明图已经连通了。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=10010,maxv=110;
int f[maxv];//
struct node{
int u,v;//边的两个顶点编号
int cost;//边权
}graph[maxn];
bool cmp(node x,node y)//设置为升序
{
return x.cost<y.cost;
}
int findfather(int x)//并查集部分
{
int a=x;
while(x!=f[x]) x=f[x];
while(a!=f[a]){//状态压缩
int z=a;
a=f[a];
f[z]=x;
}
return x;
}
int kruskal(int n,int m)//kruskal部分
{
int ans=0,sumedge=0;//ans为所求边权之和,sumedge为当前生成树的边数
for(int i=0;i<n;i++) f[i]=i;//并查集初始化
sort(graph,graph+m,cmp);
for(int i=0;i<m;i++){
int fu=findfather(graph[i].u);//查询测试边两个端点所在集合的根结点
int fv=findfather(graph[i].v);
if(fu!=fv){
f[fu]=fv;//合并集合
ans+=graph[i].cost;//边权之和增加测试边的边权
sumedge++;//当前生成树的边数加1
if(sumedge==n-1) break;//边数等于顶点数减一时结束算法
}
}
if(sumedge!=n-1) return -1;//无法连通时返回-1
return ans;//返回最小生成树的边权之和
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);//顶点数,边数
for(int i=0;i<m;i++){
scanf("%d%d%d",&graph[i].u,&graph[i].v,&graph[i].cost);//两个端点,边权值
}
printf("%d\n",kruskal(n,m));
return 0;
}
/*
6 10
0 1 4
0 4 1
0 5 2
1 2 1
1 5 3
2 3 6
2 5 5
3 4 5
3 5 4
4 5 3
*/