最小生成树问题有两种经典的算法,分别是Prim和Kruskal,他们的代码有很多种,但要使复杂度更低,算法的具体实现过程还是值得讨论的:
这里分别给出相对复杂度更低一点的代码:
Prim:
struct node{
int u,w;
node(){};
node(int a, int b)
{
this->u = a;
this->w = b;
}//构造函数
bool operator<(const node& a)
const{
return this->w > a.w
}//比较运算函数用以建优先队列
}Edge[101];
struct node2{
int len;
int v[101];
int w[101];
}V[101];
上述步骤已经顺利写出了两组数据结构,第一组是储存待会构造优先队列的数据,第二组是储存我们输入的边、点集,接下来是主函数:
int main()
{
int N,i,a,b,c;
for(i = 0; i <= 100; i++)
V[i].len = 0;
scanf("%d",&N);
while(scanf("%d%d%d",&a,&b,&c)
{
V[a].v[len] = b;
V[a].w[len++] = c;
V[b].v[len] = a;
V[b].w[len++] = c;
}
int U[101],key[101],p[101];
//U用来标记是否已经遍历,key用来标记当前点的最短邻接边,p用来标记点的最短路径所连接的端点
for(i = 1; i <= N; i++)
{
U[i] = 0;
p[i] = 0;
key[i] = MAX_N;
}
key[a] = 0;//从a开始选路线
priority_queue<node> Q;
Q.push(node(a,0));
while(!Q.empty())
{
node E = Q.top();
Q.pop();
if(U[E.u] != 0)
continue;
U[E.u] = 1;
for(i = 0; i < V[E.u].len; i++)
{
int u = E.u;
int v = V[E.u].v[i];
int w = V[E.u].w[i];
if(U[v] != 0)
continue;
if(key[v] < w)
{
key[v] = w;
Q.push(node(v,w));
p[v] = u;
}
}
}
for(i = 1; i <= N; i++)
{
printf("%d %d %d\n",i,p[i],key[i]);
}
return 0;
}
Kruskal:
接下来我们再来探讨一下Kruskal算法,Kruskal算法的思想就是先把边按照权值排序,遍历的时候按照边的权值大小来取边,只要不形成回路,这条边就能使用:
先来构造数据结构:
struct node {
int u,v,w;
node(){};
node(int a, int b, int c)
{
this->u = a;
this->v = b;
this->w = c;
}
bool operator< const(node &a)
const{
return this->w > a.w
}
}Edge[101];
int p[x], rank[x];//后述并查集需要用到
这样我们的数据结构就已经构造完毕了,接下来是输入的过程:
int main()
{
int N,i,a,b,c;
scanf("%d",&N);
std::priority_queue<node> Q;
while(scanf("%d%d%d", &a, &b, &c) != EOF)
{
Q.push(node(a,b,c));
}
用优先队列直接将每一轮输入压入队列中,在队列中针对权值大小对每一条边进行排序,接下来是取边,取边的时候我们需要保障不形成回路,这里就要用到并查集的思想,每一个相连通的点都在同一个Union中,如果一条边的两个点不在一个Union中,那么我们就可以取这条边:
for(i = 0; i <= N; i++)
{
p[i] = i;
rank[i] = 0;
}
int s = 0;
while(!Q.empty())
{
node V = Q.top();
Q.pop();
int u = Q.u;
int v = Q.v;
int w = Q.w;
if(Find(u) != Find(v))//判定u和v是否在同一个Union中,如果不在,则执行下面程序
{
Link(Find(u), Find(v));
Edge[s].u = u;
Edge[s].v = v;
Edge[s++].w = w;
}
}
for(i = 0; i < s; i++)
{
printf("%d %d %d", Edge[i].u, Edge[i].v, Edge[i].w);
}
return 0;
}
到此我们的主函数就结束了,下面介绍一下并查集:
并查集就是集合,把一些特定相关的元素放到同一个集合中,比如此处的相连通的点,就应该放在同一个集合中,如果下次取边的时候恰好边的两端点在同一个集合中,那么就说明如果把这条边加上去图就形成回路了,岂不是一种很方便的操作?
补充:
Find函数:
int Find(int x)
{
if(p[x] == x)
return x;
else
return (Find(p[x]));
}
Link函数:
void Link(int x, int y)
{
if(rank[x] == rank[y])
{
p[x] = y;
rank[y]++;
}
else if(rank[x] > rank[y])
{
p[y] = x;
}
else if(rank[x] < rank[y])
{
p[x] = y;
}
}