图算法总结

Dijkstra算法

1)一旦找到从S到顶点P的最短路径,这条最短路径的所有中间节点的最短路径也知道了!
从S到D的最短路径是一定存在的,假设已经找到了这条路径,它是S-a-b-c-d-e-…-D。那么立即确定:
S到a的最短路径就是S-a
S到b的最短路径就是S-a-b
S到c的最短路径就是S-a-b-c

证明:
假设S到a的最短路径不是S-a,而是经过了其它的节点,比如A’,那么很显然S-A’-a的长度小于S-a,因此就会得出从S到D的最短路径是S-A’-a-b-c-d-e-…-D而不是S-a-b-c-d-e-…-D,这与假设是违背的。
同样的,如果S到b的最短路径不是S-a-b,而是经过了其他节点,路径是S-XXX-b,那么从S到D的最短路径是S-XXX-b-c-d-e-…-D而不是S-a-b-c-d-e-…-D,这与假设是违背的。
换句话说,我找离源点S有一点距离的点P,是一步一步的找最短路径,慢慢靠近,稳扎稳打,直到到达P点。

补充:从第一步来说,dis数组里面有具体值(不是无穷)对应的点,都是直接和源点S相连的点,从中选出的那个和S之间权值最低的值S1,我一定直接能确定S-S1就是源点到S1的最短路径。
【 脑子里有的那种经过一段曲折的、权值和更低的路径是完全!不可能存在的!可以知道,S1已经是和S之间权值最低的了,其他任何脑子里“臆想”曲折路径如果避开S1,踩另外任一点,那第一步的权值就已经超过S-S1的权值了!所以肯定不可能! 】

2)
dis[V]中为特定的s->v的最短路径长度,该路径仅经过S中的顶点。
虽然多半不是最终的最短路径长度,但随着一个个顶点被加进S中,dis[v]是在不断变小的(因为我每添加一个节点,都会检查一下所有的新走法有没有带来更小的到达U中节点的路径,有的话立即更新),最后就变成真正的最短路径,从而V点被收录进S中。

3)按照路径长度递增(非递减)的顺序找出从源点到各个顶点的最短路径,所以真正的最短路径必须只经过S中的顶点。
证明:假如从S到一个未收录的V的最短路径上有S以外的点,比如说是W,走S-…-W-V的路线。
根据1)的推论立刻得到 S-…-W就是源点到W点的最短距离,这个距离一定比S到点V要小,凡是最小路径距离比V小的点应该都被集合S收进去了,所以这样就有矛盾。所以点V的最短路径一定只经过S中顶点。

4)每次从未收录的顶点中选出一个dis最小的收录(贪心)
因为我是按照路径长度递增(非递减)的顺序找源点到各个顶点的最短路径,我现在手头能参考的就是最新的dis数组中到各个顶点的最小长度,从里面挑出一个最小的,收录进来就行了。
但要小心的一点是:
当我把新节点v进入S中,可能影响另外一个w(不在S中)的dis值:
(1)如果添加v以后,w对应的dis变小的话,那么v一定在从S到W的路径上。

注:如果w现在的dis值路径里不包括v的话,新添加V就对它没影响,

这里写图片描述
(2)v不仅一定在从S到W的路径上,而且v和W之间必定存在一条直接的边。

为什么不能是S-…-V-X-W呢?(X在S中)还是上面的道理,路径是按照递增的顺序生成的,如果S-…-V-X-W是到W的最短路径,X在V后面,那么X和源点之间的最短距离比V到源点的最短距离要大。而这时不可能出现的!因为V才刚刚添加进来呢,它才是到源点距离最大的那个点,结果推出来却是X早就在S里面了,最短路径还比V大,存在矛盾。

所以,V新增加进S中,它能影响的就是它的邻接点。
所以要马上检查更新:
dis[w]=min{dis[w],dis[v]+w[v][w]} 其中w[v][w]表示v和w的权重。

下面给一个示例:
这里写图片描述
这里写图片描述

