最短路径算法汇总

Dijkstra
d[0]=0;d[1~n]=INF; 
1选最小的d[i];
2从i点出发所有边 松弛d[j] 

伪代码:


for(1~n)
{
所有未标记的点选出d最小的点x;
标记x点走过;
i点出发所有边 更新d[y]=min(d[y],d[x]+w[x][y]); // <x,y>属于E; 


1)简易版:O(n*n) 

对边编号:u[i],v[i] 表示第i边连通哪两点 v[i]为i点标记; 

memset(v,0,sizeof(v));
for(int i=0;i<n;i++) 
for(int j=0;j<n;j++)
{
	int x,m=INF;
	for(int y=0;y<n;y++) if(!v[y]&&m>=INF) m=d[y],x=y;  //选出最小的d[x]; 
	v[x]=1;
	for(int y=0;y<n;y++) d[y]=min(d[y],d[x]+w[x][y]);   //更新d[y]; 
}

打印路径:将 for(int y=0;y<n;y++) d[y]=min(d[y],d[x]+w[x][y]);
 换为    for(int y=0;y<n;y++) if(d[y]>d[x]+w[x][y]) d[y]=d[x]+w[x][y],path[y]=x;
    
2)优化版 O(m*logn) 

 

 对点编号 vector<int> G[maxn]; 保存邻接表;

 优先队列取出d[i]最小的,就不用O(n)找出d[i]最小的了;

struct Edge
{
	int from,to,dist;
	Edge(int u,int v,int w) : from(u),to(v),dist(w) {}
};
struct HeapNode
{
	int d,u;
	bool operator < (const HeapNode& rhs) const {
		return d<rhs.d;
	}
}
vector<int> G[maxn]; //G[u][i] 表示u点连接的边的编号; 
vector<Edge> edges; //保存对应边的编号的一切信息 
bool vis[maxn]; //
int d[maxn],path[maxn];
void dijkstra(int s)
{
	priority_queue<HeadNode> Q;
	Q.push(HeadNode(0,s));
	for(int i=0;i<n;i++) d[i]=INF,vis[i]=false;
	d[s]=0;
	while(!Q.empty())
	{
		HeapNode x=Q.top();Q.pop();
		int u=x.u;
		if(vis[u]) continue;
		vis[u]=true; 
		for(int i=0;i<G[u].size();i++)
		{
			Edge e=edges[G[u][i]];
			if(d[e.to]>d[e.from]+e.dist)
			{
				d[e.to]=d[e.from]+e.dist;
				path[e.to]=G[u][i];
				Q.push(HeapNode(e.to,e.dist));
			}
		}
	}
}
打包成下面 
struct Dijkskra
{
	int n,m;
	vector<int> G[maxn]; //G[u][i] 表示u点连接的边的编号; 
	vector<Edge> edges; //保存对应边的编号的一切信息 
	bool vis[maxn]; //
	int d[maxn],path[maxn];
	void init(int n)
	{
		this->n=n;
		for(int i=0;i<n;i++) G[i].clear();
		edges.clear();
	}
	void AddEdge(int from,int to,int dist)
	{
		edges.push_back(from,to,dist);
		m=edges.size();
		G[from].push_back(m-1);
	}
	void dijkstra(int s)
	{
		priority_queue<HeadNode> Q;
		Q.push(HeadNode(0,s));
		for(int i=0;i<n;i++) d[i]=INF,vis[i]=false;
		d[s]=0;
		while(!Q.empty())
		{
			HeapNode x=Q.top();Q.pop();
			int u=x.u;
			if(vis[u]) continue;
			vis[u]=true; 
			for(int i=0;i<G[u].size();i++)
			{
				Edge& e=edges[G[u][i]];
				if(d[e.to]>d[e.from]+e.dist)
				{
					d[e.to]=d[e.from]+e.dist;
					path[e.to]=G[u][i];
					Q.push(HeapNode(e.to,e.dist));
				}
			}
		}
	}
}

Bellman-Ford
对每个已更新过的节点都更新其相连的节点;
最短路最多经过n-1点(起点不算),经过m条边; 

伪码描述


  for(1~n-1)  //最多检查n-1次 
  for(1~m)  //每次每条边都要检查 
{
  if(x点已经更新) 更新周围的点; 

}   


因为Dijkstra是每次取出最小d[i],当存在负权就会出现问题;
而使用Bellman-Ford只是随意取出相邻边所以不会出现问题;
但正是因为是随意取出相邻边,所以对于每个节点要多次检查; 
即 每个节点可以多次进入并检查该节点是否能更新 ,直到无法更新周围点; 


对于起点i,其他的点都要扫一遍n-1,对于每一点相邻的边都要走一遍m 

//简易版  O(m*n) 
for(int i=0;i<n;i++) d[i]=INF;
d[0]=0;
for(int k=0;k<n-1;k++)
for(int i=0;i<m;i++)
{
	int x=u[i],y=v[i];
	if(d[x]<INF) d[y]=min(d[y],d[y]+w[i]);
	//if(x点更新过) //因为只有更新过才能更新你周围的点; 
} 
//优化版
队列优化  O(m*n) 但实际更快 
bool inq[maxn];  //即inqueue 表示该点已经压入队列; 
int cnt[maxn]; //记录每个点收录次数,当出现cnt[i]>n 则说明不存在最短路径 ;
bool Bellman-ford(int s)
{
	queue<int> Q;
	memset(inq,0,sizeof(inq));
	memset(cnt,0,sizeof(cnt));
	for(int i=0;i<n;i++) d[i]=INF;
	d[s]=0;
	Q.push(s);
	while(!Q.empty())
	{
		int u=Q.front();Q.pop();
		inq[u]=false;     //Q中取出来了,标为false; 
		for(int i=0;i<G[u].size();i++)
		{
			Edge& e=edges(G[u][i]);
			if(d[u]<INF&&d[e.to]+e.dist)  //若d[u]是INF表明u点未被更新,也就不用更新其周围的点; 
			{
				d[e.to]=d[u]+e.dist;
				path[e.to]=G[u][i];
				if(!inq[e.to])
				{
					Q.push(e.to);
					inq[e.to]=true;  //e.to点放入queue ,标识为true; 
					if(++cnt[e.to]>n) return false;  //图不通; 
				}
			}
		}
	}
	return true;
}

有三点要注意
1)每次for循环e.to只能压入队列一次,所以要用inq标识 ;
2)每个节点最多要放入队列n次(因为每个节点至少放入队列一次,若每个节点都可连通到x点,那么x点要进入队列n次)
     所以cnt[e.to]>n 表明无最短路,可能存在负环;
3)每当e.to被更新过,就要放入队列更新e.to相连的点,直到所有的点都更新完,算法结束; 
 

Floyd 多源最短路径 O(n^3) 
通过一个图的权值矩阵求出它的每两点间的最短路径矩阵

i~j 要经过k点 

d[0~n][0~n]=INF;
d[0][0]=0;
for(int k=0;k<n;k++)
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
	if(d[i][j]<INF&&d[k][j]<INF) 
	d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}

 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值