- Prim算法
- Kruskal算法
A------Prim算法:
#include<bits/stdc++.h>
using namespace std;
const int INF=0x3fffffff;
const int N=100;
bool s[N];
int closest[N];
int lowcost[N];
void Prim(int n,int u0,int c[N][N])//n-顶点个数,u0-开始顶点,c[N][N]-带权邻接矩阵;如果s[i]=true,说明顶点i已经加入最小生成树的顶点集合u,否则顶点i属于集合v-u,将最后的相关的最小权值传递到数组lowcost。
{
s[u0]=true;//初始时,集合中u只有一个元素,即顶点u0
int i,j;
for(i=1;i<=n;i++)
{
if(i!=u0)
{
lowcost[i]=c[u0][i];//除u0之外的顶点
closest[i]=u0;//最邻近点初始化为u0
s[i]=false;//初始化u0之外的顶点不属于u集合,即属于v-u集合
}
else
lowcost[i]=0;
}
for(i=1;i<=n;i++)
{
int temp=INF;
int t=u0;
for(j=1;j<=n;j++)//在集合v-u中寻找距离集合u最近的顶点t
{
if((!s[j])&&(lowcost[j]<temp))//!s[j]表示j结点在v-u集合中
{
t=j;
temp=lowcost[j];
}
}
if(t==u0)//找不到t,跳出循环
break;
s[t]=true;//否则,将t加入集合u
for(j=1;j<=n;j++)//更新lowcost和closest
{
if((!s[j])&&(c[t][j]<lowcost[j]))//!s[j]表示j结点在v-u集合中,t到j的边值小于当前的最邻近值
{
lowcost[j]=c[t][j];//更新j的最近邻近值为t到j的边值
closest[j]=t;//更新j的最邻近点为t
}
}
}
}
int main()
{
int n,c[N][N],m,u,v,w,u0;
cout<<"输入结点数n和边数m:"<<endl;
cin>>n>>m;
int sumcost=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
c[i][j]=INF;
cout<<"输入结点数u,v和边值w:"<<endl;
for(int i=1;i<=m;i++)
{
cin>>u>>v>>w;
c[u][v]=c[v][u]=w;
}
cout<<"输入任一结点u0:"<<endl;
cin>>u0;
//计算最后的lowcost的总和,即为最后要求的最小费用之和
Prim(n,u0,c);
cout<<"数组lowcost的内容为:"<<endl;
for(int i=1;i<=n;i++)
cout<<lowcost[i]<<" ";
cout<<endl;
for(int i=1;i<=n;i++)
sumcost+=lowcost[i];
cout<<"最小的花费是:"<<sumcost<<endl;
return 0;
}
B------ Kruskal算法:
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 100;
int nodeset[N];
int n, m;
struct Edge {
int u;
int v;
int w;
}e[N*N];
bool comp(Edge x, Edge y)
{
return x.w < y.w;
}
void Init(int n)
{
for(int i = 1; i <= n; i++)
nodeset[i] = i;
}
int Merge(int a, int b)
{
int p = nodeset[a];
int q = nodeset[b];
if(p==q) return 0;
for(int i=1;i<=n;i++)//检查所有结点,把集合号是q的改为p
{
if(nodeset[i]==q)
nodeset[i] = p;//a的集合号赋值给b集合号
}
return 1;
}
int Kruskal(int n)
{
int ans = 0;
for(int i=0;i<m;i++)
if(Merge(e[i].u, e[i].v))
{
ans += e[i].w;
n--;
if(n==1)
return ans;
}
return 0;
}
int main()
{
cout <<"输入结点数n和边数m:"<<endl;
cin >> n >> m;
Init(n);
cout <<"输入结点数u,v和边值w:"<<endl;
for(int i=0;i<m;i++)
cin >> e[i].u>> e[i].v >>e[i].w;
sort(e, e+m, comp);
int ans = Kruskal(n);
cout << "最小的花费是:" << ans << endl;
return 0;
}
C------利用并查集来优化Kruskal算法:
#include <bits/stdc++.h>
using namespace std;
const int N = 100;
int father[N];
int n, m;
struct Edge {
int u;
int v;
int w;
}e[N*N];
bool comp(Edge x, Edge y) {
return x.w < y.w;//排序优先级,按边的权值从小到大
}
void Init(int n)
{
for(int i = 1; i <= n; i++)
father[i] = i;//顶点所属集合号,初始化每个顶点一个集合号
}
int Find(int x) //找祖宗
{
if(x != father[x])
father[x] = Find(father[x]);//把当前结点到其祖宗路径上的所有结点的集合号改为祖宗集合号
return father[x]; //返回其祖宗的集合号
}
int Merge(int a, int b) //两结点合并集合号
{
int p = Find(a); //找a的集合号
int q = Find(b); //找b的集合号
if(p==q) return 0;
if(p > q)
father[p] = q;//小的集合号赋值给大的集合号
else
father[q] = p;
return 1;
}
int Kruskal(int n)
{
int ans = 0;
for(int i=0;i<m;i++)
if(Merge(e[i].u, e[i].v))
{
ans += e[i].w;
n--;
if(n==1)
return ans;
}
return 0;
}
int main()
{
cout <<"输入结点数n和边数m:"<<endl;
cin >> n >> m;
Init(n);
cout <<"输入结点数u,v和边值w:"<<endl;
for(int i=0;i<m;i++)
cin>>e[i].u>>e[i].v>>e[i].w;
sort(e, e+m, comp);
int ans = Kruskal(n);
cout << "最小的花费是:" << ans << endl;
return 0;
}
两种算法的比较
(1)从算法的思想可以看出,如果图G中的边数较小时,可以采用Kruskal算法,因为Kruskal算法每次查找最短的边;边数较多可以用Prim算法,因为它是每次加一个结点。可见,Kruskal算法适用于稀疏图,而Prim算法适用于稠密图。
(2)从时间上讲,Prim算法的时间复杂度为O(n2),Kruskal算法的时间复杂度为O(eloge)。
(3)从空间上讲,显然在Prim算法中,只需要很小的空间就可以完成算法,因为每一次都是从V−U集合出发进行扫描的,只扫描与当前结点集到U集合的最小边。但在Kruskal算法中,需要对所有的边进行排序,对于大型图而言,Kruskal算法需要占用比Prim算法大得多的空间。