【图论】最短路径算法大全

来源:互联网 发布:插画师卤猫 知乎 编辑:程序博客网 时间:2024/05/08 16:59

Dijkstra算法

特点:单源最短路径,无负权边。

算法思路:设置一个dis[i]数组记录i到源点的最小距离。一开始只有源点的dis为0,其他全为无限大。从源点出发,更新周围与它相连的点。进行顶点次数的循环,每次寻找与源点距离最近的点,标记为已访问,并从该点出发更新相连的点的状态。根据三角不等式dis[i]+map[i][j]跟dis[j]比较,如果小于,说明经过i再到j比原来源点到j的最短距离还要短,则更新之。最后能得到源点到任意点的最短路径。

代码如下:

 

//djikstra算法 void djikstra(){int i,j,k;for(i=1;i<=n;i++){dis[i]=inf;//一开始所有点到源点的距离为无限大 vis[i]=0;}dis[1]=0;//将源点到自身距离设置为0for(i=1;i<=n;i++){int min=inf;for(j=1;j<=n;j++){if(!vis[j]&&dis[j]<min)// 遍历所有点,如果点没有被访问过,并且dis[j]被更新过,则保留dis[j]最小的点的下标 {min=dis[j];k=j;}}if(minc==inf)break;vis[k]=1;//将找出的dis[j]最小的点标记为已访问,这样首先一开始源点会被标记。从该点开始,进行下面的操作。for(j=1;j<=n;j++){if(map[k][j]!=inf&&!vis[j])//如果k点可以到达j点,即存在(k,j)边,则更新这条边 {if(dis[k]+map[k][j]<dis[j])//如果经过k点可以到达j点,则判断源点到j的距离是否大于经过该点再到j的距离,如果是,则更新之。通过这样不断地寻找最短路径。 {dis[j]=dis[k]+map[k][j];//到达j点的最短路径=到达k点的最短路径+(k,j)边的距离 pre[j]=k;//将j的前置设置为k } }} } } 


Head优化的djikstra算法

由于普通的dijkstra算法每次寻找与源点最近的点都要遍历整个dis数组,效率有点慢,可以使用堆进行优化。将dis数组看成是最小堆,用o(logn)的时间复杂度去调整一个堆,只需要O(1)的时间就可以取出最小值。效率得到明显提高。

 

