图论——最短路
前言
最短路是图论基础。
图是什么
图是一种点集与边集共同组成的集合,可以理解为由边和点构成的数据结构。
图分为有向图和无向图(字面意思)。无向边在存储时可以由两条相反的有向边组成。
无向图中,若所有点都可以通过一些边互相到达,则称图是联通的。
若有 \(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\)
这是一种单源最短路算法,运用了暴力思想。

浙公网安备 33010602011771号