此博文不具体给出其算法的代码,只对其中算法进行分并且给予证明
PS:这些算法我不用证明都是它是正确的(上世纪的数学家看着这些都不用证明,为啥,很简单的),但是我坚持重新证明一遍实际是为了加深印象,并且理解其中的道理和思想,这样在以后的运用中才能灵活运用,当然证明这些算法也
算法一:Flord 算法,也是传说中的只用五行就可以解决的多源最短路径问题
采用邻接矩阵来储存图,时间复杂度为O(n^3),能解决含正权,负权的最短路径,不能解决含有负环的最短路径(负环也没有最短路径)
for(k=1;k<=n;k++) for(i=1;i<=n;i++) for(j=1;j<=n;j++) if(e[i][j]>e[i][k]+e[k][j]) e[i][j]=e[i][k]+e[k][j];
当k=1时进行一个嵌套的for循环求出来的是任意两点之间只允许经过点1的最短路径,这个都可以理解吧。
( ̄▽ ̄)"
当k=2时,经过一个if else的判断最终求出来的是任意两个顶点之间只允许经过1 2号顶点的最短路径。
这时候可能有人说在进行 if else 之后判断的是i->j通过2号顶点路径后路径能否缩小,那么如果i->j还能通过1号顶点再次缩小路径,那还能体现出来吗?
好的下面咱们来证明
假设咱们判断过之后i->j之间通过2号顶点可以缩小路径,此时i与j的路径为i->2->j,假设还可以通过1号顶点缩小路径(也就是上面所说的情况)
此时1号顶点肯定在i->j之间,也就是i->2或2->j之间 ,而咱们在第一次for循环之后的意义上面已经说过了,即任意两点可以经过1号顶点后更新的最短路径,那么回过来即i->2之间或者2->j之间若可以经过1号顶点在缩短路径,那么铁定在第一次循环后就已经在i->2或2->j之间添加1号顶点了,所以上面的担心是多余的(●'◡'●)
当k=2的循环结束后,得到的意义便是任意两个顶点只允许经过1 2号顶点的最短路径
当k=n时 循环结束后便是任意两点之间允许经过1~n个顶点后的最短路径,(如果有疑问,那么再按着刚刚在2号顶点的证明过程比较一下)
算法二:dijkstra算法 (求单源最短路径,只允许正权)
这个是比较常用的算法,其常用的储存图的方式有邻接矩阵,邻接表两种。采用邻接矩阵的时间复杂度为O(n^2)
在此用邻接矩阵来举例子
先说一下算法原理,初始化就不说了吧(任意两点之间之间路径为INF,自己与自己的路径为0)随后输入图。
用一个dis[]数组记录源点到其他点的直接路径。假设源点号为S,并且用数组book[]来表示s->其他点的最短路径是否确定,确定的点令其值为1(book[s]=1)。
然后从dis[]数组找s->v最小的值,假设为这个顶点为u,那么s->u的最短路径肯定就确定了。为啥 ?因为s->u若再经过其他点所成的路径一定比s->u的路径的大。
已经确定s->u是最短路径后,松弛u点到其他点的路径,令book[u]=1。(这点是关键)
所以算法分三个步骤
1.然后再从dis[]从最短路径未确定的路径找出的最小值,令其为确定的最短路径(假设这个顶点是u),令book[u]=1;(证明下面在说)
2.从u点松弛
3.重复以上1. 2点直至所有点的最短路径确定。
也许有很多人对第1步有些不解。为什么从中找出的符合条件的u的最短路径一定确定了呢?别急下面来证明
看这个图
第一次 找出s->a最小,那么令此时s->a的路径为最短路径,令book[2]=1,(这个都没疑问吧)...... 然后松弛a->b得到mid=dis[a]+map[a][b]<dis[b] 且松弛之后s->b的路径变小了
第二次,再次按步骤1找出最小的为dis[b],此时的dis[b]为s->b的最短路径。为啥?假设此时dis[b]的值不是s->b的最短路径,那么要想找s->b的更小路径至少中转至一个顶点v,且s->v的距离满足dis[v]<dis[b](不然路径会更大),然后经v中转至b,但是因为dsi[v]<dis[b]表明v已经确定为最小路径了,前面咱们说过,确定为最短路径的点一定要经过此点到其他顶点的松弛,所以你找的所有的s->b的路径一定>=dis[b],
也就是下面这个原理
u为此时经过步骤1确定的点,假设s->u的距离不是最短路径,即存在另一条路径s->v->u(你也可以假设s->v之间还有许多点,但是我们要确保v->u是直接的,即不经过任何一个中转点)使得路径长度mid<dis[u],
又因为假设的路径中v是在s->u之间,所以v的最短路径已经确定且v经过松弛,即v到任何点的都经过松弛(包括点u),所以此时我们找的mid是一定>=dis[u]的,即假设不成立。
所以你就默认他们是最短路径这一事实吧( ̄▽ ̄)"
代码:
附上dijkstr算法
/* dijkstr 算法 二步: 1.初始化 2.每次找出dis最小值则为确定对短路 注意:minn=INF 或者找到答案这一提前跳出 */ #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<string> #include<iostream> #include<cstdlib> #define N 1010 #define INF 0x3f3f3f3f #define MOD 1000000007 #define WC 1e-6 typedef long long LL; using namespace std; int book[N],dis[N],map[N][N]; void dijkstr(int n) { int t=n,minn,u,to; memset(book,0,sizeof(book)); for(int i=1;i<=n;i++) dis[i]=INF; dis[1]=0; while(t--) { minn=INF; for(int i=1;i<=n;i++) { if(!book[i]&&dis[i]<minn) { u=i; minn=dis[i]; } } book[u]=1; for(int i=1;i<=n;i++)//松弛 { if(!book[i]) { to=dis[u]+map[u][i]; if(to<dis[i]) dis[i]=to; } } } } int main() { //cin map 就OK }
有时间再补充另两个算法的证明
附上SPFA代码
/* SPFA 三步 1.初始信息 将初始点加入队列 2.每次从队列的第一个点进行松弛,可以更新另一个点则将其加入队列(已经加入队列则不需要再次加入) 2.队列为空结束 */ #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<string> #include<iostream> #include<cstdlib> #include<queue> #define N 1010 #define INF 0x3f3f3f3f #define WC 1e-6 typedef long long LL; using namespace std; int book[N],dis[N],map[N][N];//存信息 queue<int>mmp; void SPFA(int n)//n得出范围 输入map就OK { int to; memset(book,0,sizeof(book)); for(int i=1;i<=n;i++) dis[i]=INF; dis[1]=0; while(!mmp.empty()) mmp.pop(); mmp.push(1); while(!mmp.empty()) { int u=mmp.front();//出队伍 mmp.pop(); book[u]=0; for(int i=1;i<=n;i++)//松弛 { to=dis[u]+map[u][i]; if(to<dis[i]) { dis[i]=to; if(!book[i]) { mmp.push(i); book[i]=1; } } } } } int main() { //cin map }
附上bellman算法
/* bellman算法 可以判断负环 1.初始化dis (struct)edge 2.遍历所有边 2.更新成功则进行下一次 */ #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<string> #include<iostream> #include<cstdlib> #define N 4010 #define INF 0x3f3f3f3f #define WC 1e-6 typedef long long LL; using namespace std; int dis[N]; struct node{ int u,v,w; }e[N]; void bellman(int n,int cnt) { int to,t,flag; for(int i=2;i<=n;i++) dis[i]=INF; dis[1]=0; t=n-1; while(t--) { flag=0; for(int i=0;i<cnt;i++) { to=dis[e[i].u]+e[i].w; if(to<dis[e[i].v]) { dis[e[i].v]=to; flag=1; } } if(!flag) break; } // if(flag)//最后一次用于判断负环 } int main() { //cin edge }