图论 Algorithms

1) Dijkstra

基本思路:更新每个点到原点的最短路径;寻找最短路径点进行下一次循环;循环次数达到 n - 1 次说明每个点到原点的最短路已成,停止程序。

 1  function Dijkstra(Graph, source):
 2
 3      create vertex set Q
 4
 5      for each vertex v in Graph:             // Initialization
 6          dist[v] ← INFINITY                  // Unknown distance from source to v
 7          prev[v] ← UNDEFINED                 // Previous node in optimal path from source
 8          add v to Q                          // All nodes initially in Q (unvisited nodes)
 9
10      dist[source] ← 0                        // Distance from source to source
11      
12      while Q is not empty:
13          u ← vertex in Q with min dist[u]    // Source node will be selected first
14          remove u from Q 
15          
16          for each neighbor v of u:           // where v is still in Q.
17              alt ← dist[u] + length(u, v)
18              if alt < dist[v]:               // A shorter path to v has been found
19                  dist[v] ← alt 
20                  prev[v] ← u 
21
22      return dist[], prev[]
// Dijkstra
#include <bits/stdc++.h>
using namespace std;
const int N = 25000 + 5, M = 62000 + 5;
const int inf = 2147483647;
int n, m, s, e, dis[N];
int first[N], nex[M], to[M], w[M], en;
bool v[N];

void add(int x, int y, int z) {
    nex[++en] = first[x]; first[x] = en; to[en] = y; w[en] = z;
    nex[++en] = first[y]; first[y] = en; to[en] = x; w[en] = z;
}

int main() {
    int x, y, z;
    scanf("%d%d%d%d", &n, &m, &s, &e);
    for (int i = 1; i <= n; i++)
        dis[i] = inf;
    for (int i = 1; i <= m; i++) {
        scanf("%d%d%d", &x, &y, &z);
        add(x, y, z);
    }

    dis[s] = 0; v[s] = true;
    int k = s;
    for (int i = 1; i <= n - 1; i++) {
        int minlen = inf;
        for (int j = first[k]; j; j = nex[j])
            if (dis[k] + w[j] < dis[to[j]])
                dis[to[j]] = dis[k] + w[j];
        for (int j = 1; j <= n; j++) {
            if ((!v[j]) && (dis[j] < minlen)) {
                k = j;
                minlen = dis[j];
            }
        }
        v[k] = true;
    }

    printf("%d\n", dis[e]);
    return 0;
}
/* Dijkstra Optimized
 * Au: H15teve
 */
#include <bits/stdc++.h>
// header <queue> included.
using namespace std;

const int N = 200003, M = 1000003, inf = 2147483647;

struct node {
	int u, dis;
	bool operator < (const node &n) const {
		return dis > n.dis;
	}
} temp;

priority_queue<node> q;

int d[N], n, m, s, head[N], nex[M], to[M], w[M], en;
bool v[N];

inline void add(int x, int y, int z) {
	nex[++en] = head[x], head[x] = en,
	to[en] = y, w[en] = z;
}

int main() {
	scanf("%d%d%d", &n, &m, &s);
	for (int i = 1; i <= n; i++) d[i] = inf;
	for (int i = 1, x, y, z; i <= m; i++) {
		scanf("%d%d%d", &x, &y, &z);
		add(x, y, z);
	}

	d[s] = 0, temp.u = s; q.push(temp);

	while (!q.empty()) {
		temp = q.top(), q.pop();
		if (v[temp.u]) continue;
		v[temp.u] = true;

		for (int i = head[temp.u]; i; i = nex[i])
			if (temp.dis + w[i] < d[to[i]])
				d[to[i]] = temp.dis + w[i],
				q.push((node) {to[i], d[to[i]]});
	}

	for (int i = 1; i <= n; i++)
		printf("%d ", d[i]);
	return 0;
}

2) Kruskal

基本思路:按边长度从小到大排序,循环添加「不成环」的边;边数达到 n - 1 说明最小生成树已成,停止程序。

/* Kruskal
 * Au: GG
 */
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <ctime>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1000 + 3, M = 20000 + 3;
int dset[N], n, m;

struct edge {
    int x, y, w;
    bool operator < (const edge &a) const { return w < a.w; }
} edges[M];

int find(int x) { return (dset[x] == -1) ? x : dset[x] = find(dset[x]); }
void join(int x, int y) { if (find(x) != find(y)) dset[find(x)] == find(y); }

int Kruskal() {
    memset(dset, -1, sizeof(dset));
    sort(edges + 1, edges + m + 1);
    int cnt = 0, tot = 0;
    for (int i = 1; i <= m; i++)      //循环所有已从小到大排序的边
        if (find(edges[i].x) != find(edges[i].y)) { // (因为已经排序,所以必为最小)
            join(edges[i].x, edges[i].y); // 相当于把边(u,v)加入最小生成树。
            tot += edges[i].w;
            cnt++;
            if (cnt == n - 1) // 说明最小生成树已经生成
                break; 
        }
    return tot;
}

int main() {
    printf("Enter vertex number & edge number:\n");
    scanf("%d%d", &n, &m);
    printf("Enter information of %d edge(s):\n", m);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d%d", &edges[i].x, &edges[i].y, &edges[i].w);
    }

    printf("The weight of minimum spanning tree is %d.\n", Kruskal());
    
    return 0;
}

3) Bellman-Ford

基本思路:更新每个点到原点的最短路径;循环次数达到 n - 1 次说明每个点到原点的最短路已成,停止程序。

