最短路 -- HL集训回校复习day1(各种模板)


在复习的开始我还是想先说一下对于这一天上课我的感受,
怎么说,day1当然是我们上课的第一天,老师是一个漂亮(心虚)小姐姐,
讲今天课的时候老师前面内容基本上一带而过,许多基础内容直接默认我们都会,一上午大部分时间都在讲题,而我,由于自学图论并没学会多少,前面的还好说,到后面几到题的时候基本上就是挂机听思路了。QAQ

图的储存

图的储存其实对于刚接触图论的初学者来说会有点生涩,对于后面写多了的大佬们,不懂其实也会。
那我们先说最容易理解的矩阵存图
矩阵存图
其实就是用一个二维数组存图中点与点之间的关系
在这里插入图片描述
比如这个有向图我们用v[MAXN][MAXN]来存储
表示为v[1][2] = 1;v[2][4] = 1,v[3][4] = 1, v[4][5] = 1
对于无向图只需要反向再建一次就行;
v[2][1] = 1, v[4][2] = 1, v[4][3] = 1, v[5][4] = 1;
l

另一个比较常用的就是邻接表和链式前向星
这两个其实差不多,都是用于存边的,也就是记录每个边的属性;
这里就说链式前向星

#define MAXN 10010
struct node {
	int x,y,next;//x可以不要,有时做题时要用;
}
int  lin[MAXN],len = 0;
//这里lin数组记录的是最近读入的连接x的边
inline addedge(int x,int y){
	e[++len].x = x;e[len].y = ;
	e[len].next = lin[x];
	lin[x] = len;
}

//从x开始遍历;
for(int i = lin[x]; i ; i = e[i].next){
 	......
 }

emm
这里先挖个坑回来再填详解;.

一些相关知识点

顶点的度:在无向图中,某个顶点的度是与它相关联的边的数目在有向图中,一个顶点的出度是以它为起始的边的数它为终止的边的数目。
简单路径:顶点不重复的路径。
自环:从某个顶点出发连向它自身的边。
:从某个顶点出发再回到自身的路径,又称回路。
重边:从一个顶点到另一个顶点有两条边直接
连通性
无向图中,若从顶点u 到v 存在路径,那么称顶点u 和v 是连通
的。
如果无向图中任意一对顶点都是连通的,那么称此图为连通图。
如果一个无向图不是连通的,则称它的一个极大连通子图为连通分量。
这里的极大是指顶点个数极大。
有向图中,如果每一对顶点u 和v,既存在从u 到v 的路径,又存
在从v 到u 的路径,那么称此图为强连通图
对于非强连通图,其极大强连通子图成为其强连通分量

最短路算法

Floyd 算法

Floyd算法其实就是暴力更新点与点之间的最短距离
对于较小的数据Floyd其实是非常好的算法,又短又明了,也方便更新时进行其他操作
但显然这是O(|n|3)的复杂度,大部分时候它并不适用
Floyd第一层循环是枚举每个点能否作为中间点使i到j的距离更小;
里面两层很显然就是枚举i,j是否能用k更新;

void floyd(){
	for(int k = 1 ; k <= n; ++k)
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= n; ++j)
				a[i][j] = min(a[i][j],a[i][k] + a[k][j]);//更新a[i][j]的值
}

Dijkstra算法

如果有向图的边权值全为正数,那么有一种复杂度有保证的单源最
段路算法——Dijkstra 算法。它的时间复杂度是O(|n|2)。
简而言之Dijkstra不能跑负权图求最短路,并且复杂度稳定的O(|n|2)。
对于单源最短路的话就是一个点到其他点的最短距离;

对于Dijkstra算法我们需要一个dist数组来记录所求点到其他每个点的最短距离,
和一个vis数组标记节点;
首先
我们先初始化数组dist[1] = 0;其余赋为最大值memset(dist,0x3f3f3f,sizeof(dist));
然后找出没有被vis数组标记的dist[x]最小的节点x,然后标记节点x,vis[x] = 1。
then遍历节点x的所有出边(无向图就是所有边)(x,y,z)//x为当前点y为x连接的另一个点,z为改边的权值
如果dist[y] > dis[x] + z//也就是找到一条所求点到y更短的路,即先到x再到y这条路径;
就用dist[x] + z 的值去更新dist[y]的值
最后 我们再重复上面步骤(不用再初始化),知道vis数组标记所有点;
代码

#define MAXN 10010
#define INF 0x3f3f3f

int dis[MAXN],vis[MAXN];
int a[500][500]//存图 

void dijkstra(){
	memset(dis,INF,sizeof(dis));
	memset(vis,false,sizeof(vis));
	dis[1] = 0;
	for(int i = 1; i < n; ++i){
		int x = 0;
		for(int j = 1; j <= n; ++j){
			if(!v[j] &&  (x == 0 || dis[j] < dis[x]))
				x = j;
			vis[x] = true;
			for(int j = 1; j <= n; ++j){
				dis[j] = min(dis[j], dis[x] + a[x][y])
			}
		}
	}
}

