图的最短路径问题

单源最短路径问题

目的:求解一类给定起点,求其到其他点的最短路的问题

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

posted @ 2021-12-13 20:04  Lord-Phantom-city  阅读(60)  评论(0)    收藏  举报