const int maxn=50000+5;const long long INF=0x3f3f3f3f3f;int weight[maxn];int cnt;int head[maxn];long long dis[maxn];bool vis[maxn];struct Edge{    int to;//指示边的终点 int w;//边的权重 int next;//下一条边的起始点 } edge[maxn<<1];struct Node{int u,dis;bool operator <(const Node &a) const{return dis>a.dis;// 当返回true时会更新堆,因此当新元素a的dis小于堆顶元素dis        // 的时候会返回true,同时会更新堆,故此堆为小顶堆}} void Init(){    cnt=0;    memset(head,-1,sizeof(head));    memset(vis,false,sizeof(vis));    for(int i=0; i<=n; i++)        dis[i]=INF;}void addEdge(int u,int to,int w){    edge[cnt].to=to;    edge[cnt].w=w;    edge[cnt].next=head[u];//将这条边插入以u开头的链表     head[u]=cnt++;//将u这个点的head指针指向最后插入的节点,可以看成是一个头插法的链表 }void Dijkstra(int s){    Node now,next;    priority_queue<Node>q;//定义一个优先队列     now.dis=0;//将当前点的距离设置为0     now.u=s;    dis[s]=0;    q.push(now);//将源点加入到优先队列中     while(!q.empty())//如果优先队列非空     {        now=q.top();//取出队列中dis最小的元素.这里便是整个程序优化的地方,之前我们是通过遍历n个点来得到最小值,现在我们通过维护一个堆来取得。         q.pop();//将最小元素出队         if(vis[now.u])continue;//如果该点已经被访问,则跳过         int u=now.u;//否则从该点出发并将该点标记为已访问         vis[u]=true;        for(int i=head[u]; i!=-1; i=edge[i].next)//遍历以u开头的整个链表 ,更新与u相连的点         {            int to=edge[i].to;            if(!vis[to]&&dis[u]+edge[i].w<dis[to])            {                dis[to]=dis[u]+edge[i].w;                next.dis=dis[to];                next.u=to;                q.push(next);            }        }    }}


Bellman-Ford算法

特点:单源最短路径,含负权边,可以找出负环,然而效率低下,一般使用队列优化过的SPFA方法.

思路:持续地进行松弛(即上文介绍的“三角不等式”),在每次松弛时把每条边都更新一下,若在n-1次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。

//Bellman-Ford算法  struct Edge{    int from;int to; int cost; }edge[N*2];  int ednum;bool Bellman(int start,int n){int i,j,flag;for(i=1;i<=n;i++){dis[i]=inf;//一开始所有点到源点的距离为无限大 vis[i]=0;}dis[start]=0;//将源点到自身距离设置为0for(i=1;i<n;i++)//进行|V|-1次循环{flag=0;for(j=0;j<ednum;j++){if(dis[edge[j].to]>dis[edge[j].from]+edge[j].cost){dis[edge[j].to]=dis[edge[j].from]+edge[j].costflag=1;}}if(!flag)break;//若没有松弛,则跳出 } for(i=1;i<n;i++)//进行第|V|次循环{if(dis[edge[i].u]>dis[edge[i].v]+edge[i].w)//如果第V次还能进行松弛,说明存在负环 return true;}return false;//第|V|次没有松弛,说明不存在负环。 } 


SPFA(Short PathFaster Algorithm)

特点:bellman-ford的一种队列实现,可以处理负权边,但无法处理负环。只要最短路径存在,上述SPFA算法必定能求出最小值。效率很高。BFS形式相似,当BFS中一个点出队后就不可能再次进入,而SPFA中一个点可以被反复迭代。如果某个点进入队列的次数超过N次则存在负环

思路:初始时将源加入队列,每次从队列中取出一个元素,并对所有与它相邻的点松弛,若松弛成功,则将其入队,直到队列为空时算法结束。

int SPFA(int n,int s){        int i,head=0,tail=0,p,t;        memset(vis,0,sizeof(vis));        for(i=1;i<=n;i++)dis[i]=inf;//初始化所有点的距离到源点为无限大        dis[s]=0;//将起点到起点距离设置为0        vis[s]=1;//将起点入队并标记为已访问        que[tail++]=s;        while(head<tail)        {                p=que[head];                for(i=1;i<=pnt[p][0];i++)                {                        t=pnt[p][i];                        if(dis[p]+map[p][t]<dis[t])                        {                                dis[t]=dis[p]+map[p][t];                                if(!vis[t])                                {                                        que[tail++]=t;                                        vis[t]=1;                                        flag[t]++;//记录每个点进入队列的次数                                        if(flag[t]>n)return 0;//如果某个点进入队列次数超过n次则存在负环                                }                        }                }                vis[p]=0;                head++;        }        return 1;}


SPFA(dfs版本)

特点:判断负环更快

//SPFA dfs写法,判断负环更快int spfa_dfs(int u){    vis[u]=1;    for(int k=head[u]; k!=0; k=edge[k].next)    {        int v=edge[k].v,w=edge[k].w;        if( d[u]+w < d[v] )        {            d[v]=d[u]+w;            if(!vis[v])            {                if(spfa_dfs(v))                    return 1;            }            else                return 1;        }    }    vis[u]=0;    return 0;} 


floyd算法

特点:全源最短路径 时间复杂度o(n^3) 空间 o(n^2),边权可正可负,不适合

思路:利用动态规划的思路来寻找任意两点之间的最短路径,其状态转移方程为map[i,j]:=min{map[i,k]+map[k,j],map[i,j]}

void floyd(){        for(int k=1;k<=n;k++)                for(int i=1;i<=n;i++)                        for(int j=1;j<=n;j++)                                if(dis[i][j]>dis[i][k]+dis[k][j])                                        dis[i][j]=dis[i][k]+dis[k][j];}


 

0 0
原创粉丝点击