图论的应用

本文详细介绍了单源最短路问题,包括Dijkstra算法、Bellman-Ford算法以及它们在处理有向图和无向图中的应用,特别是如何处理负权边和负环的情况,以及SPFA算法作为队列优化的Bellman-Ford算法的实现。
摘要由CSDN通过智能技术生成

 1.最短路问题

  • 从起点到终点的路径中,边的权值和最小的路径(带权图)
  • 单源最短路问题和任意两点间最短路问题(全源最短路问题)

2.单源最短路问题

  • SSSP:Single Source Shortest Path
  • 求以st点位起点到其他结点的最短路径长度
  • min dist[1...n]         特别地, dis[st]=0

单源最短路的算法:

  • Dijkstra
  • Bellman-Ford
  • SPFA

2.1松弛操作

用数组d记录每个顶点的最短路径估计值。若用 u 来松弛v,就是判断是否 d[u] + w[u][v] < d[v],

如果该式成立则用 d[u] + w[u][v] 更新 d[v] ,否则不变。

2.2 Dijkstra算法

  • 基于贪心思想
  • 仅适用于求边权为正的有向图或无向图的单源最短路。

算法流程 

  1. 初始化 d[源点] = 0,其余顶点的d值为无穷大;
  2. 在所有未标记顶点中找d值最小的顶点x,并标记之;
  3. 对x的所有未标记邻接点进行 "松弛操作” 。
  4. 重复上述2~3两个步骤, 直至所有顶点都被标记。 

特别说明: Dijkstra算法基于贪心思想,一旦某顶点被选中并加了访问标记,

则不再被其他顶点松弛!

算法框架

定义数组G,存储图
定义数组d,存储源点到各点的最短距离

dijkstra(s) {  //s是源点
    初始化d数组;
    for(循环n次)  {
        u=使d[u]最小的还未被访问的定点的标号
            记u已经被访问
            for(从u出发能到达的所有顶点v)
                if(v未被访问 && d[v]可以被松弛)
                    优化d[v];
    }

}

核心代码(邻接矩阵)

void Dijkstra(int s){            //s为源点 
	fill(d, d+MaxV, INF);        //初始化d数组 
	d[s]=0;
	for(int i=0;i<n;i++){
		int u=-1, minu=INF;      //u使d[u]最小,minu存放最小的d[u] 
		for(int j=0;j<n;j++)	 //找到未访问的顶点中d值最小的顶点 
			if(vis[j]==false && d[j]<minu)
				u=j, minu=d[j];
		if(u==-1) return;        //找不到小于inf的d[u], 说明顶点和源点s不连通 
		vis[u]=true;             //标记u为已访问 
		for(int v=0;v<n;v++)     //松弛操作 
			if(vis[v]==false && G[u][v]!=inf && d[u]+G[u][v]<d[v])
				d[v]=d[u]+G[u][v];
	}
}

核心代码(邻接表)

时间复杂度为O(N^2)

void Dijkstra(int s){            //s为源点 
	fill(d, d+MaxV, INF);        //初始化d数组 
	d[s]=0;
	for(int i=0;i<n;i++){
		int u=-1, minu=INF;      //u使d[u]最小,minu存放最小的d[u] 
		for(int j=0;j<n;j++)	 //找到未访问的顶点中d值最小的顶点 
			if(vis[j]==false && d[j]<minu)
				u=j,  minu=d[j];
		if(u==-1) return;        //找不到小于inf的d[u], 说明顶点和源点s不连通 
		vis[u]=true;             //标记u为已访问 
		for(int j=0;j<G[u].size();j++)     //松弛操作 ,以下是两段代码的主要区别 
			int v=G[u][j].v;
			if(vis[v]==false && d[u]+G[u][j].dis<d[v])
				d[v]=d[u]+G[u][j].dis;
	}
}

算法缺陷

  • 基于贪心思想,一旦某顶点被选中并加了访问标记,则不再被其他顶点松弛!
  • 仅适用于求边权为正的有向图或无向图的单源最短路。

2.3 Bellman-Ford算法

  • 基于迭代思想,解决含负权边的有向图的单源最短路问题。
  • 不能处理带权边的无向图(因可以来回走一条负权边)
  • 不能处理负环图,如下图所示:

算法思路 

  1. 对图中的边进行n-1轮操作,每轮都遍历图中所有的边。
  2. 对每条边<u,v>, 如果d[u] + w[u][v] < d[v], 则更新d[v]的值。代码实现

关于Bellman-Ford松弛n-1轮

简单来说,从源点到一个点的最短路的极端情况是路径经过n-1个顶点(不算源点),也就是需要松弛n-1轮。这样,我们执行n-1轮就可以保证所有点都被松弛到最佳情况。

  • 如果执行了n-1轮迭代后还能继续松弛,说明图中有负环。
  • 有负环,最短路径不存在。
  • Yes是存在负环,No是不存在负环

代码实现

​
Bellman(int s){            //s为源点 
	fill(d, d+MaxV, INF);        //初始化d数组 
	d[s]=0;
	for(int i=0;i<n-1;i++){      //执行n-1轮操作,n为顶点数 
		for(int u=0;u<n;u++)	 //每轮都遍历所有的边
			for(int j=0;j<G[u].size();j++){
				int v=G[u][j].v;
				int dis=G[u][j].dis;
				if(d[u]+dis<d[v])    //松弛操作 
					d[v]=d[u]+dis;
	}
}

​

队列优化

  • SPFA算法是队列优化的Bellman-Ford算法的别称
  • 流程:
  1. 建立一个队列,最初队列中只包含源点,并给起点打上标记
  2. 取队头结点想,清除结点x的访问标记,以便再次入队;对x的所有邻接点y进行松弛操作。若y不在队列中,则把y入队。
  3. 重复上述步骤,直到队列为空。

SPFA代码实现

void SPFA(int s){
	queue<int> q;
	q.push(s);
	vis[s]=true;                          //设置源点入队 
	d[s]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop;
		vis[u]=false;                     //设置顶点u不在队列中 
		for(int j=0;j<G[u].size();j++){
			int v=G[u][j].v;
			int dis=G[u][j].dis;
			if(d[u]+dis<d[v]){
				d[v]=d[u]+dis;
				if(!vis[v]){
					q.push(v);            //设置顶点v入队 
					vis[v]=true;
				} 
			}
		}
	}
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值