prim
普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graphtheory))且其所有边的权值之和亦为最小。
在我看来prim算法可以拆分成一下几步
- 初始化邻接矩阵:初始化起点到各个顶点的距离
- 记录顶点到各个的距离以及它的邻接顶点(一个线段的另一个端点)
- 寻找到剩余顶点的最小的距离
- 对新加入的顶点寻找到剩余顶点的最小的距离与原先起点初始化的距离进行比较,取最小值
- 重复 3 和 4 步骤,直到没有剩余节点
附录:对顶点操作,在最小生成树的顶点集U和待处理顶点集V-U中,不断地寻找最短边(代价最小变),找到后将对应顶点加入集合U,直到所有顶点均处理完毕(V-U里没有剩余顶点)
对于第一步,我们采用邻接矩阵的方式进行初始化(不了解的小伙伴可以查阅资料),先初始化每俩个顶点之间的距离为最大值(limit)(根据题目自己定义),后面输入端点和权值信息进行更新,没有更新的说明,两个端点没有相连,默认最大值。
int Great(int cost[][maxn])
{
// jinx
cout << " 请输入你的顶点数目和边的数目 " << endl;
int vnum , anum;
cin >> vnum >> anum;
// 对数组进行初始化
for(int i = 0 ; i < vnum ; i++)
{
for(int j = 0 ; j < vnum ; j++)
{
cost[i][j] = limt;
}
}
// 进行赋值
cout << " 开始输入 " << endl;
for(int i = 0 ; i < anum ; i++)
{
cout << "请输入顶点和权值:" << endl;
int v1 , v2 , w;
cin >> v1 >> v2 >> w;
cost[v1][v2] = w;
cost[v2][v1] = w; // 有向图删除此语句
}
return vnum;
}
第二步 定义两个辅助数组lowcost存储V-U中各顶点到集合U中各顶点的最小距离和closest用来储存依附于该边的在集合U中的顶点注意:当closest的值为-1时表明该顶点已经加入U中
// 初始化(默认从0开始)
for(int i = 0 ; i < n ; i++)
{
lowcost[i] = cost[0][i];
closest[i] = 0;
}
closest[0] = -1;
第三步只要寻找一下U中的顶点到V-U中的顶点最短的距离就行 ,记录最小值及其顶点编号
int minn = limt , loc;
for(int j = 0 ; j < n ; j++)//2.这里j = 0 或 j = 1无所谓,下面有if语句判断
{
if(closest[j] != -1 && minn > lowcost[j])
{
minn = lowcost[j];
loc = j;
}
}
第四步,为什么要进行这一步呢,我们看个图片就懂了,当加入了A节点后,A到C的距离已经小于B到C的距离,所以后面就要更新lowcost中的存储的最小值(图中写的shortedge)
for(int j = 0; j < n ; j++)//3.同上1 和 0无所谓
{
if(closest[j] != -1 && cost[loc][j] < lowcost[j])
{
lowcost[j] = cost[loc][j];
closest[j] = loc;
}
}
总体上实现这个代码就是,对除起点以外的点对进行2,3,4操作,完整代码如下
#include<iostream>
#include<limits>
#include<cstdio>
using namespace std;
const int maxn = 30;
int limt = 4000;
int lowcost[maxn],closest[maxn]; // 记录最小值和邻接顶点
// 初始化
int Great(int cost[][maxn])
{
// jinx
cout << " 请输入你的顶点数目和边的数目 " << endl;
int vnum , anum;
cin >> vnum >> anum;
// 对数组进行初始化
for(int i = 0 ; i < vnum ; i++)
{
for(int j = 0 ; j < vnum ; j++)
{
cost[i][j] = limt;
}
}
// 进行赋值
cout << " 开始输入 " << endl;
for(int i = 0 ; i < anum ; i++)
{
cout << "请输入顶点和权值:" << endl;
int v1 , v2 , w;
cin >> v1 >> v2 >> w;
cost[v1][v2] = w;
cost[v2][v1] = w; // 有向图删除此语句
}
return vnum;
}
void prim(int cost[][maxn],int n)
{
// 初始化(默认从0开始)
for(int i = 0 ; i < n ; i++)
{
lowcost[i] = cost[0][i];
closest[i] = 0;
}
closest[0] = -1;//加入U集合
// 开始执行
for(int i = 1 ; i < n ; i++)// 1.这里 i 不能从0开始,因为0已经用过了,只要找1~n-1就行
{
// 寻找的那个最小的顶点
int minn = limt , loc;
for(int j = 0 ; j < n ; j++)//2.这里j = 0 或 j = 1无所谓,下面有if语句判断
{
if(closest[j] != -1 && minn > lowcost[j])
{
minn = lowcost[j];
loc = j;
}
}
printf("(%d %d):%d \n",closest[loc],loc,lowcost[loc]);//输出
closest[loc] = -1;//加入U集合
// 为下一次做准备
for(int j = 0; j < n ; j++)//3.同上1 和 0无所谓
{
if(closest[j] != -1 && cost[loc][j] < lowcost[j])//将新加入的顶点和原先的距离比较
{
lowcost[j] = cost[loc][j];
closest[j] = loc;
}
}
}
}
int main()
{
int n ; // 存顶点的数目
int cost[maxn][maxn];
n = Great(cost);
prim(cost,n);
return 0;
}
kruskal
可以去我的这篇博客看看(26条消息) 数据结构与算法_拉的很多的博客-优快云博客_kruskal算法并查集
Dijsktra
基本思想
通过Dijkstra计算图G中的最短路径时,需要指定起点s(即从顶点s开始计算)。
此外,引进两个集合S和U。S的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而U则是记录还未求出最短路径的顶点(以及该顶点到起点s的距离)。
初始时,S中只有起点s;U中是除s之外的顶点,并且U中顶点的路径是"起点s到该顶点的路径"。然后,从U中找出路径最短的顶点,并将其加入到S中;接着,更新U中的顶点和顶点对应的路径。 然后,再从U中找出路径最短的顶点,并将其加入到S中;接着,更新U中的顶点和顶点对应的路径。 ... 重复该操作,直到遍历完所有顶点。
步骤
在我看来,其实Dijsktra和Prim算法的思想其实差不多,只是在其中加入了path[i]数组表示从源点到顶点Vi之间的最短路径上Vi的前驱节点和一个vis数组进行标记节点是否使用,因为它是一条路径并不是到每个节点的路径,所以进行单独储存,下面我们可以看出步骤其实也差不多
- 初始化邻接矩阵:初始化起点到各个顶点的距离
- 记录顶点到各个的距离以及它的邻接顶点(一个线段的另一个端点)
- 寻找到剩余顶点的最小的距离
- 对新加入的顶点Vi的最短距离长度加上Vi到该顶点的距离和原先的最短路径长度进行比较
- 重复 3 和 4 步骤,直到没有剩余节点
下面我对第四步进行一下解释说明:举个例子从A点到C点
我可以看到,只有一个节点的时候A到C的距离是10,但是加入了B点之后可以这样走A-B-C,距离为9小于A-C,所以我们可以看到加入新节点后要进行更新最短距离
功能代码如下:
for(int k = 0 ; k < n ; k++)
{
if(!vis[k] && dis[flage] + cost[flage][k] < dis[k])
{
dis[k] = dis[flage] + cost[flage][k];
path[k] = flage;
}
}
现在贴入完整代码:
关于输出的话小编为了让你们看明白在解释一下,看过上面小编发的并查集实现 kruskal算法的应该可以理解,这个while循环相当于并查集里面的find查找函数,不断的查找该节点的父节点,只是这个输出语句将每个搜寻到的父节点进行输出,就是大家可以看到的路径,而并查集是搜寻根节点
#include<iostream>
#include<limits>
#include<cstdio>
using namespace std;
const int maxn = 30;
int limt = 4000;
int vis[maxn] , dis[maxn] , path[maxn];// 是否使用 权值 邻接点
// 初始化
int Great(int cost[][maxn])
{
cout << " 请输入你的顶点数目和边的数目 " << endl;
int vnum , anum;
cin >> vnum >> anum;
// 对数组进行初始化
for(int i = 0 ; i < vnum ; i++)
{
for(int j = 0 ; j < vnum ; j++)
{
cost[i][j] = limt;
}
}
// 进行赋值
cout << " 开始输入 " << endl;
for(int i = 0 ; i < anum ; i++)
{
cout << "请输入顶点和权值:" << endl;
int v1 , v2 , w;
cin >> v1 >> v2 >> w;
cost[v1][v2] = w;
}
return vnum;
}
void Dijsktra(int cost[][maxn],int n , int v)//v指的源点
{
// 初始化
int pre;
for(int i = 0 ; i < n ; i++)
{
dis[i] = cost[v][i];
vis[i] = 0;
path[i] = v;
/*
教材上是这样写的
if(cost[v][i] < M)
path[i] = v;
else
path[i] = -1;
但是我感觉不用,下面会有判断语句,不必要多此一举
*/
}
vis[v] = 1; // 标记已经使用
path[v] = 0;
int flage ;
for(int i = 1 ; i < n ; i++)
{
flage = 0;
int mindis = limt;
// 寻找最小顶点
for(int j = 0 ; j < n ; j++)
{
if(!vis[j] && mindis > dis[j])
{
mindis = dis[j];
flage = j;
}
}
vis[flage] = 1;
// 下面的if也可以省略,不过要贴合教材就算了吧
if(flage)
{
for(int k = 0 ; k < n ; k++)
{
if(!vis[k] && dis[flage] + cost[flage][k] < dis[k])
{
dis[k] = dis[flage] + cost[flage][k];
path[k] = flage;
}
}
}
}
//输出
for(int i = 0 ; i < n ; i++)
{
if(i != v)
{
printf("\n%d->%d:",v,i);
if(vis[i])
{
printf("路径长度为%2d",dis[i]);
pre = i;
printf("路径逆序为:");
while(pre != v)
{
printf("%d,",pre);
pre = path[pre];
}
printf("%d",pre);
}
else
printf("不存在路径\n");
}
}
}
int main()
{
int n;
int cost[maxn][maxn];
n = Great(cost);
Dijsktra(cost,n,0);
return 0;
}
希望能帮助到大家!