6.1 图

作者: 月影追猎者 | 来源:发表于2020-07-20 15:14 被阅读0次

    邻接、关联
    G = (V; E),vertex: n = |V|,edge | arc: e = |E|
    同一边的两个顶点彼此邻接(adjacency)
    同一顶点自我邻接,构成自环(self-loop)
    不含自环为简单图(simple graph)
    顶点与其所属边彼此关联(incidence)
    与同一顶点关联的边数称作度(degree / valency)

    无向、有向
    若邻接顶点u与v次序无所谓,则(u, v)为无向边(undirected edge)
    所有边均无方向的图为无向图(undigraph)
    有向图(digraph)中均为有向边(directed edge),u、v分别称作边(u, v)的尾(tail)、头(head)
    无向边、有向边并存的图,称作混合图(mixed graph)

    路径、环路
    路径π = <v0, v1, ..., vk>
    长度|π| = k
    简单路径 若i ≠ j,则vi ≠ vj
    环路 v0 = vk
    有向无环图(DAG, Directed Acyclic Graph)
    欧拉环路(Eulerian tour) |π| = |E|,各边恰好出现一次
    哈密尔顿环路(Hamiltonian tour) |π| = |V|,各顶点恰好出现一次

    Graph模板类

    template <typename Tv, typename Te> class Graph { // 顶点类型、边类型
    private:
        void reset() { // 所有顶点、边的辅助信息复位
            for (int i = 0; i < n; i++) { // 顶点
                status(i) = UNDISCOVERED;
                dTime(i) = -1;
                fTime(i) = -1;
                parent(i) = -1;
                priority(i) = INT_MAX;
                for (int j = 0; j < n; j++) // 边
                    if (exists(i, j))
                        status(i, j) = UNDETERMINED;
            }
        }
    public:
        /* ... 顶点操作、边操作、图算法 ... */
    }
    

    邻接矩阵(adjacency matrix),以二维矩阵记录顶点之间的邻接关系
    关联矩阵(incidence matrix),以二维矩阵记录顶点与边之间的关联关系

    顶点

    typedef enum {UNDISCOVERED, DISCOVERED, VISITED} VStatus;
    template <typename Tv> struct Vertex { // 顶点对象(未严格封装)
        Tv data; // 数据
        int inDegree; // 出度
        int outDegree; // 入度
        VStatus status; // 状态
        int dTime;
        int fTime; // 时间标签
        int parent; // 在遍历树中的父节点
        int priority; // 在遍历树中的优先级(最短通路、极短跨边等)
        Vertex(Tv const & d): // 构造新顶点
            data(d), inDegree(0), outDegree(0), status(UNDISCOVERED), dTime(-1), fTime(-1), parent(-1), priority(INT_MAX) {}
    };
    

    typedef enum {UNDETERMINED, TREE, CROSS, FORWARD, BACKWARD} EStatus;
    template <typename Te> struct Edge { // 边对象(未严格封装)
        Te data; // 数据
        int weight; // 权重
        EStatus status; // 类型
        Edge(Te const & d, int w): // 构造新边
            data(d), weight(w), status(UNDETERMINED) {}
    };
    

    GraphMatrix

    template<typename Tv, typename Te> class GraphMatrix: public Graph<Tv, Te> {
    private:
        Vector<Vertex<Tv>> V; // 顶点集
        Vector<Vector<Edge<Te>*>> E; // 边集
    public:
        /* 操作接口:顶点相关、边相关… */
        GraphMatrix() { // 构造
            n = 0;
            e = 0;
        }
        ~GraphMatrix() {
            for (int j = 0; j < n; j++)
                for (int k = 0; k < n; k++)
                    delete E[j][k]; // 清除所有动态申请的边记录
        }
    };
    

    顶点静态操作

    Tv & vertex(int i) { // 数据
        return V[i].data;
    }
    int inDegree(int i) { // 入度
        return V[i].inDegree;
    }
    int outDegree(int i) { // 出度
        return V[i].outDegree;
    }
    Vstatus & status(int i) { // 状态
        return V[i].status;
    }
    int & dTime(int i) { // 时间标签dTime
        return V[i].dTime;
    }
    int & fTime(int i) { // 时间标签fTime
        return V[i].fTime;
    }
    int & parent(int i) { // 在遍历树中的父节点
        return V[i].parent;
    }
    int & priority(int i) { // 优先级数
        return V[i].priority;
    }
    int nextNbr(int i, int j) { // 若已枚举至相邻顶点j,则转向下一相邻顶点
        while ((-1 < j) && !exists(i, --j)); // 逆向顺序查找
        return j;
    }
    int firstNbr(int i) { // 首个相邻顶点
        return nextNbr(i, n);
    }
    

    边操作

    bool exists(int i, int j) { // 判断边(i, j)是否存在
        return (0 <= i) && (i < n) && (0 <= j) && (j < n) && E[i][j] != NULL; // 短路求值
    }
    Te & edge(int i, int j) { // 边(i, j)的数据
        return E[i][j] -> data;
    }
    Estatus & status(int i, int j) { // 边(i, j)的状态
        return E[i][j] -> status;
    }
    int & weight(int i, int j) { // 边(i, j)的权重
        return E[i][j] -> weight;
    }
    void insert(Te const& edge, int w, int i, int j) { // 插入(i, j, w)
        if (exists(i, j)
            return; // 忽略已有边
        E[i][j] = new Edge<Te>(edge, w); // 创建新边
        e++; // 更新边计数
        V[i].outDegree++; // 更新关联顶点i的出度
        V[j].inDegree++; // 更新关联顶点j的入度
    }
    Te remove(int i, int j) { // 删除顶点i与j之间的联边
        Te eBak = edge(i, j) // 备份边(i, j)的信息
        delete E[i][j];
        E[i][j] = NULL; // 删除边(i, j)
        e--; // 更新边计数
        V[i].outDegree--; // 更新关联顶点i的出度
        V[j].inDegree--; // 更新关联顶点j的入度
        return eBak; // 返回被删除边的信息
    }
    

    顶点动态操作

    int insert(Tv const & vertex) { // 插入顶点,返回编号
        for (int j = 0; j < n; j++) {
            E[j].insert(NULL);
            n++;
        }
        E.insert(Vector<Edge<Te>*>(n, n, NULL));
        return V.insert(Vertex<Tv>(vertex));
    }
    Tv remove(int i) { // 删除顶点及其关联边,返回该顶点信息
        for (int j = 0; j < n; j++) {
            if (exists(i, j)) { // 删除所有出边
                delete E[i][j];
                V[j].inDegree--;
            }
        }
        E.remove(i);
        n--; // 删除第i行
        for (int j = 0; j < n; j++) {
            if (exists(j, i)) { // 删除所有入边及第i列
                delete E[j].remove(i);
                V[j].outDegree--;
            }
        }
        Tv vBak = vertex(i); // 备份顶点i的信息
        V.remove(i); // 删除顶点i
        return vBak; // 返回被删除顶点的信息
    }
    

    邻接矩阵
    直观,易于理解与实现
    适用范围广(digraph / network / cyclic / ...),尤其适用于稠密图(dense graph)
    判断两点之间是否存在联边O(1)
    获取顶点出/入度数O(1)
    添加、删除边后更新度数O(1)
    扩展性(scalability),得益于Vector良好的空间控制策略,空间溢出等情况可透明处理
    空间Θ(n2),与边数无关
    平面图(planar graph),可嵌入平面的图,不相邻的边不相交

    // 欧拉公式(Euler's formula)
    v - e + f - c = 1, for any PG
    // v,顶点数
    // e,边数
    // f,区域面片数
    // c,连通域数
    

    平面图e ≤ 3n - 6 = O(n) << n2,此时空间利用率约1/n

    相关文章

      网友评论

          本文标题:6.1 图

          本文链接:https://www.haomeiwen.com/subject/yfflkktx.html