Dijkstra 算法的堆优化

上面dijkstra的复杂度是O(|n|2),主要在于第一步找最小值的过程,可以用二叉堆优化dis数组,O(logn)的时间获取最小值并从堆中删除O(logn)的时间对一条边扩展并更新,这里我们用链式前向星存图,最终时间复杂度为O(mlogn);
代码

struct node{
	int y,z,ne;
}e[MAXN];

int lin[MAXN],len = 0;

inline addedge(int x,int y, int z){{
	e[++len].y = y;e[len].z = z;
	e[len].ne = lin[x]; lin[x] = len;
}

int dis[MAXN],vis[MAXN];
priority_queue< pair < int , int > > q;

void dijkstra(){
	memset(dis,INF,sizeof(dis));
	memset(vis,false,sizeof(vis));
	while(!q.empty()){
		int x = q.top().second;q.pop();
		if(vis[x]) continue;
		vis[x] = 1;
		for(int i = lin[x]; i ; i = e[i].ne){
			int y = e[i].y, z = e[i].z;
			if(dis[y] > dis[x] + z){//更新dis[y]的值 
				dis[y] = dis[x] + z;
				q.push(make_pair(-dis[y],y));//放入二叉堆 
			}
		}
	}
}

Bellman-Ford

给定了一个边带有权值(可以为负数)的有向图(不包含负环)和一个指定的顶点s。要求求出从s 到其余各点的最短路径长度。Bellman-Ford 算法是一个比较直观的求解单源最短路问题的算法。我们可以肯定最短路径包含的边的条数不会超过n − 1 个,如果超过这个数,那么肯定形成了一个环,又因为这个环权值是正的,我们可以将路径上这个环删除,路径长度就会变短
Bellman-Ford 算法描述

创建源顶点 v 到图中所有顶点的距离的集合 distSet,为图中的所有顶点指定一个距离值,初始均为极大值,源顶点距离为 0;
计算最短路径,执行 V - 1 次遍历;
对于图中的每条边:如果起点 u 的距离 d 加上边的权值 w 小于终点 v 的距离 d,则更新终点 v 的距离值 d;
检测图中是否有负权边形成了环,遍历图中的所有边,计算 u 至 v 的距离,如果对于 v 存在更小的距离,则说明存在环;
值得一提的是Floyd算法也可以通过这样检测负环 两边Floyd,如果第二遍仍被更新就说明有负环。

代码

bool bellman_ford(int s){
	memset(dis,INF,sizeof(dis));
	dis[s] = 0;
	bool flag;
	for(int i = 1;i <= n; ++i){
		flag = false;
		for(int j = 1; j <= len; ++j){
			int x = e[i].x, y = e[i].y, z = e[i].z;
			if(dis[y] > dis[x] + z){
				dis[y] = dis[x] + z;
				flag = true;
			}
		}
		if(!flag) return true;
	}
	//判负环
	for(int i = 1; i <= len; ++i){ //第二遍遍历,如果还能更新则有负环。
		int x = e[i].x, y = e[i].y, z = e[i].z;
		if(dis[y] > dis[x] + z) 
			return false;
		return true;
	}
}

这个代码是判负环用的,如果想单纯的找最短路的话吧判负环的内容去掉就行了。

SPFA

假设我们现在已经得到了Bellman-Ford 算法某个阶段的dist 数组,然后我们发现了一条s 到u 的距离比dist[u] 更加短的路径。我们更新了dist[u]。
那么接下来直接受到影响的就是与u 直接关联的顶点v,也就是如果dist[u] + w[u][v] < dist[v] 的话,s 到v 的最短路就可以利用s 到u 的最短路加上u 到v 的边来更新。这样的话与v 直接关联的顶点又会受到影响. . . . . . 不断这样持续下去直到最后没有顶点能被影响。
那么一个优化就是我们利用队列存储这些需要更新的结点,每次从队列中取出一个结点,计算是否有结点需要更新,如果有,并且这个结点不在队列中,那么就将它加入队列。
这样的算法被称为SPFA——一种优化的Bellman-Ford 算法
代码

void spfa(int s){
	memset(dis,INF,sizeof(dis));
	memset(vis,false,sizeof(vis));
	queue< int > q;
	q.push(s); vis[s] = true; dis[s] = 0;
	while(!q.empty()){
		int x = q.front(); q.pop();
		vis[x] = false;
		for(int i = lin[s]; i ; i = e[i].ne){
			int y = e[i].y, z = e[i].z;
			if(dis[y] > dis[s] + z){
				dis[y] = dis[s] + z;
				if(!vis[y]){
					vis[y] = true;
					q.push(y);
				}
			}
		}
		
	}
} 

部分例题

回来再说
。。。゛(ノ><)ノ逃

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BIGBIGPPT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值