6.1 图

邻接、关联
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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352