最短路算法简介

1. floyd

  • 描述
    对于节点数为n的无环无负权图,图中最远两个节点的最短距离最多经过n-1条边。

子图 V ( 1 , k ) V(1,k) V(1,k)为除去 V V V除去边 e d g e ( i , j ) , ∀ i , j > k edge(i,j),\forall i,j \gt k edge(i,j),i,j>k后形成的子图。

对于允许通过子图 V ( 1 , k ) V(1,k) V(1,k),点 x , y x,y x,y间的最短路为

d i s [ k , x , y ] = m i n ( d i s [ k − 1 , x , y ] , d i s [ k − 1 , x , k ] + d i s [ k − 1 , k , y ] ) dis[k,x,y]=min(dis[k-1,x,y],dis[k-1,x,k]+dis[k-1,k,y]) dis[k,x,y]=min(dis[k1,x,y],dis[k1,x,k]+dis[k1,k,y])
最短路即跑到 n = k n=k n=k

for (int k = 1;k <= n; ++k) 
	for (int i = 1; i <= n; ++i) 
   		for (int j = 1; j <= n; ++j) 
   			dis[k][i][j] = min(dis[k -1][i][j], dis[k-1][i][k] + dis[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) 
   			dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j])

时间复杂度 O ( n 3 ) O(n^3) O(n3),空间复杂度 O ( n 2 ) O(n^2) O(n2)

2. bellman-ford

对每条边进行松弛操作。

即对于 e d g e ( u , v ) edge(u,v) edge(u,v)

d i s [ v ] = m i n ( d i s [ v ] , d i s [ u ] + w e i g h t [ u ] [ v ] ) dis[v]=min(dis[v], dis[u]+weight[u][v]) dis[v]=min(dis[v],dis[u]+weight[u][v])
对于无负环图的情况,最多经过 n − 1 n-1 n1次松弛;

所以对于 n n n次还可以松弛的情况说明了,存在负环。

struct Edge {
  int u, v, w;
};

vector<Edge> edge;

int dis[MAXN], u, v, w;
const int INF = 0x3f3f3f3f;

bool bellmanford(int n, int s) {
  memset(dis, 0x3f, sizeof(dis));
  dis[s] = 0;
  bool flag = false;  // 判断一轮循环过程中是否发生松弛操作
  for (int i = 1; i <= n; i++) {
    flag = false;
    for (int j = 0; j < edge.size(); j++) {
      u = edge[j].u, v = edge[j].v, w = edge[j].w;
      if (dis[u] == INF) continue;
      // 无穷大与常数加减仍然为无穷大
      // 因此最短路长度为 INF 的点引出的边不可能发生松弛操作
      if (dis[v] > dis[u] + w) {
        dis[v] = dis[u] + w;
        flag = true;
      }
    }
    // 没有可以松弛的边时就停止算法
    if (!flag) {
      break;
    }
  }
  // 第 n 轮循环仍然可以松弛时说明 s 点可以抵达一个负环
  return flag;
}
  • spfa
    松弛操作只可能发生在相邻的两条边,所以我们用队列进行优化。
struct edge {
  int v, w;
};

vector<edge> e[maxn];
int dis[maxn], cnt[maxn], vis[maxn];
queue<int> q;

bool spfa(int n, int s) {
  memset(dis, 63, sizeof(dis));
  dis[s] = 0, vis[s] = 1;
  q.push(s);
  while (!q.empty()) {
    int u = q.front();
    q.pop(), vis[u] = 0;
    for (auto ed : e[u]) {
      int v = ed.v, w = ed.w;
      if (dis[v] > dis[u] + w) {
        dis[v] = dis[u] + w;
        cnt[v] = cnt[u] + 1;  // 记录最短路经过的边数
        if (cnt[v] >= n) return false;
        // 在不经过负环的情况下,最短路至多经过 n - 1 条边
        // 因此如果经过了多于 n 条边,一定说明经过了负环
        if (!vis[v]) q.push(v), vis[v] = 1;
      }
    }
  }
  return true;
}

3. dijstra

将结点分成两个集合:已确定最短路长度的点集(记为 S S S 集合)的和未确定最短路长度的点集(记为 T T T 集合)。一开始所有的点都属于 T T T 集合。

初始化 d i s ( s ) = 0 dis(s)=0 dis(s)=0,其他点的 d i s dis dis 均为 + ∞ +\infty +

然后重复这些操作:

从 T 集合中,选取一个最短路长度最小的结点,移到 S 集合中。
对那些刚刚被加入 S 集合的结点的所有出边执行松弛操作。
直到 T 集合为空,算法结束。

堆优化,可以使用堆来进行插入,每次取堆上的最短长度节点。

struct edge {
  int v, w;
};

struct node {
  int dis, u;

  bool operator>(const node& a) const { return dis > a.dis; }
};

vector<edge> e[maxn];
int dis[maxn], vis[maxn];
priority_queue<node, vector<node>, greater<node> > q;

void dijkstra(int n, int s) {
  memset(dis, 63, sizeof(dis));
  dis[s] = 0;
  q.push({0, s});
  while (!q.empty()) {
    int u = q.top().u;
    q.pop();
    if (vis[u]) continue;
    vis[u] = 1;
    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});
      }
    }
  }
}

0. Ref

OI-wiki

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值