总结:实际上Dijkstra算法是一个排序过程。就本例来说,是根据A到图其余点的最短路径长度进行排序。路径越短,越先被找到。要找A到F的最短路径,我们依次找到了:
A->C 的最短路径
A->B的最短路径
A->D的最短路径
A-E的最短路径
A-F的最短路径
Dijkstra算法运行的一个附加效果是得到了另一个信息,A到C的路径最短,接下来依次是A到B、A到D、A到E、A到F。

/*
伪代码实现
*/
void Dijkstra(Vertex s)
{
    while(1)
    {

        V = 未收录顶点中dist最小者;
        if(这样的V不存在)
        break;               //这表示顶点都已经被收录了

        collected[V]= true;   //把V收录到集合中去
        for(V的每个邻接点 w)

            if (collected[W] == false){    //对于还没收录的V的邻接点W来说

                if (dis[V]+ E<V,W> < dist[W]){  //如果加入V以后,;路径中有V的到达W路径长度比之前的dist[W]要短

                    dis[W]=dis[V]+ E<V,W;  //立即更新
                    path[W]=V;   //把V记录在W的路径上
            }                   
    }

}

/*不能解决有负边的情况*/

注:
dis[]数组的值,如果当前节点和未收录的某个节点之间没有直接相连,其dis[]初始值只能被定义成正无穷,而不能是-1。原因在于我每次加入一个新的节点V,都会检查dis[V]+w

Floyd算法

每一条最短路不是一次成型的。使用邻接矩阵来记稠密图。
Floyd算法定义了一个二维矩阵,这个矩阵的第i、j个元素定义了这个路径的最小长度:从i到j。(但是只经过一部分顶点,只经过编号小于等于K的顶点)
D的初始值定义为邻接矩阵。对角元素全部是0,如果ij之间有相邻边,就定义为边的权重;否则,定义为正无穷。
这里写图片描述

void Floyd()
{
    for(i=0;i<N;i++)
        for(j=0;j<N;j++){
            D[i][j]=G[i][j];     //D[i][j]被初始化为邻接矩阵
            path[i][j]=-1;
        }

     for(k=0;k<N;k++)
        for(i=0;i<N;i++)
            for(j=0;j<N;j++){
                if(D[i][k]+D[k][j]<D[i][j]){

                D[i][j]=D[i][k]+D[k][j];
                path[i][j]=k;

            }   

}

注:打印从i到j的最短路径是一个递归问题。
从i到j的最短路径=从i到k的最短路径+K+从k到j的最短路径这三段组成。
所以我们可以
1)递归地打印从i到k的路径
2)打印path[i][j]=k
3)递归地打印从k到j的路径

Prim算法

从某个图中寻找最小生成树(minimizing spanning tree)

从图中找到一个最小生成树(minimizing spanning tree):
1)是一课树:
▪️连通、没有回路
▪️|V|个顶点一定有|V|-1条边
2)是生成树:
▪️连通、没有回路
▪️包含图中的全部顶点
▪️连通、没有回路
▪️|V|-1条边都在图里
3)边的权重最小

贪心算法
何为贪心:每一步都要眼下最好的
何为最好:权重最小
约束:
1.只能用图里存在的边
2.树的边要正好是|V|-1条
3.不能有回路

/*
Prim算法
这里dist定义为:一个顶点P到当前最小生成树的距离。
即顶点跟MST中已经收录进去的点的点间距离最小的距离。
*/
void Prim()
{
    MST={s};   //先选中一个源点
    while(1){
    V=未收录的节点中dist最小者;
    if(这样的V不存在)
        break;
    将V收录进MST:dist[v]=0;
    for(V的每个邻接点W)
        if(dist[W]!=0)
            if(E<v,w> < dist[W]){

                dist[W]=E<v,w>;
                parent[W]=V;

            }
    }


    if(MST中收到的顶点不到|V|个)
    Error("生成树不存在");

}

Kruskal算法

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值