Prim和Kruskal算法
普里姆(Prim)算法——基本思想:
设G=(V, E)是具有n个顶点的连通网,
T=(U, TE)是G的最小生成树,
T的初始状态为U={u0}(u0∈V),TE={ },
重复执行下述操作:
在所有u∈U,v∈V-U的边中找一条代价最小的边(u, v)并入集合TE,同时v并入U,直至U=V。
Prim算法——伪代码:
- 初始化两个辅助数组lowcost(=arc[0][i])和adjvex(=0)(0是始点);
- 输出顶点u0,将顶点u0加入集合U中;
- 重复执行下列操作n-1次
3.1 在lowcost中选取最短边(lowcost[k]),取对应的顶点序号k;
3.2 输出顶点k和对应的权值;
3.3 将顶点k加入集合U中(lowcost[k]=0);
3.4 调整数组lowcost和adjvex;
Void prime(MGraph G){
for(int i=1;i<G.vertexNu;i++){
lowcost[i]=G.arc[0][i]; adjvex[i]=0;
}
lowcost[0]=0;
for(i=1;i<G.vertexNum;i+++){
k=MinEdge(lowcost,G.vertexNum)
cout<<K<<adjvex[k]<<lowcost[k];
lowcost[k]=0;
for(j=1;j<G.vertexNum;j++)
if((G.arc[k][j]<lowcost[j]){
lowcost[j]=G.arc[k][j];
arcvex[j]=k;
}
}
}
克鲁斯卡尔(Kruskal)算法 ——基本思想:
1.设无向连通网为G=(V, E),令G的最小生成树为T=(U, TE),其初态为U=V,TE={ },
2.然后,按照边的权值由小到大的顺序,考察G的边集E中的各条边。
(1)若被考察的边的两个顶点属于T的两个不同的连通分量,则将此边作为最小生成树的边加入到T中,同时把两个连通分量连接为一个连通分量;
(2)若被考察边的两个顶点属于同一个连通分量,则舍去此边,以免造成回路,
3.如此下去,当T中的连通分量个数为1时,此连通分量便为G的一棵最小生成树。
克鲁斯卡尔(Kruskal)算法 ——伪代码:
- 初始化:U=V; TE={ };
- 循环直到T中的连通分量个数为1
2.1 在E中寻找最短边(u,v);
2.2 如果顶点u、v位于T的两个不同连通分量,则
2.2.1 将边(u,v)并入TE;
2.2.2 将这两个连通分量合为一个;
2.3 在E中标记边(u,v),使得(u,v)不参加后续最短边的选取;
关键问题:
1.图的存储结构
采用边集数组存储图。
2.如何判断一条边所依附的两个顶点在同一个连通分两中
定义Parent[i]数组,辅助完成连通分量的处理。数组分量的值表示顶点i的双亲节点(初值为-1;)
当一条边(u,v)的两个顶点的根结不同时,这两个结点属于不同的连通分量(利用parent 数组查找一棵树的根节点。当一个结点n的parent==-1,树的根节点即为n)
3. 如何将一条边所依附的两个顶点合并到同一个连通分量中
要进行联通分量的合并 ,其中一个顶点所在的树的根节点为vex1,另一个顶点所在的树的根节点为vex2,则:parent[vex2]=vex1;
for (k=0;k<arcNum;k++) {
begin=edge[k].from; end=edge[k].to;
int m,n;
m=Find(parent,begin); n=Find(parent,end);
if(m!=n) {
cout<<begin<<","<<end<<","<<edge[k].weight<<endl;
parent[n]=m; count++;
if(count==vertexNum-1) break;
}
}
return 0;
}
int Find(int *parent, int node)
{
int f;
f=node;
while(parent[f]>-1)
f=parent[f];
return f;
}
Prim 完整代码:
#include<iostream>
#define INF 10000
using namespace std;
constint N = 6;
bool visit[N];
intdist[N] = { 0, };
intgraph[N][N] = { {INF,7,4,INF,INF,INF}, //INF代表两点之间不可达
{7,INF,6,2,INF,4},
{4,6,INF,INF,9,8},
{INF,2,INF,INF,INF,7},
{INF,INF,9,INF,INF,1},
{INF,4,8,7,1,INF}
};
intprim(intcur)
{
intindex = cur;
intsum = 0;
inti = 0;
intj = 0;
cout << index << " ";
memset(visit,false, sizeof(visit));
visit[cur] = true;
for(i = 0; i < N; i++)
dist[i] = graph[cur][i];//初始化,每个与a邻接的点的距离存入dist
for(i = 1; i < N; i++)
{
intminor = INF;
for(j = 0; j < N; j++)
{
if(!visit[j] && dist[j] < minor) //找到未访问的点中,距离当前最小生成树距离最小的点
{
minor = dist[j];
index = j;
}
}
visit[index] = true;
cout << index << " ";
sum += minor;
for(j = 0; j < N; j++)
{
if(!visit[j] && dist[j]>graph[index][j]) //执行更新,如果点距离当前点的距离更近,就更新dist
{
dist[j] = graph[index][j];
}
}
}
cout << endl;
returnsum; //返回最小生成树的总路径值
}
intmain()
{
cout << prim(0) << endl;//从顶点a开始
return0;
}
Kruskal完整代码:
#include<iostream>
#define N 7
using namespace std;
typedef struct _node{
intval;
intstart;
intend;
}Node;
Node V[N];
intcmp(constvoid *a, constvoid *b)
{
return(*(Node *)a).val - (*(Node*)b).val;
}
intedge[N][3] = { { 0,1,3},
{0,4,1},
{1,2,5},
{1,4,4},
{2,3,2},
{2,4,6},
{3,4,7}
};
intfather[N] = { 0, };
intcap[N] = {0,};
voidmake_set() //初始化集合,让所有的点都各成一个集合,每个集合都只包含自己
{
for(inti = 0; i < N; i++)
{
father[i] = i;
cap[i] = 1;
}
}
intfind_set(intx) //判断一个点属于哪个集合,点如果都有着共同的祖先结点,就可以说他们属于一个集合
{
if(x != father[x])
{
father[x] = find_set(father[x]);
}
returnfather[x];
}
voidUnion(intx, inty) //将x,y合并到同一个集合
{
x = find_set(x);
y = find_set(y);
if(x == y)
return;
if(cap[x] < cap[y])
father[x] = find_set(y);
else
{
if(cap[x] == cap[y])
cap[x]++;
father[y] = find_set(x);
}
}
intKruskal(intn)
{
intsum = 0;
make_set();
for(inti = 0; i < N; i++)//将边的顺序按从小到大取出来
{
if(find_set(V[i].start) != find_set(V[i].end)) //如果改变的两个顶点还不在一个集合中,就并到一个集合里,生成树的长度加上这条边的长度
{
Union(V[i].start, V[i].end); //合并两个顶点到一个集合
sum += V[i].val;
}
}
returnsum;
}
intmain()
{
for(inti = 0; i < N; i++) //初始化边的数据,在实际应用中可根据具体情况转换并且读取数据,这边只是测试用例
{
V[i].start = edge[i][0];
V[i].end = edge[i][1];
V[i].val = edge[i][2];
}
qsort(V, N, sizeof(V[0]), cmp);
cout << Kruskal(0)<<endl;