之前讲解了BFS&DFS基本场景与演化。
(ps:https://blog.csdn.net/weixin_42178241/article/details/12595659)
最短路的诞生是因为每条边有多了一个权值,不能用BFS,DFS按照层次去算了。
最短路径还是图论的一种,和BFS,DFS一样有三种建图方式,不过我们引入了2个新概念。
最短路径的求法思路有4大种,代码优化实现总共5种
单源最短路(只能算从一个源点开始,到其他节点的最短路)
每个节点的状态分3种,已知最短路,求出短路不确定是否最短,未求
我们首先将所有点赋值无穷大,用dis[i]表示源点到i的最短路径,dis[s]=0(s源点)
从源点开始向外扩展没有向外扩展的节点,更新其他节点的最短路,分别后续扩展
最后dis的值就ok了。
抖音号:向往星辰大海的white_hacker的视频中有
void dijkstra(int s){
for(int i=1;i<=n;i++) dis[i]=inf; // dis用来存储源点到任意结点的最短路,初始值无穷大
dis[s]=0; // 源点到自身的最短路显然为 0
for(int i=1;i<n;i++){ // 每次扩展一个结点,除源点外共有n-1个结点待扩展
int u,cost=inf;
// 找到距离孤岛最近的结点
for(int j=1;j<=n;j++){
if(!vis[u]&&dis[j]<cost){
cost=dis[j];
u=j;
}
}
vis[u]=true; // u 标记为已访问
// 对 u 进行扩展
for(int j=head[u];j;j=edge[j].nxt){
int v=edge[j].v,w=edge[j].w;
if(!vis[v]&&dis[v]>dis[u]+w)dis[v]=dis[u]+w;
}
}
}
堆优化 实际上与无优化的Dijkstra的思路一样,只是在每次扩展点的时,加以数据结构对时间复杂度的优化。
inline void Dijkstra(int u){
//初始化
queue<int>q;
memset(vis,false,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[u]=0;
vis[u]=true;
q.push(u);
//扩展
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=false;
//更新子节点
for(int i=head[x];i;i=e[i].nxt){
//判断 赋值并加入扩展新的节点的序列
if(dis[e[i].v]>dis[x]+e[i].w){
//赋值
dis[e[i].v]=dis[x]+e[i].w;
if(!vis[e[i].v]){
//加入预扩展队列,标记
vis[e[i].v]=true;
q.push(e[i].v);
}
}
}
}
}
每个节点只会经过一次扩展,每次扩展的都是在没有扩展过的且被标记过路径的最短,去扩展其他点,绝对性的保障了最短路径的最优解
动态规划,主要想看,i->j的最短路中间加一个k点,能否比之前路径短
只有5行很简单
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
问题来了,每次加点只能保证是两条边的最短路,会不会不包含呢?
解答来了
{解答来了}
解答来了
包含,假设有两个点u,v,k,且u,v之间有一条边,这时我们开始加点了u->k,k->v,算在了d[u][v]里,之后继续调用它,得到证明。
Bellman-ford算法,主要是在松弛边,共松弛n-1次,如果还能松弛,证明此图有环.
inline bool Bellman_ford(){
//初始化
memset(dis,0,sizeof(dis));
dis[s]=v;
//n-1次松弛
for(int i=1;i<n;i++){
bool flag=false;
//找边松弛
for(int j=1;j<=cnt;j++){
//判断赋值
if(dis[e[j].b]<(dis[e[j].a]-e[j].c)*e[j].r){
dis[e[j].b]=(dis[e[j].a]-e[j].c)*e[j].r;
flag=true;
}
}
if(!flag)return false;
}
//还可以松弛 ==》(有环图)
for(int i=1;i<=cnt;i++){
if(dis[e[i].b]<(dis[e[i].a]-e[i].c)*e[i].r)return true;
}
return false;
}
与Dijkstra相反,每个点可以多次扩展,每次保证用最小值扩展其他子节点,可以保证最短路,还可以判断负环。
和Bellman-ford一样;
bool spfa( int s ){
for ( int i = 1; i <= n; i++ )
dis[i] = inf; /* 初始化操作 */
queue<node> q;
dis[s]=0;
in[s]= true; /* 标记结点是否在队列中 */
cnt[s]++; /* 记录结点进入队列的次数 */
q.push( s );
while ( q.size() )
{
int u = q.front(); q.pop();
in[u] = false;
for ( int j = head[u]; j; j = edge[j].nxt )
{
int v = edge[j].v, w = edge[j].w;
if ( dis[v] > dis[u] + w ) /* 对相邻的每条边进行松弛 */
dis[v] = dis[u] + w;
if ( in[v] )
continue;
q.push( v );
in[v] = true;
cnt[v]++;
if ( cnt[v] > n ){
return(false); /* 存在负环 */
}
}
}
return(true);
}
· 时间复杂度:O(n^2)
· 空间复杂度:O(m)
· 应用场景:单源最短路 边权均为正 无环图
· 时间复杂度: O(m*log(m))
· 空间复杂度: O(m)
· 应用场景: 单源最短路 边权均为正 无环图
· 时间复杂度:O(n^3)
· 空间复杂度:O(n^2)
· 应用场景:多源最短路 边权环都兼容
· 时间复杂度:O(n*m)
· 空间复杂度:O(m)
· 应用场景:单源最短路 边权环都兼容
· 时间复杂度:O(n*m)
· 空间复杂度:O(m)
· 应用场景:单源最短路 边权环都兼容
后面会讲解tarjan算法和A*算法,尽情期待