Skip to content

Graph

Definition

\[ \displaylines{ G = (V, E) } \]

We only considers simple graphs that have No self-Connection, No parallel edge.

  • Complete graph: contains all possible edges, \(E = \frac {V(V-1)}{2}\)
  • neighbors: vertices connected by an edge.
  • degree: in and out. leaf's degree is 0.
  • subgraph: \(V' \in V, E' \in E\), and all vertices connected by \(E'\) are in \(V'\).
  • path: \(V_p \rightarrow ... \rightarrow V_q\)
  • simple path: 除了起点终点,其他顶点无重复。
  • Simple cycle:起点终点相同的简单路径。
  • DAG:Directed Acyclic Graph. 有向无环图。
  • 有根图:有向图中,从顶点V可以抵达其他所有顶点,则称之为根。
  • 连通图:无向图任意两个顶点连通,则称为连通图。
  • 强连通图:有向图任意两个顶点强联通,则成为强连通图。
  • 连通分量:无向图的最大连通子图
  • 强连通分量:有向图的最大连通子图。
  • 网络:带权连通图。
  • 自由树:无回路的连通无向图,且\(E=V-1\).

Storage

  • 邻接矩阵 Adjacency Matrix

    空间代价\(O(|V|^2)\). (可以稀疏矩阵优化)

    • 无向图:对称。
    • 有向图:不一定对称。
  • 邻接表

    最常使用的结构,空间代价小,但失去了邻接矩阵的性质。

    • 无向图空间代价\(O(|V|+2|E|)\).
    • 有向图空间代价\(O(|V|+|E|)\),只记录入边表或出边表。
  • 十字链表

    • 顶点表,边链表

Traversal

  • 问题:非连通,回路。

    使用VIS标记来避免回路。

  • DFS

    时间复杂度与存储结构的空间复杂度同数量级。

  • BFS

    时间复杂度与存储结构的空间复杂度同数量级。

  • 拓扑排序

    A Linear order that can finish the task as well as satisfying all pre request.

    Usually Not Unique.

    Can only be performed on a DAG. (有拓扑排序等价于是DAG)

Shortest Path

  • Dijkstra

    Single source, Directed or Undirected, Non-negative edge weight, Greedy.

    \(O((V+E)logE)\) or \(O(V^2 + E)\)

  • Floyd

    All pairs, Dynamic programming.

    \(O(V^3)\)

Minimum cost Spanning Tree (MST)

MST may be not unique, but the min cost is the same.

  • Prim

    \(O((V+E)logV)\).

    The structure is very similar to Dijkstra, but here we find the shortest edge to current tree, instead of the shortest point to source point.

    Need to assign the Root.

  • Kruskal

    \(O(ElogE)\)

    Use disjoint set to merge points from shortest edge.

Toy Graph

#include <iostream>
#include <cstring>
#include <set>
#include <queue>
#include <vector>

using namespace std;

const int maxv = 100;
const int maxw = 0x3f3f3f3f; // if use floyd, 2*maxw must not overflow.
int V;

struct node {
    int ind, outd;
    int data;
    node() {
        ind = 0;
        outd = 0;
        data = 0;
    }
} nodes[maxv];

struct edge {
    int f, t, w;
    edge() {}
    edge(int f, int t, int w) :f(f), t(t), w(w) {}
    bool operator < (const edge& b) const { return w > b.w; }
};

vector<edge> G[maxv];
int vis[maxv];

void insert_edge(int a, int b, int w) {
    G[a].push_back(edge(a, b, w));
    //G[b].push_back(edge(b, a, w));
    nodes[a].outd++;
    nodes[b].ind++;
}

void visit(int n) {
    cout << "visit:" << n << endl;
}

// clear vis first
void dfs(int rt) {
    if (vis[rt]) return;
    vis[rt] = 1;
    int len = G[rt].size();
    for (int i = 0; i < len; i++) {
        dfs(G[rt][i].t);
    }
    visit(rt);
}

void bfs(int rt) {
    memset(vis, 0, sizeof(vis));
    queue<int> q;
    q.push(rt);
    while (!q.empty()) {
        int p = q.front(); q.pop();
        if (vis[p]) return;
        vis[p] = 1;
        visit(p);
        int len = G[p].size();
        for (int i = 0; i < len; i++) {
            q.push(G[p][i].t);
        }
    }
}

