最佳路径算法总结

本文深入探讨了Warshall算法、Floyed-Warshall算法、Dijkstra算法、Bellman-Ford算法及SPFA算法的核心原理及应用场景,详细阐述了它们在解决最短路径问题、穿闭包问题等方面的优势与限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

warshall算法:

warshall算法是求二元关系穿闭包的算法.设关系R的关系图为G,设图G的所有顶点为v1,v2,…,vn,则t(R)的关系图可用该方法得到:若G中任意两顶点vi和vj之间有一条路径且没有vi到vj的弧,则在图G中增加一条从vi到vj的弧,将这样改造后的图记为G’,则G’即为t(R)的关系图。G’的邻接矩阵A应满足:若图G中存在从vi到vj路径,即vi与vj连通,则A[i,j]=1,否则A[i,j]=0。

这样,求t(R)的问题就变为求图G中每一对顶点间是否连通的问题。

定义一个n阶方阵序列A(0),A(1),A(2),…,A(n),每个方阵中的元素值只能取0或1。A(m)[i,j]=1表示存在从vi到vj且中间顶点序号不大于m的路径(m=1..n),A(m)[i,j]=0表示不存在这样的路径。而A(0)[i,j]=1表示存在从vi到vj的弧,A(0)[i,j]=0表示不存在从vi到vj的弧。

这样,A(n)[i,j]=1表示vi与vj连通,A(n)[i,j]=0表示vi与vj不连通。故A(n)即为t(R)的关系矩阵,n表示每个中间节点的标号都不大于n,比如A(1)表示只能用第一个节点作为中间节点,A(n)表示所有节点都可以作为中间节点。

下面是代码:

void warshall(int a[N][N])
{
    
    for (int k = 0;k < N;k++)                
        for (int i=0;i < N;i++) 
            for (int j = 0;j < N;j++) 
                a[i][j] |= a[i][k] & a[k][j];
}

Floyed-warshall算法:

该算法使用来求任意两点之间最短距离的算法,跟warshall算法差不多,该算法要求求最短路径是不允许出现负权回路,求最长路径时,不允许出现正权回路。

void floyed_warshall(int a[N][N])
{
    
    for (int k = 0;k < N;k++)                
        for (int i=0;i < N;i++) 
            for (int j = 0;j < N;j++) 
                a[i][j]=max(a[i][j],a[i][k]+a[k][j]);
}

Dijkstra算法:

该算法求有向加权图最短路径问题,条件是所有边权值非负。

算法具体步骤 

  (1)初始时,S只包含源点,即S=,v的距离为0。U包含除v外的其他顶点,U中顶点u距离为边上的权(若v与u有边)或 )(若u不是v的出边邻接点)。

  (2)从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。

  (3)以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u(u U)的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。

  (4)重复步骤(2)和(3)直到所有顶点都包含在S中。

代码如下:

void Dijkstra(int n, int v, int *dist, int *prev, int c[maxnum][maxnum])
{
    bool s[maxnum];    // 判断是否已存入该点到S集合中
    for(int i=1; i<=n; ++i)
    {
        dist[i] = c[v][i];
        s[i] = 0;     // 初始都未用过该点
        if(dist[i] == maxint)
            prev[i] = 0;
        else
            prev[i] = v;
    }
    dist[v] = 0;
    s[v] = 1;

    // 依次将未放入S集合的结点中,取dist[]最小值的结点,放入结合S中
    // 一旦S包含了所有V中顶点,dist就记录了从源点到所有其他顶点之间的最短路径长度
    for(int i=2; i<=n; ++i)
    {
        int tmp = maxint;
        int u = v;
        // 找出当前未使用的点j的dist[j]最小值
        for(int j=1; j<=n; ++j)
            if((!s[j]) && dist[j]<tmp)
            {
                u = j;              // u保存当前邻接点中距离最小的点的号码
                tmp = dist[j];
            }
        s[u] = 1;    // 表示u点已存入S集合中

        // 更新dist
        for(int j=1; j<=n; ++j)
            if((!s[j]) && c[u][j]<maxint)
            {
                int newdist = dist[u] + c[u][j];
                if(newdist < dist[j])
                {
                    dist[j] = newdist;
                    prev[j] = u;
                }
            }
    }
}

Bellman-Ford算法:

Bellman-ford算法是求含负权单源最短路径算法,效率很低,但代码很容易写。即进行不停地松弛,每次松弛把每条边都更新一下,若n-1次松弛后还能更新,则说明图中有负环,无法得出结果,否则就成功完成。Bellman-ford算法有一个小优化:每次松弛先设一个旗帜flag,初值为FALSE,若有边更新则赋值为TRUE,最终如果还是FALSE则直接成功退出。Bellman-ford算法浪费了许多时间做无必要的松弛,所以SPFA算法用队列进行了优化,效果十分显著,高效难以想象。SPFA还有SLFLLL,滚动数组等优化。

