最短路(Floyd & Bellman_Ford &Dijkstra)

最短路

Floyd(全源最短路)

适用

复杂度较高,但常数较小,容易实现。

适用于 n≤500n \le 500n500 的情况。

概念

定义一个状态 dist[k][i][j] ,表示只允许使用编号从 1k 的顶点作为中间点,从 ij最短路径长度

求解 dist[k][i][j] ,有两种选择:

  1. 不经过顶点 k :直接由 ij 。其路径直接为 dist[k - 1][i][j]
  2. 经过顶点 k :由 ik ,再由 kj。其路径为 dist[k - 1][i][k] + dist[k - 1][k][j]

则顶点 ij 的最短路径长度为 dist[k][i][j] = min(dist[k - 1][i][j] dist[k - 1][i][k] + dist[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 ++) {
        	dist[k][i][j] = min(dist[k - 1][i][j] dist[k - 1][i][k] + dist[k - 1][k][j];
    	}
	}
}
bitset<MAXN> f[MAXN];

void floyd_warshall() {
    for (int k = 1; k <= n; k ++) {
        for (int i = 1; i <= n; i ++) {
            for (int j = 1; j <= n; j ++)
                f[i][j] |= f[i][k] & f[k][j];
        }
    }
}

优化得 ↓

bitset<MAXN> f[MAXN];

void floyd_warshall() {
    for (int k = 1; k <= n; k ++) {
        for (int i = 1; i <= n; i ++) {
            if (f[i][k])
                f[i] |= f[k];
        }
    }
}

Bellman-Ford(单源最短路)

适用

可处理负权边,并能检测从源点可到达的负权环。

概念

核心原理:松弛操作

对于一条权重为 w 的边 (u, v) ,若从源点 s -> v 的距离(dist[v])大于从s -> u 的距离(dist[u])加上边(u, v) 的权重 w

dist[v] > dist[u] + w , 就找到了一个经过 u 到达 v 的更短的路径。

则 可更新为 dist[v] = min(dist[v], dist[u] + w);

算法过程

不断尝试对图上的每一条边进行松弛操作,当一轮循环中没有成功の松弛操作,算法停止。

代码模板

const int INF = 0x3f3f3f3f;

struct edge_{
    int u, v, w;
};
vector<edge_> edge;		//存边
int dist[N], u, v, w;

bool bellman_ford(int n, int s) {
    memset(dist, 0x3d, sizeof dist);
    dist[s] = 0;		//初始化
    
    bool f = false;
    
    for (int i = 1; i <= n; i ++) {
        f = false;
        
        for (int j = 0; j < edge.size(); j ++) {
            u = edge[j].u, v = edge[j].v, w = edge[j].w;
            
            if (dist[u] == INF)			//不可达的起点
                continue;
            
            if (dis[v] > dis[u] + w) {	//松弛操作
                dis[v] = dis[u] + w;
                f = true;	//标记有更新
            }
        }
        
        if (! f)	//无更新,提前结束
            break;
    }
    
    return f;		//若第n轮仍有更新,则存在负环
}

优化-SPFA

太玄学了不写了

其实是来不及记了

过程

使用队列优化松弛操作,仅对距离更新的节点进行处理。

若节点被松弛超过vvv次,则存在负环。

时间复杂度为O(E)O(E)O(E),最坏为O(VE)O(VE)O(VE)

Dijkstra(单源最短路)

适用

解决无负权边图上最短路问题の最常用高效の算法。

算法思路

Obviously,先初始化(dis[s] = 0 ,其余元素为无穷大,vis[] 初始化为空)

迭代:
选取最小の节点 u 并将其标记为已访问。

对于 u 的每个节点 v ,若通过 u 到达 v 的路径更短(即 dis[u] + w < dis[v]),更新dis[v]

代码模板(优化后)

老师写的抽象代码

这是老师写的代码但是其实我没看懂

void Dijkstra(int s) {
    dis[s] = 0;
    vector<int> vis(n + 1);
    
    using pr = pair<ll, int>
        
    struct Pair{
        int u;
        ll w;
        bool operator<(const Pair &_) const {
            return w > _.w;
        }
    };
    priority_queue<Pair> Q;
    
    Q.push({s, 0});		//小根堆
    
    while (! Q.empty()) {
        auto [u, now] = Q.top();
        Q.pop();
        
        if (vis[u])
            continue;
        
        for (auto [v, w] : E[u]) {
            if (dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                Q.push({v, dis[u]});
            }
        }
    }
}

所以我又去看了OIwiki的代码。。。

OI wikiの朴素实现:
struct edge_{
    int v, w;
};
vector<edge_> edge[N];
int dis[N], vis[N];

void dijkstra(int n, int s) {
    memset(dis, 0x3f, sizeof dis);
    dis[s] = 0;
    
    for (int i = 1; i <= n ; i++) {
        int u = 0, mind = 0x3f3f3f3f;
        
        for (int j = 1; j <= n; i++) {
            if (! vis[j] && dis[j] < mind)	
                u = j, mind = dis[j];
            
            vis[u] = true;
            
            for (auto ed : e[u]) {		
                int v = ed.v, w = ed.w;
                
                if (dis[v] > dis[u] + w)
                    dis[v] = dis[u] + w;
            }
        }
    }
}
OI wikiの优先队列实现
struct edge_{
    int v, w;
};
struct node{
    int dis, u;
    
    bool operator > (const node & a) const{
        return dis > a.dis;
    }
};			//优先队列中的节点结构体,用于优先队列的比较。

vector<edge_> e[N];		//邻接表存图
int dis[N], vis[N];		//dis最短距离,vis是否确定最短路径。
priority_queue<node, vector<node>, greater<node>> q;

void dijkstra(int n, int s) {
    memset(dis, 0x3f, sizeof dis);
    memset(vis, 0, sizeof vis);		//初始化
    
    dis[s] = 0;
    q.push({0, s});
    
    while (! q.empty()) {
        int u = q.top().u;		//距离最小の节点
        
        q.pop();
        
        if (vis[u])				//若已确定最短路径,则跳过
            continue;
        
        vis[u]= 1;				//标记u为已确定の最短路
        
        for (auto ed : e[u]) {	//遍历邻接边
            int v =ed.v, w = ed.w;
            
            if (dis[v] > dis[u] + w) {	//松弛操作
                dis[v] = dis[u] + w;
                q.push({dis[v], v});	//将更新后の节点v加入优先队列
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值