// output one of the toposorts.
// 有向图判断有无环的方法之一。
void toposort_bfs() {
    memset(vis, 0, sizeof(vis));
    queue<int> q;
    for (int i = 0; i < V; i++)
        if (nodes[i].ind == 0)
            q.push(i);
    while (!q.empty()) {
        int v = q.front(); q.pop();
        visit(v);
        vis[v] = 1;
        int len = G[v].size();
        for (int i = 0; i < len; i++) {
            nodes[G[v][i].t].ind--;
            if (nodes[G[v][i].t].ind == 0) q.push(G[v][i].t);
        }
        // vertices in and after loops are not visited.
        for (int i = 0; i < maxv; i++) {
            if (!vis[i]) {
                cout << "Loop Detected" << endl;
                break;
            }
        }
    }
}

// can't detect loop! 
vector<int> res; // reverse-ordered
void _toposort_dfs(int n) {
    vis[n] = 1;
    for (int i = 0; i < G[n].size(); i++) {
        int m = G[n][i].t;
        if (!vis[m])
            _toposort_dfs(m);
    }
    res.push_back(n);
}

void toposort_dfs() {
    memset(vis, 0, sizeof(vis));
    for (int i = 0; i < V; i++)
        if (!vis[i])
            _toposort_dfs(i);
    for (int i = res.size() - 1; i >= 0; i--)
        visit(res[i]);
}


int dist[maxv], parent[maxv];

bool cmp(int a, int b) {
    return dist[a] > dist[b];  // min heap should use greater<>
}

// O((V+E)logE),单源最短路,求起点s到任意其他点t的最短距离dist[t]
/*
实现方式:最小堆+不删除旧值。
其他方式:
    扫描dist来寻找下一个最小元素。 O(V^2 + E)
    最小堆+删除旧元素:不方便用std优先队列实现。O(VlogV)?
不连接的点距离为maxw。可以用来检查图的连接。
*/
void dijkstra(int s) {
    for (int i = 0; i < V; i++) {
        vis[i] = 0;
        parent[i] = -1;
        dist[i] = maxw;
    }
    dist[s] = 0;
    priority_queue<int, vector<int>, bool(*)(int,int)> que(cmp);  // note the use of function cmp
    que.push(s);
    while (!que.empty()) {
        int u = que.top(); que.pop();
        if (vis[u]) continue; // prevent old element rescanning, necessary.
        vis[u] = 1;
        for (int i = 0; i < G[u].size(); i++) {
            int v = G[u][i].t;
            if (vis[v]) continue;  // this can be deleted. Simple pruning.
            if (dist[v] > dist[u] + G[u][i].w) {
                dist[v] = dist[u] + G[u][i].w;
                parent[v] = u;
                que.push(v);
            }
        }
    }
}

// Simplified Version !!!
int d[maxn];
#define P pair<int,int>

void dijkstra(int s) {
    memset(d, 0x3f, sizeof(d));
    priority_queue<P, vector<P>, greater<P>> q; // use pair<int,int> 
    q.push(P(0, s));
    d[s] = 0;
    while (!q.empty()) {
        P p = q.top(); q.pop();
        int u = p.second;
        if (d[u] < p.first) continue; // outdated records
        for (int i = 0; i < G[u].size(); i++) {
            edge& e = G[u][i];
            int v = e.t;
            if (d[v] > d[u] + e.w) {
                d[v] = d[u] + e.w;
                q.push(P(d[v], v));
            }
        }
    }
}

// a STL version
vector<vector<pair<int, int>>> G(n+1);
for (auto& p: times) {
    G[p[0]].push_back({p[1], p[2]}); // p = {f, t, w}
}
vector<int> d(n+1, 0x7fffffff);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q; // top i smallest
q.push({0, k});
d[k] = 0;
while (!q.empty()) {
    auto p = q.top(); q.pop();
    int u = p.second;
    //cout << "dij " << u << " " << d[u] << endl;
    if (d[u] < p.first) continue;
    for (auto& [v, w]: G[u]) {
        if (d[v] > d[u] + w) {
            d[v] = d[u] + w;
            q.push({d[v], v});
        }
    }
}

