#ACM&算法
不知是不是因为自己的抽象学习能力还是不够强,之前在翻阅别人博客学习这两个算法的时候,整个人一直处于懵逼状态。
分析了一下,发现之前存在的博客教程,大多趋向于方法讲解,学完以后有知其然不知其所以然的感觉。另一部分博客则偏向数学证明,语言严谨但理解起来确实要费一番功夫。所以打算尝试一下在下文里以解决一个问题的方式讲解这两个算法。
在敲算法之前先阐明一下这两个算法所能解决的问题,先从Dijkstra开始。
举个栗子
我们现在拿到了一张无向图,如下:
这是一个由7个节点和10条边组成的无向图。每条路上的数字代表这条路的长度。 现在任选其中两个点,我们需要找到一条路把它们连起来,并使这条路尽可能的短。
根据动态规划的思路,我们把这个问题化解成一个较小的问题去解决。我们将挑选两个紧挨着(有边直接相连)的点,A和B。
显而易见,如果从A到B有一条路能联通的话,那这条路只有两种可能: 1,这条路只有一条边,直接连接A和B。 2,这条路有多条边,从A出发,经过其他点中转,到达B。
那我们分别来讨论。 这两种方法的共同点就是,都要从A点出发。而从A点出发的路是有限的。那也就是说,如果从A出发直达B的这条路是从A出发所有路中最短的一条,那么这条路一定是AB间的
最短路。 证明也很简单,因为
所有路的长度都是正数,所以如果有另一条路从A出发绕过某些点到达B的话,那它一定会在从A出发的所有路的长度的基础上再加上一些数(至少为0)。也就绝对不可能小于AB直接连接的长度。 如下:
我们先把起点标记为0,意为起点到自己的距离为0。因为这一定是A到A的最短路,所以把所有和A连接的点的值更新为A点的值加上A到该点的路程距离。
接着,根据刚才的论述,我们确定B的值已经更新为A到B的最短路,故,我们对B点也进行上述操作。得到下图:
在本图中,已经更新过值,但还未确定为最短路的节点有C/E/F,他们中值最小的是E,那我们可以确定E现在的值就是从A到E的最短路。 原因同上,我们已经把A和B所有能走的路尝试走过了,如果从C和F还有另一条路能绕过某些点到达E的话,那一定比E现在的值要大,因为C和F的值已经比E大了,加上一个大于等于零的数只会更大。
重复进行上述操作,我们最终会得到如下的图:
这时候,我们会发现,图上未被确定为最短路的点里,值最小的就是目标点D了,那我们就可以得出,从A到D的最短路是现在D的值,即12。
而Prim算法所解决的问题与上面的略有不同。
我们现在依然是面对这张无向图:
现在,我们需要从这些路径中挑选出几条,使得所有点都可以通过这些路相互连通,并要求这些路的总长度尽量短。
先从思路讲起,
既然是求全联通的最短路,那么只要是已经连在一起的点,我们都可以把它们看成同一个点,而它们直接连接的所有的边,都是这个点直接连接的边。
用例子解释一下:假如我们已经挑选了两条路连接了A、B、C三个点:
我们通过红色的路连接了A、B、C三点,图中所有标为黄色的路就是这个连通体外部所有直接连接的路。那么,我们可以确定,为了使E点接入连通体,我们最少的花费一定是通过C-E这条路。 原因同上,如果通过其它路去连接的话,那这条路一定比现有的路还要长一些,所以当前标为黄色的路中最短的一条一定是下一步选择的最优解。
反复上述操作,我们能得到下图:
即为该无向图的
最小生成树。
附Dijkstra代码,题目是HDOJ 1874:
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
#define MAX 1e9;
int city, road;
long vis[205];
struct point{
long dis=1e9;
int conect[205][2];
long i=0;
}node[205];
void dij(long be, long ed)
{
if (vis[be]==1)//如果已经踩过了 就不管了 【退出】
{
return;
}
vis[be] = 1; //如果没踩过 就标记成 踩过了
for (size_t i = 0; i < node[be].i; i++) // 对于所有的 联通 的 点 更新 注意这个min函数
{
node[node[be].conect[i][0]].dis = min(node[node[be].conect[i][0]].dis, node[be].dis + node[be].conect[i][1]);
}
long minn=1e9;//为 寻找 下一个根节点做准备
int targ=1e9;
for (size_t i = 0; i < city; i++)
{
if (minn>node[i].dis&&vis[i]==0)//找到 未踩过的 dis值最小的 点 作为根节点
{
minn = node[i].dis;
targ = i;
}
}
if (targ==1e9)//如果没有 【退出】
{
return;
}
dij(targ, ed);//否则 对这个根节点 重复上述操作。
}
int main()
{
while (cin >> city >> road)
{
memset(vis, 0, sizeof(vis));
for (size_t i = 0; i < city; i++)
{
node[i].dis = 1e9;
node[i].i = 0;
}
while (road--)
{
long a, b,c;
cin >> a >> b>>c;
node[a].conect[node[a].i][0] = b;
node[a].conect[node[a].i][1] = c;
node[a].i++;
node[b].conect[node[b].i][0] = a;
node[b].conect[node[b].i][1] = c;
node[b].i++;
}
long be, ed;
cin >> be >> ed;
node[be].dis = 0;
dij(be, ed);
if (node[ed].dis==1e9)
{
cout << "-1\n";
}
else
{
cout << node[ed].dis << "\n";
}
}
}
Prim代码 题目是HDOJ 1863:
/*Author: Res*/
/*Date: 16/2/16*/
/*使用邻接矩阵存图,点数查询,没有使用并查集*/
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
using namespace std;
int con[1005][1005];//邻接矩阵
int confar[1005];//已连通的图到未联通点的距离,若距离为0为无法连通,距离为1e9为已联通
long long ans,node;//node是已联通的点的数量
int main()
{
int be, ed,lenth,n,m,target;
while (cin >> m >> n)
{
memset(con, 0, sizeof(con));//初始化
memset(confar, 0, sizeof(confar));
ans = 0;
node = 1;
target = 0;//是否有某个点无法连通
if (m==0)
{
return 0;
}
for (size_t i = 0; i < m; i++)
{
cin >> be >> ed >> lenth;
if (con[be][ed])//存入邻接矩阵,因为两点间有多条路,只存最短的一条。
{
con[be][ed] = min(con[be][ed], lenth);
}
else
{
con[be][ed] = lenth;
}
if (con[ed][be])
{
con[ed][be] = min(con[ed][be], lenth);
}
else
{
con[ed][be] = lenth;
}
}
be = 1;
confar[1] = 1e9;
while (node != n)
{
for (size_t i = 1; i <= n; i++)
{
if (con[be][i]&&confar[i]!=1e9)//在该点的邻接表中搜索更近的路程,存入距离表confar中
{
if (confar[i])
{
confar[i] = min(confar[i], con[be][i]);
}
else
{
confar[i] = con[be][i];
}
}
}
int find = 1e9;
for (size_t i = 1; i <= n; i++)
{
if (find>confar[i]&&confar[i]!=0)//在距离表中搜索最近的未使用的路
{
find = confar[i];
be = i;
}
}
if (find==1e9)
{
target = 1;
}
ans += find;//累加
confar[be] = 1e9;
node++;
}
if (target)
{
cout << "?\n";
}
else
{
cout << ans << "\n";
}
}
}
1,无向图:由若干点与边构成,每条边都连接着两个点,且每条边都可以双向联通的图。
2,上述两个算法,当遇到待选取的路距离相同时,可任选一个,对结果没有影响。