SPFA算法【最短路径】

来源:互联网 发布:童谣的诡计知乎 编辑:程序博客网 时间:2024/05/12 11:54

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;}


0 0
原创粉丝点击