// floyd
int dist2[maxv][maxv], parent2[maxv][maxv];
void floyd() {
    memset(dist2, maxw, sizeof(dist2));
    memset(parent2, -1, sizeof(parent2));
    // init the graph's edge
    for (int u = 0; u < V; u++) {
        dist2[u][u] = 0;
        for (int i = 0; i < G[u].size(); i++) {
            int v = G[u][i].t;
            dist2[u][v] = G[u][i].w;
            parent2[u][v] = u;
        }
    }
    // O(n^3), the order is i->u->j
    for (int u = 0; u < V; u++) {
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                if (dist2[i][j] != maxw && dist2[i][j] > dist2[i][u] + dist2[u][j]) {
                    dist2[i][j] = dist2[i][u] + dist2[u][j];
                    parent2[i][j] = parent2[u][j];
                }
            }
        }
    }
}

vector<edge> MST;
// only for connected graph, so check vis later for whether it's connected.
// O((V+E)logE), dense graph.
void prim(int s) {
    MST.clear();
    memset(vis, 0, sizeof(vis));
    vis[s] = 1;
    priority_queue<edge> que;  
    for(int i=0; i<G[s].size(); i++) que.push(G[s][i]);
    while (!que.empty()) {
        edge e = que.top(); que.pop();
        if (vis[e.t]) continue; // prevent out of date element
        vis[e.t] = 1;
        MST.push_back(e);
        for(int i=0; i<G[e.t].size(); i++){
            edge ee = G[e.t][i];
            if(!vis[ee.t]) que.push(ee);
        }
    }
}

// disjoint set for kruskal
int par[maxn];
void init(int n) {
    for (int i = 0; i <= n; i++) par[i] = i;
}
int getpar(int x) {
    if (par[x] == x) return x;
    return par[x] = getpar(par[x]);
}
void merge(int x, int y) {
    par[getpar(x)] = getpar(y);
}
bool query(int a, int b){
    return getpar(a) == getpar(b);
}


// kruskal MST, O(ElogE), sparse graph.
void kruskal(){
    MST.clear();
    priority_queue<edge> que;
    // push all edges
    for(int i=0; i<V; i++)
        for(int j=0; j<G[i].size(); j++)
            que.push(G[i][j]);
    int n = V;
    init(n);
    // merge from shortest edge until only one class left.
    while(n>1){
        edge e = que.top(); que.pop();
        if(!query(e.f, e.t)){
            merge(e.f, e.t);
            MST.push_back(e);
            n--;
        }
    }
}


int main() {
    V = 4;
    insert_edge(0, 2, 1);
    insert_edge(0, 1, 3);
    insert_edge(0, 3, 1);
    insert_edge(1, 3, 6);
    insert_edge(2, 3, 2);
    kruskal();
    for(int i=0; i<V-1; i++){
        cout<<MST[i].f<<" "<<MST[i].t<<" "<<MST[i].w<<endl;
    }
}

Examples

  • ウサギと桜

    弗洛伊德+路径找回。

#include <iostream>
#include <sstream>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>

using namespace std;

const int maxv = 30;
int V, E;

struct edge {
    int f, t, w;
    edge() {}
    edge(int f, int t, int w) :f(f), t(t), w(w) {}
    bool operator < (const edge& b)const {
        return w > b.w;
    }
};

vector<edge> edges;
vector<int> G[maxv];

void insert_edge(int f, int t, int w) {
    edges.push_back(edge(f,t,w));
    int s = edges.size() - 1;
    G[f].push_back(s);
}

int dist[maxv][maxv];
int parent[maxv][maxv];

void floyd() {
    memset(dist, 0x3f, sizeof(dist));
    memset(parent, -1, sizeof(parent));
    for (int i = 1; i <= V; i++) {
        dist[i][i] = 0; // self-connection
        for (int j = 0; j < G[i].size(); j++) {
            int e = G[i][j];
            int v = edges[e].t;
            dist[i][v] = edges[e].w;
            parent[i][v] = e;
        }
    }
    for (int u = 1; u <= V; u++) {
        for (int i = 1; i <= V; i++) {
            for (int j = 1; j <= V; j++) {
                if (dist[i][j] > dist[i][u] + dist[u][j]) {
                    dist[i][j] = dist[i][u] + dist[u][j];
                    parent[i][j] = parent[u][j];
                }
            }
        }
    }
}

map<string, int> m;
map<int, string> mm;

