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