(一)prime算法
【概念】:
***生成树:一个连通图的生成树,指的是该图的一个子图(没有形成回路),他包含图的所有结点,但只有把所有结点连接在一起的N-1条必要的边;
***最小生成树:一个连通图的生成树中,所有边的权值加起来最小的生成树;称为最小生成树;
【简介】:Prime算法可在加权连通图里搜索最小生成树。即:所有边的权值之和为最小。
Prime算法是图论中求最小生成树的一种算法,与之类似的算法还有Kruskal算法;
区别:
- Prime算法适合边多定点少的图;
2.Dijkstra算法适合边少定点多的图;
1.1 存图方式
要求最小生成树,当然首先要把图存进一个东西中,这样才能图对进行搜索操作。
一般的存图方式有三种:
1.邻接矩阵
存图思想:用一个矩阵来记录一个图,矩阵第 i 行第 j 列的值就表示顶点 i 到顶点 j 的权值
1.2 邻接表
存图思想:对每个顶点,使用不定长的链表来存储从该点出发的所有边的情况,第 i 个链表的第 j 个值就是顶点 i 到顶点 j 的权值。
1.3 链式前向星
存图思想:主要的数据结构是边数组(存储边的数组),当然想要完美表示图,光有一个边集数组还不够,还要有一个数组每一个点的第一条边。同时,每一条边都需要存储接下来一条边的“指针”
1.2 Prime算法分解
1.2.1 用到的数组
我写的这个Prime算法存图用的是邻接矩阵。
int mp[N][N]; //使用邻接矩阵存图;
int dis[N]; //到生成树的最短距离;
int vis[N]; //标记数组,标记该结点是否纳入集合,即该结点是否访问过;
int p[N]; //存储父亲结点;
1.2.2 初始化
初始化:自己与自己的距离为0,自己与别的结点的距离初始化为无穷大,即,表示不连通;
将所有的结点都标记为为访问,我这用0表示未访问,1表示已访问;
memset(vis, 0, sizeof(vis)); //全部未访问过
无穷大:#define INF 0x3f3f3f3f
void inin(int n)//初始化:mp数组初始化,vis标记数组初始化;
{
int i, j;
memset(vis, 0, sizeof(vis)); //全部未访问过
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
{
if (i == j)
mp[i][j] = 0; //自己到自己的距离为0;
else
mp[i][j] = INF; //任意两点之间的距离置为无穷大,即:刚开始都未连接;
}
}
}
1.2.3 Prime算法主体
传入一个节点,当然,传入的这个节点是随意的。将这个点到其他点的距离存入dis数组,并将传入的这个点标记为已访问。
然后找出从st(传入的那个点)出发的路径中的最短的一个路径;并将它到达的那个点记性并标记。然后更新dis数组(重点):如果该结点没有被访问过,且点距离当前点的距离更近,就执行更新;最后dis数组中就是最小生成树的最短路径的集合;对其求和,即是最小生成树的最短路径;
代码实现:
void prime(int st, int n) //st是任意一个开始的结点,此节点随意;
{
int i, j;
for (i = 1; i <= n; i++) //将与st点相连的路径存到dis数组中;
{
if (mp[st][i] != INF)
{
p[i] = st;
}
dis[i] = mp[st][i];
}
vis[st] = 1; //将该点标记为访问过;
while (1)
{
int k = -1;
int min = INF;
for (i = 1; i <= n; i++) //在此时从st发出的路径中找到一个最短的路径;
{
if (vis[i] != 1 && dis[i] < min)
{
min = dis[i];
k = i; //此时,st到i的路径最短,并将i记下,后面标记为已访问;
}
}
if (k == -1) //所有结点都已访问;即:已生成最小生成树;跳出死循环;
{
break;
}
vis[k] = 1; //将刚才i连接的结点标记为已访问;
for (i = 1; i <= n; i++) //更dis数组,
{
if (vis[i] != 1 && dis[i] > mp[k][i]) //该结点没有被访问过,且点距离当前点的距离更近,就执行更新
{
dis[i] = mp[k][i];
p[i] = k;
}
}
}
for (i = 1; i <= n; i++)
{
sum = sum + dis[i];
}
}
完整的使用邻接矩阵存图的Prime算法如下:
#include<iostream>
#include<cstring>
using namespace std;
#define N 1000
#define INF 0x3f3f3f3f
int mp[N][N]; //使用邻接矩阵存图;
int dis[N]; //到生成树的最短距离;
int vis[N]; //标记数组,标记该结点是否纳入集合,即该结点是否访问过;
int sum = 0;
int p[N]; //父亲结点;
void inin(int n)//初始化:mp数组初始化,vis标记数组初始化;
{
int i, j;
memset(vis, 0, sizeof(vis)); //全部未访问过
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
{
if (i == j)
mp[i][j] = 0; //自己到自己的距离为0;
else
mp[i][j] = INF; //任意两点之间的距离置为无穷大,即:刚开始都未连接;
}
}
}
void prime(int st, int n) //st是任意一个开始的结点,此节点随意;将st看做最小生成树的根节点
{
int i, j;
for (i = 1; i <= n; i++) //将st点出发的所有路径存到dis数组中;
{
if (mp[st][i] != INF)
{
p[i] = st;
}
dis[i] = mp[st][i];
}
vis[st] = 1; //将该点标记为访问过;
while (1)
{
int k = -1;
int min = INF;
for (i = 1; i <= n; i++) //在此时从st发出的路径中找到一个最短的路径;即:在当前的生成树中找一条最短路径;
{
if (vis[i] != 1 && dis[i] < min)
{
min = dis[i];
k = i; //此时,st到i的路径最短,并将i记下,后面标记为已访问;
}
}
if (k == -1) //所有结点都已访问;即:已生成最小生成树;跳出死循环;
{
break;
}
vis[k] = 1; //将刚才i连接的结点标记为已访问;
for (i = 1; i <= n; i++) //更dis数组,
{
if (vis[i] != 1 && dis[i] > mp[k][i]) //该结点没有被访问过,且该点到K点的距离比该点到根节点的距离更近,就执行更新
{
dis[i] = mp[k][i];
p[i] = k;
}
}
}
for (i = 1; i <= n; i++)
{
sum = sum + dis[i];
}
}
void parent(int n)
{
int i, j;
for (i = 1; i <= n; i++)
{
if (p[i] == i)
cout << i << "为根节点" << endl;
else
cout << i << "的父亲结点是:" << p[i] << endl;
}
}
int main()
{
int n;
int i, j;
cin >> n;
inin(n);
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
{
cin >> mp[i][j];
}
}
prime(1, n);
cout << sum << endl;
parent(n);
return 0;
}
输入样例:
4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0
如果传入的结点是1,那么,
刚开始的dis数组如下:
0 0 4 9 21
执行更新之后:
0 0 4 8 16
当然,你也可以在输入的时候指定特定点之间的距离:
例:x到y的距离为sp;
x y sp
1 2 5
1 3 3
1 4 7
1 5 4
1 6 2
inin(n);
for (int i=1;i<=m;i++)
{
int x,y,sp;
cin>>x>>y>>sp
mp[x][y]=mp[y][x]=sp;
}
prim();
parent();