SPFA算法【最短路径】

SPFA算法

只要最短路径存在,SPFA算法必定能求出最小值,SPFA对Bellman-Ford算法优化的关键之处在于意识到:只有那些在前一遍松弛中改变了距离估计值的点,才可能引起他们的邻接点的距离估计值的改变。为什么队列为空就不改变了呢?就是因为要到下一点必须经过它的前一个邻接点。。SPFA可以处理负权边。很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。简洁起见,我们约定有向加权图G不存在负权回路,即最短路径一定存在。当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路。

        初始化: dis数组全部赋值为Inf(无穷大,不能是map[s][i]),path数组全部赋值为s(即源点),或者赋值为-1,表示还没有知道前驱,然后dis[s]=0;  表示源点不用求最短路径,或者说最短路就是0。将源点入队;另外记住在整个算法中有顶点入队了要记得标记vis数组,有顶点出队了记得消除那个标记(可能多次入队)。

        核心:读取队头顶点u,并将队头顶点u出队(记得消除标记);将与点u相连的所有点v进行松弛操作,如果能更新估计值(即令d[v]变小),那么就更新,另外,如果点v没有在队列中,那么要将点v入队(记得标记),如果已经在队列中了,那么就不用入队以此循环,直到队空为止就完成了单源最短路的求解。

        判断有无负环:如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图),假设这个节点的入度是k(无向权则就是这个节点的连接的边)如果进入这个队列超过k,说明必然有某个边重复了,即成环;换一种思路:用DFS,假设存在负环a1->a2->…->an->a1。那么当从a1深搜下去时又遇到了a1,那么直接可以判断负环了所有用。当某个节点n次进入队列,则存在负环,此时时间复杂度为O(n*m),n为节点,m为边。

        SPFA算法有两个优化算法 SLF 和 LLL: SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾。 LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。个人觉得LLL优化每次要求平均值,不太好,为了简单,我们可以之间用c++STL里面的优先队列来进行SLF优化。

#include <iostream>
#include <deque>
#include <stack>
#include <vector>
using namespace std;
 
const int MAXN=100;
const int INF=0x7FFFFFFF;
 
struct edge
{
    int to,weight;
};
 
vector<edge> adjmap[MAXN];//邻接表
bool in_queue[MAXN];//顶点是否在队列中
int in_sum[MAXN];//顶点入队次数
int dist[MAXN];//源点到各点的最短路径
int path[MAXN];//存储到达i的前一个顶点
int nodesum;//顶点数
int edgesum;//边数
 
bool SPFA(int source)
{
    deque<int> dq;
    int i,j,x,to;
    for(i=1;i<=nodesum;i++)
    {
        in_sum[i]=0;
        in_queue[i]=false;
        dist[i]=INF;
        path[i]=-1;
    }
    dq.push_back(source);
    in_sum[source]++;
    dist[source]=0;
    in_queue[source]=true;
//初始化完成
 
    while(!dq.empty())
    {
        x=dq.front();
        dq.pop_front();
        in_queue[x]=false;
        for(i=0;i<adjmap[x].size();i++)
        {
            to=adjmap[x][i].to;
            if((dist[x]<INF)&&(dist[to]>dist[x]+adjmap[x][i].weight))
            {
                dist[to]=dist[x]+adjmap[x][i].weight;
                path[to]=x;
                if(!in_queue[to])
                {
                    in_queue[to]=true;
                    in_sum[to]++;
                    if(in_sum[to]==nodesum) return false;
                    if(!dq.empty())
                    {
                        if(dist[to]>dist[dq.front()]) dq.push_back(to);
                        else dq.push_front(to);
                    }else dq.push_back(to);
                }
            }
        }
    }
    return true;
}
 
void Print_Path(int x)
{
    stack<int> s;
    int w=x;
    while(path[w]!=-1)
    {
        s.push(w);
        w=path[w];
    }
    cout<<"顶点1到顶点"<<x<<"的最短路径长度为:"<<dist[x]<<endl;
    cout<<"所经过的路径为:1";
    while(!s.empty())
    {
        cout<<s.top()<<"";
        s.pop();
    }
    cout<<endl;
}
 
int main()
{
    int i,s,e,w;
    edge temp;
    cout<<"输入顶点数和边数:";
    cin>>nodesum>>edgesum;
    for(i=1;i<=nodesum;i++)
    adjmap[i].clear();//清空邻接表
    for(i=1;i<=edgesum;i++)
    {
        cout<<"输入第"<<i<<"条边的起点、终点还有对应的权值:";
        cin>>s>>e>>w;
        temp.to=e;
        temp.weight=w;
        adjmap[s].push_back(temp);
    }
    if(SPFA(1))
    {
        for(i=2;i<=nodesum;i++) Print_Path(i);
    } else cout<<"图中存在负权回路"<<endl;
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值