#include<iostream>
#include<algorithm>
using namespace std;
#define MVNum 100//最大顶点数
#define OK 1
#define ERROR 0
typedef char VerTexType;//假设顶点的数据类型为字符型
typedef int ArcType;//假设边的权值类型为整型
typedef int Status;
typedef struct
{
VerTexType vexs[MVNum];//顶点表
ArcType arcs[MVNum][MVNum];//邻接矩阵
int vexnum, arcnum;//图的当前点数和边数
}AMGraph;
struct EdgeM
{
VerTexType Head;
VerTexType Tail;
ArcType lowcost;
}Edge[MVNum];
int Vexset[MVNum];
pair <int, int> ans[MVNum];
int LocateVex(AMGraph G, VerTexType u)
{//初始条件:图G存在,u和G中顶点有相同特征
//操作结果:若G中存在顶点u,则返回该顶点在图中位置,否则返回-1;
int i;
for (i = 0; i < G.vexnum; ++i)
if (u == G.vexs[i])
return i;
return -1;
}
Status CreatUDN(AMGraph& G)
{//采用邻接矩阵表示法,创建无向网G
int i, j, k,w;
char v1, v2;
cout << "请输入总顶点数,总边数:" << endl;
cin >> G.vexnum >> G.arcnum;//输入总顶点数,总边数
cout << "依次输入点的信息:" << endl;
for (i = 0; i < G.vexnum; ++i)//依次输入点的信息
cin >> G.vexs[i];
for (i = 0; i < G.vexnum; ++i)//初始化邻接矩阵,边的权值均置为0
for (j = 0; j < G.vexnum; ++j)
G.arcs[i][j] = 0;
cout << "请分别输入边依附的顶点以及权值:" << endl;
for (k = 0; k < G.arcnum; ++k)//构建邻接矩阵
{
cin >> v1 >> v2>>w;//输入一条边依附的顶点和权值
i = LocateVex(G, v1);//确定v1和v2在G中的位置,即顶点数组的下标
j = LocateVex(G, v2);
G.arcs[i][j] = w;//边<v1,v2>的权值置为w
G.arcs[j][i] = G.arcs[i][j];//置<v1,v2>的对称边<v2,v1>的权值为w
Edge[i] = { G.vexs[i], G.vexs[j], w };
}
return G.arcnum;
}
bool cmp(const EdgeM& a, const EdgeM& b)
{
return a.lowcost < b.lowcost;
}
void Sort(AMGraph G)
{
sort(Edge, Edge + G.arcnum, cmp);
}
void output(AMGraph* G)
{
int i, j, count = 0;
for (i = 0; i < G->vexnum; i++)
cout << " " << G->vexs[i];
cout << endl;
for (i = 0; i < G->vexnum; i++)
{
cout << G->vexs[i] << " ";
for (j = 0; j < G->vexnum; j++)
{
cout << G->arcs[i][j] << " ";
count++;
if (count % G->vexnum == 0)
cout << endl;
}
}
}
void MiniSpanTree(AMGraph& G)
{
Sort(G);
int cnt = 0;
int v1, v2, vs1, vs2;
for (int i = 0; i < G.vexnum; ++i)
Vexset[i] = i;
for (int i = 0; i < G.arcnum-1; ++i) {
v1 = LocateVex(G, Edge[i].Head);
v2 = LocateVex(G, Edge[i].Tail);
vs1 = Vexset[v1];
vs2 = Vexset[v2];
if (vs1 != vs2)
{
cout << Edge[i].Head << ' ' << Edge[i].Tail << endl;
ans[cnt++] = { LocateVex(G, Edge[i].Head), LocateVex(G, Edge[i].Tail) };//c++11标准,c99会警告,分开赋值即可
for (int j = 0; j < G.vexnum; ++j) {
if (Vexset[j] == vs2) Vexset[j] = vs1;
}
}
}
}
int main() {
AMGraph(G);
CreatUDN(G);
output(&G);
MiniSpanTree(G);
return 0;
}
输出截图发我
kruskal 算法也是一种贪心算法,只不过 Prim 算法是对生成树上的点所连的边贪心,而kruskal 算法是对所有的边进行贪心。
kruskal 算法的策略是:先把每个节点当成一颗最小生成树,然后按照边权从小到大排序,每次枚举边权最小的边,如果两个点不在同一棵最小生成树中,就将两棵生成树合并,反之则舍弃,直到插入 n-1 条边形成一珂生成树。得到的生成树就是最小生成树。
kruskal 算法与并查集有惊人的相似,所以建议先学会并查集。
显然,这种策略是正确的。
因此,kruskal 算法的时间复杂度是:O(m×log m) 。
图文解释:
【代码实现】
int n,m;//n个点,m条边
int sum,ans;//sum记录连接的边数
int fa[1000001];//记录其祖先
struct node//结构体
{
int x;//端点之一
int y;//端点之二
int w;//边权值
}a[1000001];
int cmp(node x,node y)//将数据按照从小到大的顺序排序
{
return x.w<y.w;
}
int find(int k)//寻找k的祖先
{
if(fa[k]==k)
{
return k;
}
return fa[k]=find(fa[k]);
}
for(int i=1;i<=n;i++)//初始化
{
fa[i]=i;
}
sort(a+1,a+1+m,cmp);//排序
for(int i=1;i<=m;i++)//加边
{
int x=find(a[i].x);//找端点之一的祖先
int y=find(a[i].y);//找端点之二的祖先
if(x!=y)//如果不在同一个集合
{
fa[y]=x;//合并
sum++;//边数加1
ans+=a[i].w;//记录边权和
if(sum==n-1)//如果边数达到n-1,则寻找完毕
{
break;/*/跳出循环
}
}
}
小编为大家引入一道经典的 kruskal 的题目,以便大家巩固。
题目传送名:P2330 [SCOI2005]繁忙的都市
解题思路:其实这道题不论是用 Prim 还是 kruskal 都很简单,不过这里主要以练习 kruskal 为主要目的,所以,重点讲 kruskal 算法。
题目要求的是最少的边数和生成树内最大的边权值,边的数量肯定是 n-1 ,最大的边权值也只需要在跑 kruskal 的时候记录即可。
【代码实现】
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map>
#include<vector>
#include<string>
#include<cstring>
#include<queue>//头文件
using namespace std;
int n,m;//n个点,m条边
int sum,ans;//sum用来记录加入生成树的边的数量,ans用来更新最大的边权值
int fa[1000001];//记录祖先节点
struct node//记录每条边的信息
{
int x;
int y;
int w;//边的权值
}a[1000001];
int cmp(node x,node y)//将所有的边按照从小到大的顺序排列
{
return x.w<y.w;
}
int find(int k)//寻找祖先元素
{
if(fa[k]==k)
{
return k;
}
return fa[k]=find(fa[k]);
}
int main()
{
scanf("%d%d",&n,&m);//读入
for(int i=1;i<=n;i++)
{
fa[i]=i;//初始化
}
for(int i=1;i<=m;i++)//读入
{
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
a[i].x=x;
a[i].y=y;
a[i].w=w;
}
sort(a+1,a+1+m,cmp);//排序
for(int i=1;i<=m;i++)//枚举边
{
int x=find(a[i].x);//查询a[i].x的祖先
int y=find(a[i].y);//查询a[i].y的祖先
if(x==y)//如果已经在一个集合中,则忽略
{
continue;
}
fa[x]=y;//合并两个集合
sum++;//边数累加
ans=max(ans,a[i].w);//更新最大边权值
if(sum==n-1)//如果生成树中的边数等于n-1,则最小生成树已经生成
{
break;//跳出
}
}
printf("%d %d\n",sum,ans);//输出
return 0;//完结撒花
}