<Bellman-Ford算法>

  Dijkstra算法中不允许边的权是负权,如果遇到负权,则可以采用Bellman-Ford算法。

  Bellman-Ford算法能在更普遍的情况下(存在负权边)解决单源点最短路径问题。对于给定的带权(有向或无向)图G=V,E),其源点为s,加权函数w是 边集E的映射。对G运行Bellman-Ford算法的结果是一个布尔值,表明图中是否存在着一个从源点s可达的负权回路。若不存在这样的回路,算法将给出从源点s到 图G的任意顶点v的最短路径d[v]

  适用条件&范围

  1.单源最短路径(从源点s到其它所有顶点v);

  2.有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图);

  3.边权可正可负(如有负权回路输出错误提示);

  4.差分约束系统;

  Bellman-Ford算法描述:

  1,.初始化:将除源点外的所有顶点的最短距离估计值d[v] ←+∞, d[s] ←0;

  2.迭代求解:反复对边集E中的每条边进行松弛操作,使得顶点集V中的每个顶点v的最短距离估计值逐步逼近其最短距离;(运行|v|-1次)

  3.检验负权回路:判断边集E中的每一条边的两个端点是否收敛。如果存在未收敛的顶点,则算法返回false,表明问题无解;否则算法返回true,并且从源点可达的顶点v的最短距离保存在d[v]

  描述性证明:

  首先指出,图的任意一条最短路径既不能包含负权回路,也不会包含正权回路,因此它最多包含|v|-1条边。

  其次,从源点s可达的所有顶点如果 存在最短路径,则这些最短路径构成一个以s为根的最短路径树。Bellman-Ford算法的迭代松弛操作,实际上就是按顶点距离s的层次,逐层生成这棵最短路径树的过程。

  在对每条边进行1遍松弛的时候,生成了从s出发,层次至多为1的那些树枝。也就是说,找到了与s至多有1条边相联的那些顶点的最短路径;对每条边进行第2遍松弛的时候,生成了第2层次的树枝,就是说找到了经过2条边相连的那些顶点的最短路径……。因为最短路径最多只包含|v|-1条边,所以,只需要循环|v|-1次。

  每实施一次松弛操作,最短路径树上就会有一层顶点达到其最短距离,此后这层顶点的最短距离值就会一直保持不变,不再受后续松弛操作的影响。(但是,每次还要判断松弛,这里浪费了大量的时间,怎么优化?单纯的优化是否可行?)

  如果没有负权回路,由于最短路径树的高度最多只能是|v|-1,所以最多经过|v|-1遍松弛操作后,所有从s可达的顶点必将求出最短距离。如果d[v]仍保持+∞,则表明从sv不可达。

  如果有负权回路,那么第|v|-1遍松弛操作仍然会成功,这时,负权回路上的顶点不会收敛。

代码如下:

typedef struct edge
{
    int v;//起点
    int u;//终点
    int w;
} edge;
edge edges[200004];
int d[1004];
int maxData = 1000000000; //此处要特别注意,bellman-ford算法中不要使用0x7fffffff
int edgenum;
bool BellmanFord(int s)
{
    int i,j;
    bool flag = false;
    for(i=1; i<n+1; ++i)
    {
        d[i] = maxData;
          //其余点的距离设置为无穷
    }
    d[s]=0;
        //源点的距离设置为0
    for(i=1; i<n; ++i)
    {
        flag = false; //优化:如果某次迭代中没有任何一个d值改变,尽可以立刻退出迭代而不需要把所有的n-1次迭代都做完
        for(j=0; j<edgenum; ++j)
        {
            if(d[edges[j].u]>d[edges[j].v]+edges[j].w)
            {
                flag =true;
                d[edges[j].u]=d[edges[j].v]+edges[j].w;
            }
        }
        if(!flag)
            break;
    }
//判断是否存在负权回路    for(i=0; i<edgenum; ++i)
    {
        if(d[edges[i].v]<maxData && d[edges[i].u]>d[edges[i].v]+edges[i].w)
        {
            return false;
        }
    }
    return true;
}
主函数中:
edgenum=0;
for(i=0; i<m; ++i)
{
    cin>>start>>end>>w;
    edges[edgenum].v = start;
    edges[edgenum].u = end;
    edges[edgenum].w = w;
    edgenum++;
}


SPFA(shortest path faster algorithm)算法:

我们用数组d记录每个结点的最短路径估计值,而且用邻接表来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。

设Dist代表S到I点的当前最短距离,Fa代表S到I的当前最短路径中I点之前的一个点的编号。开始时Dist全部为+∞,只有Dist[S]=0,Fa全部为0。

维护一个队列,里面存放所有需要进行迭代的点。初始时队列中只有一个点S。用一个布尔数组记录每个点是否处在队列中。

每次迭代,取出队头的点v,依次枚举从v出发的边v->u,设边的长度为len,判断Dist[v]+len是否小于Dist[u],若小于则改进Dist[u],将Fa[u]记为v,并且由于S到u的最短距离变小了,有可能u可以改进其它的点,所以若u不在队列中,就将它放入队尾。这样一直迭代下去直到队列变空,也就是S到所有的最短距离都确定下来,结束算法。若一个点入队次数超过n,则有负权环。

