图的最短路径问题
单源最短路径问题
目的:求解一类给定起点,求其到其他点的最短路的问题
Bellman-Ford算法
核心思想:记从起点s出发到顶点i的最短距离为d[i],则有如下关系:
\(d[i]=min{d[j]+cost(j->i)|e=(j,i) \in E}\)
若给定的图是一个DAG(有向无环图),可以简单按拓扑序遍历顶点,一步一步得出d;
而若图中有圈,则无法得到其拓扑排序,此时可以设d[s]=0,d[i]=INF(s为起点,i为终点),由公式递推得到d,若公式中不存在负圈,则此更新操作就是有限的,当不再更新后,算法结束,得到输出。
struct edge{int from,to,cost;}//从顶点from指向to的权值为cost的边
edge es[MAX_E];//保存所有的边
int d[MAX_V];//保存最短距离
int V,E;//顶点数和边数
void shortest_path(int s)//s为顶点
{
for(int i=0;i < V;i++)d[i]=INF;
d[s]=0;
while(true)
{
bool update=false;
for(int i=0;i<E;i++)
{
edge e=es[i];
if(d[e.from]!=INF&&d[e.to]>d[e.from]+e.cost)
{
d[e.to]=d[e.from]+e.cost;
update=true;
}
}
if(!update)break;
}
}
若图中存在负圈,则不会存在最短路径,因为路径会随着每一次更新越来越小,即最短路长度为-INF。
//若返回true则存在负圈
bool find_negative_loop()
{
memset(d,0,sizeof(d));
for(int i=0;i<V;i++)
{
for(int j=0;j<E;j++)
{
edge e=es[j];
if(d[e.to]>d[e.from]+e.cost)
{
d[e.to]=d[e.from]+e.cost;
//如果第V次仍然更新了,则存在负圈
if(i==V-1)return true;
}
}
}
}
SPFA算法
目的:为了解决含有负权边的图,并且相对于Bellman-Ford算法复杂度更低。
核心思想:先将起点入队,然后将队列中元素不断出队,取该节点的相连节点,如果距离改变则入队,直至队列为空。
注意:由于和Bellman-Ford算法一样,还是有可能出现负圈,判别依据为:若有一个点进入队列的次数超过顶点数V。
struct edge{int to,cost;};//保存边的终点和权值
vector <edge>G[MAX_V];
int dist[MAX_V];
int V;//点的数目
queue <int> que;
int times[MAX_V];
bool visited[MAX_V];
bool SPFA(int s)
{
fill(dist,dist+V,INF);
dist[s]=0;
que.push(s);
visited[s]=true;
times[s]++;
while(!que.empty())
{
int u=que.front();
que.pop();
visited[u]=false;
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i].to;
if(dist[u]+G[u][i].cost<dist[v])
{
dist[v]=dist[u]+G[u][i].cost;
que.push(v);
times[v]++;
if(times[v]>V)
{
return false;
}
}
}
}
return true;
}
Dijkstra算法
核心思想:对Bellman-Ford算法进行优化,当对已经确定最短距离的顶点不再进行更新。但是对于每次取最短距离的点需要遍历一遍,时间复杂度较高,所以可以用STL的priority_queue进行优化。
struct edge{int to,cost;};//保存边的终点和权值
typedef pair<int,int> P;//P.first是最短距离,P.second是顶点的编号
int V;//保存点的数目
vector <edge>G[MAX_V];
int d[MAX_V];
void dijkstra(int s)
{
priority_queue<P,vector<P>,greater<P> > que;//pair排序默认以first为主,second为辅
fill(d,d+V,INF);
d[s]=0;
que.push(P(0,s));
while(!que.empty())
{
P p=que.top();
que.pop();
int v=p.second();
if(d[v]<p.first)continue;//目的可以减少一个visite数组,对d[v]!=p.first的组进行删除,那些是求最短距离的中间产物
for(int i=0;i<G[v].size();i++)
{
edge e=G[v][i];
if(d[e.to]>d[v]+e.cost)
{
d[e.to]=d[v]+e.cost;
que.push(P(d[e.to],e.to));
}
}
}
}
路径还原
当使用Dijkstra算法求得最短路径的长度时,有时候还需要还原最短的路径,可以加入前驱数组保存每个点的前驱,最后将其反转。
Floyd-Warshall算法
特点:起点数较多
核心思想:
利用动态规划,把每两个点间的最短路搜索出来:
当只使用顶点0~k和i、j的情况下,记i到j的最短路为d[k+1][i][j]。通过DP的相关知识,d[k][i][j]=min(d[k-1][i][j],d[k-1][i][k]+d[k-1][k][j]),将该DP数组合并为一个,可以得到状态转移方程:d[i][j]=min(d[i][j],d[i][k]+d[k][j])。
int d[MAX_V][MAX_V];//d[u][v]表示边e=(u,v)的权值(若不存在设为INF,u==v时设为0)
int V;//定点数
void warshall_floyd()
{
for(int k=0;k<V;k++)
{
for(int i=0;i<V;i++)
{
for(int j=0;j<V;j++)
{
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
}

浙公网安备 33010602011771号