// Bellman-Ford
// Au: GG
#include <bits/stdc++.h>
using namespace std;
const int maxn = 25000 + 5, maxm = 62000 + 5;
const int inf = 1000000001;
int n, m, s, e, en;
int from[maxm], to[maxm], w[maxm];
int dis[maxn], pre[maxm];
void add(int x, int y, int z) {
    from[++en] = x; to[en] = y; w[en] = z;
}
int main() {
    int x, y, z;
    scanf("%d%d%d%d", &n, &m, &s, &e);
    for (int i = 1; i <= n; i++)
        dis[i] = inf;
    for (int i = 1; i <= m; i++) {
        scanf("%d%d%d", &x, &y, &z);
        add(x, y, z);
    }
    dis[s] = 0; pre[s] = 0;
    for (int i = 1; i <= n - 1; i++)
        for (int j = 1; j <= m; j++)
            if (dis[from[j]] + w[j] < dis[to[j]]) {
                dis[to[j]] = dis[from[j]] + w[j];
                pre[to[j]] = from[j];
            }
    printf("%d\n", dis[e]);
    return 0;
}

4) Floyd

基本思路:枚举所有点与点的中点,如果从中点走最短,更新两点间距离值。

procedure Floyd–Warshall(G)
1 let dist be a |V| × |V| array of minimum distances initialized to ∞ (infinity)
2 for each vertex v
3    dist[v][v] ← 0
4 for each edge (u,v)
5    dist[u][v] ← w(u,v)  // the weight of the edge (u,v)
6 for k from 1 to |V|
7    for i from 1 to |V|
8       for j from 1 to |V|
9          if dist[i][j] > dist[i][k] + dist[k][j] 
10             dist[i][j] ← dist[i][k] + dist[k][j]
11         end if
// Floyd
// Au: GG
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000 + 3;
const int inf = 1000000001;
int n, m;
int f[maxn][maxn];
int main() {
    scanf("%d%d", &n, &m);
    int i, j, k, a, b, w;
    for (i = 1; i <= n; i++)
        for (j = 1; j <= n; j++)
            f[i][j] = inf;
    for (i = 1; i <= m; i++) {
        scanf("%d%d%d", &a, &b, &w);
        if (f[a][b] > w) f[a][b] = w, f[b][a] = w;
    }
    for (k = 1; k <= n; k++)
        for (i = 1; i <= n; i++)
            for (j = 1; j <= n; j++)
                if (f[i][j] > f[i][k] + f[k][j])
                    f[i][j] = f[i][k] + f[k][j];
    scanf("%d%d", &a, &b);
    printf("%d", f[a][b]);
    return 0;
}

5) SPFA

基本思路:更新每个点到原点的最短路径,保证「路径可变得更小的点」在队列中;队列空说明每个点到原点的最短路已成,停止程序。

procedure Shortest-Path-Faster-Algorithm(G, s)
  1    for each vertex v ≠ s in V(G)
  2        d(v) := ∞
  3    d(s) := 0
  4    offer s into Q
  5    while Q is not empty
  6        u := poll Q
  7        for each edge (u, v) in E(G)
  8            if d(u) + w(u, v) < d(v) then
  9                d(v) := d(u) + w(u, v)
 10                if v is not in Q then
 11                    offer v into Q
/* Shortest Path Faster Algorithm
 * Au: GG
 */
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <queue>
#include <stack>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1000 + 3, M = 10000 + 3;
int n, m, s, d[N], pre[N], enq[N];
bool inq[N];
int head[N], nex[M], to[M], w[N], en;
queue<int> q;

void add(int x, int y, int z) {
    nex[++en] = head[x]; head[x] = en; to[en] = y; w[en] = z;
    nex[++en] = head[y]; head[y] = en; to[en] = x; w[en] = z;
}

bool SPFA() {
    memset(d, 0x7f, sizeof(d));
    q.push(s); d[s] = 0; inq[s] = true; enq[s]++;

    while (!q.empty()) {
        int a = q.front(); q.pop();
        inq[a] = false;
        for (int b = head[a]; b; b = nex[b]) 
            if (d[a] + w[b] < d[to[b]]) {
                d[to[b]] = d[a] + w[b];
                pre[to[b]] = a;
                if (!inq[to[b]]) {
                    q.push(to[b]);
                    enq[to[b]]++; if (enq[to[b]] >= n) return false;
                    inq[to[b]] = true;
                }
            }
    }
    return true;
}

int main() {
    int a, b, c;
    printf("Enter vertex number, edge number & source point index:\n");
    scanf("%d%d%d", &n, &m, &s);
    printf("Enter information of %d edge(s):\n", m);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    if (SPFA()) {
        for (int i = 1; i <= n; i++)
            if (i != s) {
                printf("Distance between source point to point %d is %d.\n", i, d[i]);
                int p = i;
                stack<int> ans;
                printf("    Path: %d", s);
                while (s != p) ans.push(p), p = pre[p];
                while (!ans.empty()) printf(" -> %d", ans.top()), ans.pop();
                printf("\n");
            }
    } else printf("The graph has negative circle!\n");
    
    return 0;
}

「重拾最短路」可参考 61mon.com 的博客:

  1. Dijkstra (Link link)
  2. Bellman-Ford (Link link)
  3. SPFA (Link link)
  4. 总结 (Link link)

Reference

  1. internal (blog/83) by SJoshua
  2. Stack Overflow. Trying to understand Dijkstra's Algorithm[DB/OL]. Link link, 2017-08-04
posted @ 2017-08-07 09:16  greyqz  阅读(255)  评论(0编辑  收藏  举报