下面是几段代码:

1.

void spfa(int s,int m)
{
  int i,k,ts=0,te=1;
  Q[ts] = s;
  dis[s] = 0;
  while(ts<te)
  {
    k = Q[ts];
    visit[k]=false; //修改后增加的 2010.8.18 01:27
    for(i=1;i<=m;i++)
   {
       if(g[k][i]>0 && dis[i] - g[k][i] > dis[k])
       {
          dis[i] = dis[k] + g[k][i];
          if(!visit[i])
          {
             Q[te++] = i;
             visit[i] = true;
           }
       }
    }
    ts++;
  }
}

2.

#include<iostream>
using namespace std;
#include<queue>
#define MAXN 10
#define INF 100000000
struct node
{
    int to;
    int w;
    node * next;    
};

node * List[MAXN];
int path[MAXN];
int dist[MAXN];
int vis[MAXN];
int n; 

void SPFA(int v0)
{
        int u;
        for(int i=0;i<n;i++)
        {
                dist[i]=INF;
                path[i]=v0;
                vis[i]=0;    
        }
        dist[v0]=0;
        path[v0]=v0;
        vis[v0]=1;
        node *temp;
        queue<int> q;
        q.push(v0);
        
        while(!q.empty())
        {
            u=q.front();
            q.pop();
            temp=List[u];
            while(temp!=NULL)
            {
                int v=temp->to;
                if(dist[v]>dist[u]+temp->w)
                {
                        dist[v]=dist[u]+temp->w;
                        path[v]=u;
                        if(!vis[v])
                        {
                            q.push(v);
                            vis[v]++;    
                        }    
                }
              temp=temp->next;    
            }    
        }            
}
void show(int i)
{
    if(i==0)
    return;
    show(path[i]);
    printf("%d->",i);    
}
int main()
{
        scanf("%d",&n);
    int u,v,w;
    node* temp;
    memset(List ,0,sizeof(List));
        while(1)
        {
            temp=new node;
            scanf("%d%d%d",&u,&v,&w);
            if(u==-1&&v==-1&&w==-1)
            break;
            temp->next=NULL;
            temp->to=v;
            temp->w=w;
            if(List[u]==NULL) List[u]=temp;
            else 
            {
                    temp->next=List[u];
                    List[u]=temp;    
            }
        }
        SPFA(0);
        for(int i=1;i<n;i++)
          { 
          printf("%d: ",dist[i]);
          printf("%d->",0);    
          show(path[i]); 
          printf("%d\n",i);    
        } 
    system("pause");    
    return 0;    
}

/*

7
0 1 6
0 2 5
0 3 5
1 4 -1
2 1 -2
2 4 1
3 2 -2
3 5 -1
4 6 3
5 6 3
-1 -1 -1
*/

3.邻接表实现

#include <iostream>
#include <queue>
using namespace std;

const long MAXN=10000;
const long lmax=0x7FFFFFFF;

typedef struct  
{
    long v;
    long next;
    long cost;
}Edge;


Edge e[MAXN];
long p[MAXN];
long Dis[MAXN];
bool vist[MAXN];

queue<long> q;

long m,n;//点,边
void init()
{
    long i;
    long eid=0;

    memset(vist,0,sizeof(vist));
    memset(p,-1,sizeof(p));
    fill(Dis,Dis+MAXN,lmax);

    while (!q.empty())
    {
        q.pop();
    }

    for (i=0;i<n;++i)
    {
        long from,to,cost;
        scanf("%ld %ld %ld",&from,&to,&cost);

        e[eid].next=p[from];
        e[eid].v=to;
        e[eid].cost=cost;
        p[from]=eid++;

        //以下适用于无向图
        swap(from,to);
        
        e[eid].next=p[from];
        e[eid].v=to;
        e[eid].cost=cost;
        p[from]=eid++;

    }
}

void print(long End)
{
    //若为lmax 则不可达
    printf("%ld\n",Dis[End]);    
}

void SPF()
{

    init();

    long Start,End;
    scanf("%ld %ld",&Start,&End);
    Dis[Start]=0;
    vist[Start]=true;
    q.push(Start);

    while (!q.empty())
    {
        long t=q.front();
        q.pop();
        vist[t]=false;
        long j;
        for (j=p[t];j!=-1;j=e[j].next)
        {
            long w=e[j].cost;
            if (w+Dis[t]<Dis[e[j].v])
            {
                Dis[e[j].v]=w+Dis[t];
                if (!vist[e[j].v])
                {
                    vist[e[j].v]=true;
                    q.push(e[j].v);
                }
            }
        }
    }

    print(End);

}

int main()
{
    while (scanf("%ld %ld",&m,&n)!=EOF)
    {
        SPF();
    }
    return 0;
}

总结基本就是这样,先掌握,在以后的实践中继续深化!



http://www.cnblogs.com/mycapple/archive/2012/08/12/2634227.html



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值