跳转至

定义:\(G(V,E)\)

  • 无向图:\((v_i,v_j)=(v_j,v_i)\)
  • 有向图:\(<v_i,v_j>\not=<v_j,v_i>\)

约定:

  • 不研究重边、自环

完全图:a graph that has the maximum number of edges

邻接:

  • 无向图:\(v_i,v_j\) are adjacent
  • 有向图:\(v_i\) is adjacent to \(v_j\)

子图:\(G'\subset G\iff V(G')\subset V(G),E(G')\subset E(G)\)

路径:\(\set{v_p,v_{i1},\cdots,v_q}\quad s.t.(v_p,v_{i1}),\cdots,(v_{in},v_q)\)

路径长度:路径边数

简单路径:\(v_{i1},\cdots,v_{in}\)互不相同

环:路径上\(v_p=v_q\)

连接:存在\(v_i\to v_j\)的路径

连通:

  • 强连通:任意两点都连通
    • 强连通分量:最大强连通子图
  • 弱连通:不考虑有向性时连通

树:连通的无环图(acyclic)

DAG:有向无环图

度:

  • 无向图:连边数
  • 有向图:出度、入度

    定理:\(e=(\sum_{i=0}^{n-1}d_i)/2\)

表示:

  • 邻接矩阵
\[ \text{adj_mat}[i][j]=\left\{\begin{array}{ll}1,&(v_i,v_j)\in E(G)\\0,&\text{otherwise}\end{array}\right. \]

性质:

  • 无向图邻接矩阵对称
  • 无向图:\(\text{degree}(i)=\sum_{j=0}^{n-1}\text{adj_mat[i][j]}\)
  • 有向图:
    • 出度:\(\text{degree}(i)=\sum_{j=0}^{n-1}\text{adj_mat[i][j]}\)
    • 入度:\(\text{degree}(i)=\sum_{j=0}^{n-1}\text{adj_mat[j][i]}\)

问题:稀疏图浪费空间

  • 邻接链表 graph[N],每条链连接顶点指向的出边

问题:无法找到指向该节点的顶点

解决:逆邻接表

  • 合并邻接表、逆邻接表:十字链表

    • 每个节点对应一条边
    • 每个节点具有两个指针域,分别存邻接表指针、逆邻接表指针
  • 多重链表

    • 每个节点对应一条边
    • 每个节点具有两个指针域,每个指针域指向自己的邻接表指针

拓扑排序

AOV网络:digraph G in which V( G ) represents activities and E( G ) represents precedence relations

前驱:i is a predecessor of j ::= there is a path from i to j

后继:i is an immediate predecessor of j ::= < i, j > \(\in\) E( G )

偏序关系:传递 + 反对称

DAG(directed acyclic graph):有向无环图

拓扑排序:A topological order is a linear ordering of the vertices of a graph such that, for any two vertices, i, j, if i is a predecessor of j in the network then i precedes j in the linear ordering

网络的拓扑排序不唯一

目标:Test an AOV for feasibility, and generate a topological order if possible

朴素算法:\(T=O(|V|^2)\)

void Topsort( Graph G )
{   int  Counter;
    Vertex  V, W;
    for ( Counter = 0; Counter < NumVertex; Counter ++ ) {
    V = FindNewVertexOfDegreeZero( );
    if ( V == NotAVertex ) {
        Error ( Graph has a cycle );   break;  }
    TopNum[ V ] = Counter; /* or output V */
    for ( each W adjacent to V )
        Indegree[ W ]   ;
    }
}

图算法常见思路:根据当前点信息修改相连点的信息

BFS:用队列维护顶点集

void Topsort(Graph G) {
    Queue Q;
    int Counter = 0;
    Q = CreateQueue(NumVertex);
    MakeEmpty(Q);
    for (each vertex V)
        if (Indegree[V] == 0) Enqueue(V, Q);
    while (!isEmpty(Q)) {
        V = Dequeue(Q);
        TopNum[V] = ++Counter;
        for (each W adjacent to V)
            if (--Indegree[W] == 0) Enquene(W, Q);
    }
    if (Counter != NumVertex) Error ("Graph has a cycle");
    DisposeQueue(Q);
}

应用:有向图判环

最短路算法

路径长度:给定图\(G=(V, E)\), 边权\(c(e)\quad\forall e\in E(G)\),路径\(P\)长度为\(\sum\limits_{e_i\subset P}c(e_i)\)

最短路问题:给定带权图\(G=(V,E)\)和源点\(s\),找\(s\)到任何其它点的最短路径

存在负环时,最短路为\(-\infty\)

无权最短路:BFS

实现:

  • dist[] \(s\to v_i\)距离
  • Known[] vis数组
  • Path[] 记录前驱形成路径 朴素算法:\(T=O(|V|^2)\)
    void Unweighted(Table T) {
        int CurrDist;
        Vertex V, W;
        for (CurrDist = 0; CurrDist < NumVertex; CurrDist ++) {
            for (each vertex V)
                if (!T[V].Known && T[V].Dist == CurrDist) {
                    T[V].Known = true;
                    for (each W adjacent to V)
                        if (T[W].Dist == Infinity) {
                            T[W].Dist = CurrDist + 1;
                            T[W].Path = V;
                        }
                }
        }
    }
    

BFS:\(T=O(|V|+|E|)\)

void Unweighted(Tabel T) {
    Queue Q;
    Vertex V, W;
    Q = CreateQueue(NumVertex); MakeEmpty(Q);
    Enqueue(S, Q);
    while (!IsEmpty(Q)) {
        V = Dequeue(Q);
        T[V].Known = true;
        for (each W adjacent to V) {
            if (T[W].Dist == Infinity) { // 队列按搜索先后顺序放入元素,队头一定是最近的元素
                T[W].Dist = T[V].Dist + 1;
                T[W].Path = V;
                Enqueue(W, Q);
            }
        }
    }
    DisposeQueue(Q);
}

带权最短路:Dijkstra

步骤:

  1. 划分已知点集,未知点集
  2. 从已知点集取具有最短路径的点V
  3. 找到与V相连的属于未知点集的点,松弛

每当一个点状态更新时,需同步更新相连点的状态

void Dijkstra(Table T) {
    Vertex V, W;
    for (;;) {
        V = smallest unknown distance vertex;
        if (V == NotAVertex) break;
        T[V].Known = true;
        for (each W adjacent to V)
            if (!T[W].Known)
                if (T[V].Dist + C[V][W] < T[W].Dist) {
                    Decrease(T[W].Dist to T[V].Dist + C[V][W]);
                    T[W].Path = V; // 记录用于更新的点
                }
        }
}

实现:

  1. 朴素实现 \(T=O(|V|^2+|E|)\)
  2. 建堆找最小值 \(T=O(|V|\log|V|+|E|\log|V|)=O(|E|\log|V|)\)

    Dijkstra算法无法处理负环图

负权最短路:SPFA

  • 可重复入队

void WeightedNegative(Table T) {
    Queue Q;
    Vertex V, W;
    Q = CreateQueue(NumVertex); MakeEmpty(Q);
    Enqueue(S, Q);
    while (!IsEmpty(Q)) {
        V = Dequeue(Q);
        for (each W adjacent to V)
            if (T[V].Dist + Cvw < T[W].Dist) {
                T[W].Dist = T[V].Dist + Cvw;
                T[W].Path = V;
                if (W is not already in Q)
                    Enqueue(W, Q);
            }
    }
    DisposeQueue(Q);
}
当出现负环时,进入死循环

关键路径

AOE网络:Activity On Edge Network, 顶点作为活动结束的信号

  • EC Time:任务最早完成的时间
  • LC Time:任务最晚需要被完成的时间
  • 依赖关系使用边权为0的边表示

转移:

\[ EC[w]=\max_{(v,w)\in E}\set{EC[v]+C_{v,w}} \]

正向更新

image.png|450

\[ LC[v]=\min_{(v,w)\in E}\set{LC[w]-C_{v,w}} \]

反向更新

image.png|450

关键路径:EC == LC所有点形成的路径

全图最短路:Floyd(Dp) \(T=O(|V|^3)\)

  • 状态:\(V_i\stackrel{S}\longrightarrow V_j\)
  • 边界:\(V_i\stackrel{\emptyset}\longrightarrow V_j=\left\{\begin{array}{ll}\infty,&i\not\to j,\\w[i][j],&i\to j.\end{array}\right.\)
  • 转移:\(V_i\stackrel{\set{1,\cdots,k+1}}\longrightarrow V_j=\min\set{V_i\stackrel{\set{1,\cdots,k}}\longrightarrow V_j,V_i\stackrel{\set{1,\cdots,k}}\longrightarrow V_{k+1}\stackrel{\set{1,\cdots,k}}\longrightarrow V_{j}}\)

网络流

概念:找源点\(s\)到汇点\(t\)的最大流量

image.png|200

  • 原图\(G\)
  • 流量网络\(G_f\)
  • 残差网路\(G_r\)

image.png|600

算法:

  1. \(G_r\)上找增广路\(s\to t\)
  2. 更新流量,残差
  3. \(G_r\)上仍存在增广路,回到1

复杂度:\(T=O(f\cdot|E|)\)

问题:特赦情况退化

image.png|300

解决:

  • 每次找最大增广路:\(T=T_{增广}*T_{找路}=O(|E|\log\text{cap}_\max)*O(|E|\log|V|)=O(|E|^2\log|V|)\)
  • 每次找边最少增广路:\(T=O(|E|)*O(|E|\cdot|v|)=O(|E|^2|V|)\)

最小生成树

概念:A spanning tree of a graph G is a tree which consists of V( G ) and a subset of E( G )

思路:贪心

  • 找最小边,找\(|V|-1\)
  • 边不能成环

Kruskal \(T=O(|E|\log|E|)\)

void Kruskal ( Graph G )
{   T = { } ;
    while  ( T contains less than |V|-1 edges 
                   && E is not empty ) {
        choose a least cost edge (v, w) from E ;
        delete (v, w) from E ;
        if  ( (v, w) does not create a cycle in T )     
    add (v, w) to T ;
        else     
    discard (v, w) ;
    }
    if  ( T contains fewer than |V|-1 edges )
        Error ( No spanning tree ) ;
}
数据结构优化:

  • 找最小边:堆
  • 回路判断:并查集

Prim:始终找外部节点中距离最近的点加入生成树

Kruskal对边,Prim对点

BFS

void BFS() {
    Queue Q;
    Enqueue(S, Q);
    while (!isEmpty(Q)) {
        V = Dequeue(Q);
        vis(V);
        for (each W adjacent to V) {
            Enqueue(W, Q);
        }
    }
}

DFS

void DFS(Vertex V) {
    visited[V] = true;
    for (each W adjacent to V) 
        if (!visited[W]) DFS(W);
}

访问所有点:

for (each V in G) {
    if (!vis[V]) DFS(V);
}
应用:连通分量计数

双连通性

概念:

  • 割点:移去割点后图连通性改变
  • 双连通:不存在割点的图
  • 双连通分量:最大双连通子图

求双连通分量:

  • DFS生成树:利用DFS产生生成树,并打上DFS序 image.png|500

    结论:所有不在DFS树上的边都是回边,即这些边都连接祖先与子孙,并不连接同级节点

image.png|350

  • 割点判据:
    • 有两个儿子的根节点是割点
    • 有子树的节点,子树通过非树边连接的最高点(DFS序更小的祖先)不超过该节点

刻画:low(u):该节点及其子树能连通的最高点(最小DFS序)

\[ Low(u)=\min\set{Num(u),\min\set{Num(w)|(u,w)\in\text{back edge}},\min\set{Low(w)|w\in\text{child}(u)}} \]

判据:

  1. \(u\)为根,且\(u\)至少有两个儿子
  2. \(u\)非根,\(\exists\) 1儿子 s.t. Low(child) \(\geqslant\) Num(u)

实现:

  • DFS过程中打DFS序,更新Low
  • 动态判断割点,并直接输出双连通分量

欧拉回路

定理:

  • An Euler circuit is possible only if the graph is connected and each vertex has an even degree.
  • An Euler tour is possible if there are exactly two vertices having odd degree. One must start at one of the odd-degree vertices.

算法:不断DFS,若走到重复点,则再开始一个DFS,直到所有边被访问