图论——最短路

前言

最短路是图论基础。

图是什么

图是一种点集与边集共同组成的集合,可以理解为由边和点构成的数据结构。

图分为有向图和无向图(字面意思)。无向边在存储时可以由两条相反的有向边组成。

无向图中,若所有点都可以通过一些边互相到达,则称图是联通的。

若有 \(n\) 个点,\(n-1\) 条边,称这样的连通图为
多个树构成的图称为森林。
树和森林是一定没有环的,其他无向图一定存在环。

在图中,点和边可以赋予权值,分别称为点权和边权。

(好像差不多够用了)。 oi_wiki

图存储

1.邻接矩阵(大非物):

int a[N][N];

for(int i=1,x,y,z;i<=m;i++)//注意使用之前初始化 
	cin>>x>>y>>z,a[x][y]=z;

for(int i=1;i<=n;i++)//遍历 
	ans=min(ans,a[x][i]);

2.邻接表(一般用于不带权的图):

vector<int> v[N];

for(int i=1,x,y;i<=n;i++)//建边
	cin>>x>>y,v[x].push_back(y),v[y].push_back(x);

for(int i=0;i<v[x].size();i++)//下标遍历
	ans=min(ans,v[x][i]);
for(int it:v[x])//迭代器遍历,c++14可用
	ans=min(ans,it);

3.链式前向星(适合于带边权图):

struct E{
	int to,len,last;
}e[M<<1];
int head[N],cnt;

inline void add(int x,int y,int da){//建边
	e[++cnt].to=y,e[cnt].len=da;
	e[cnt].last=head[x],head[x]=cnt;
}

for(int i=head[x];i;i=e[i].last)//遍历
	ans=min(ans,e[i].to);

图遍历

\(DFS\)\(BFS\)
\(DFS\)

int vis[N];
void dfs(int x){
	vis[x]=1;//保证复杂度的关键
	for(int u:v[x])
		if(!vis[u])dfs(u);
}

时间复杂度为 \(O(n+m)\)。(一定要打标记)。
应用:\(DFS\) 找环,\(Tarjan\) 求强联通分量。

\(BFS\)

queue<int> q;
int vis[N];
void bfs(int S){
	q.push(S); vis[S]=1;
	while(!q.empty()){
		int x=q.front(); q.pop();
		for(int u:v[x])
			if(!vis[u])q.push(u),vis[u]=1;
	}
}

时间复杂度为 \(O(n+m)\)
应用:\(BFS\) 判断连通性,\(SPFA\) 最短路。

最短路算法

1.\(Floyed\)

这是一种多源最短路算法,运用了动态规划思想。
\(dp[k][i][j]\) 表示 \(i\)\(j\) 的只能经过 \(1-k\) 的最短路。
易有 \(dp[k][i][j]=min(dp[k-1][i][j],dp[k-1][i][k]+dp[k-1][k][j])\)
再加上滚动数组,就成了我们喜闻乐见的形式。

for(int k=1;k<=n;k++)
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);

局限性:没有局限性(只要有最短路就一定能求出来)。
注意:使用之前先对 \(dp\) 数组初始化,不能到达先设一个极大值。

2.\(Dijkstra\)

这是一种单源最短路算法,运用了贪心思想。
在正权图中,若 \(dis(i,j)=min_{k=1}^{n}dis(i,n)\),则 \(dis(i,j)<dis(i,k)+dis(k,j)\)
正是基于这个简单的结论,才有了 \(Dijkstra\) 算法。
算法内容:寻找没有挑选过的点中的距离最小的点,之后对周围的点进行松弛操作(更新最短路),重复 \(n-2\) 次。

朴素实现:

int a[N][N],dis[N]; bool flag[N];
void Dijkstra(int s){
	for(int i=1;i<=n;i++)dis[i]=1e9;//初始化 
	dis[s]=0,flag[s]=1;
	for(int j=1;j<=n-2;j++){
		int minn=1e9,k;
		for(int i=1;i<=n;i++)//寻找最小的点 
			if(!flag[i]&&dis[i]<minn)
				minn=dis[i],k=i;
		flag[k]=1;//标记为选过的 
		for(int i=1;i<=n;i++)//松弛操作 
			dis[i]=min(dis[i],dis[k]+a[k][i]);
	}	
}

时间复杂度为 \(O(n^2)\)

不难发现,有很大一部分时间用于寻找最小值点,又有一部分时间用于松弛。
分别用堆/线段树,链式前向星优化。
堆常数较小,一般用堆优化。

int dis[N]; bool vis[N]; 
struct node{
	int dis,id;
	bool operator<(const node &x)const{
		return x.dis<dis;
	}
};
priority_queue<node> q;

void Dijkstra(int from){
    memset(dis,0x3f3f3f3f,sizeof(dis));//初始化 
    memset(vis,0,sizeof(vis));
    while(!q.empty())q.pop();
    dis[from]=0; q.push((node){0,from});//加入源点 
    while(!q.empty()){
        int x=q.top().id; q.pop();
        if(vis[x])continue;//去重 
        else vis[x]=1;
        for(int i=head[x];i;i=e[i].last){//松弛 
            int y=e[i].to;
            if(dis[x]+e[i].len<dis[y]){
                dis[y]=dis[x]+e[i].len;
                q.push((node){dis[y],y});
            }
        }
    }
}

时间复杂度为 \(O(mlogm)\)

比较一下朴素版与堆优化版,不难发现:
稀疏图用堆优化版更优,稠密图用朴素版更优(但一般都直接使用堆优化版)。
局限性:只能用于正权图。

3.\(SPFA\)

这是一种单源最短路算法,运用了暴力思想。

posted @ 2025-11-24 22:53  zhoumengxuan  阅读(5)  评论(0)    收藏  举报