int main() {
    cin >> V;
    string s, ss;
    int w;
    for (int i = 1; i <= V; i++) {
        cin >> s;
        m[s] = i;
        mm[i] = s;
    }
    cin >> E;
    for (int i = 0; i < E; i++) {
        cin >> s >> ss >> w;
        insert_edge(m[s], m[ss], w);
        insert_edge(m[ss], m[s], w);
    }
    floyd();
    int R;
    vector<int> path;
    cin >> R;
    for (int i = 0; i < R; i++) {
        cin >> s >> ss;
        cout << s;
        int u = m[s], v = m[ss];
        path.clear();
        while (u != v) {
            //cout << mm[u] << " to " << mm[v] << endl;
            int e = parent[u][v];
            path.push_back(e);
            v = edges[e].f;
        }
        for (int i = path.size() - 1; i >= 0; i--) {
            edge e = edges[path[i]];
            cout << "->(" << e.w << ")->" << mm[e.t];
        }
        cout << endl;
    }
}
  • 地震之后

    有向图最小树形图,朱刘算法。

    #include <iostream>
    #include <sstream>
    #include <string>
    #include <cstring>
    #include <algorithm>
    #include <vector>
    #include <queue>
    #include <map>
    
    using namespace std;
    
    const int maxv = 105;
    const int inf = 0x3f3f3f3f;
    int V, E;
    
    struct node {
      double x, y;
    } nodes[maxv];
    
    double dist(const node& a, const node& b) {
      return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2));
    }
    
    struct edge {
      int f, t;
      double w;
      edge() {}
      edge(int f, int t, double w) :f(f), t(t), w(w) {}
      bool operator < (const edge& b)const {
          return w > b.w;
      }
    };
    
    vector<edge> edges;
    vector<int> G[maxv];
    
    void init() {
      for (int i = 0; i < V; i++) G[i].clear();
      edges.clear();
    }
    
    void insert_edge(int f, int t, double w) {
      edges.push_back(edge(f,t,w));
      int s = edges.size() - 1;
      G[f].push_back(s);
    }
    
    int x, y;
    double in[maxv];
    int vis[maxv], pre[maxv], id[maxv];
    
    // v starts from 0
    double zhuliu(int s) {
      double res = 0;
      int v = V; // localize
      while (true) {
          // in[] is the longest in edge's weight
          for (int i = 0; i < v; i++) in[i] = inf;
          // calc in[]
          for (int i = 0; i < edges.size(); i++) {
              edge& e = edges[i];
              if (e.f != e.t && e.w < in[e.t]) {
                  pre[e.t] = e.f;
                  in[e.t] = e.w;
              }
          }
          // check non-connectivity
          for (int i = 0; i < v; i++)
              if (s != i && in[i] == inf)
                  return -1;
          // id[] is scc id
          memset(id, -1, sizeof(id));
          memset(vis, -1, sizeof(vis));
          // set root
          in[s] = 0;
          // count scc
          int scc = 0;
          // 
          for (int i = 0; i < v; i++) {
              res += in[i];
              int v = i;
              // find scc 
              while (vis[v] != i && id[v] == -1 && v != s) {
                  vis[v] = i;
                  v = pre[v];
              }
              // assign scc id
              if (v != s && id[v] == -1) {
                  for (int u = pre[v]; u != v; u = pre[u]) id[u] = scc;
                  id[v] = scc++;
              }
          }
          // no scc, MST built
          if (scc == 0) break;
          // non-connected single points are also scc
          for (int i = 0; i < v; i++) 
              if (id[i] == -1)
                  id[i] = scc++;
          // shrink scc
          for (int i = 0; i < edges.size(); i++) {
              edge& e = edges[i];
              int v = e.t;
              e.f = id[e.f];
              e.t = id[e.t];
              if (e.f != e.t) e.w -= in[v];
          }
          v = scc;
          s = id[s];
      }
      return res;
    }
    
    int main() {
      while (cin >> V >> E) {
          init();
          for (int i = 0; i < V; i++) {
              cin >> x >> y;
              nodes[i].x = x;
              nodes[i].y = y;
          }
          for (int i = 0; i < E; i++) {
              cin >> x >> y;
              x--; y--;
              insert_edge(x, y, dist(nodes[x], nodes[y]));
          }
          double ans = zhuliu(0);
          if (ans == -1) printf("NO\n");
          else printf("%.2f\n", ans);
      }
    }