图的基本概念,图的存储--邻接矩阵、邻接表、十字链表、邻接多重表

1 图的定义

一个图(G)定义为一个偶对(V,E),记为G=(V,E)。
V是顶点(Vertex)的非空有限集合,记为V(G)。
E是无序集V&V的一个子集,记为E(G),其元素是图的弧(Arc)。
将顶点集合为空的图称为空图。
弧:表示两个顶点v和w之间存在一个关系,用顶点偶对<v,w>表示。

2 图的重要术语

(1)无向图:
在一个图中,如果任意两个顶点构成的偶对(v,w)∈E 是无序的,即顶点之间的连线是没有方向的,则称该图为无向图。

(2)有向图:
在一个图中,如果任意两个顶点构成的偶对(v,w)∈E 是有序的,即顶点之间的连线是有方向的,则称该图为有向图。一般记作<v,w>

(3)完全无向图:
在一个无向图中,如果任意两顶点都有一条直接边相连接,则称该图为完全无向图。在一个含有 n 个顶点的完全无向图中,有n(n-1)/2条边。

(4)完全有向图:
在一个有向图中,如果任意两顶点之间都有方向互为相反的两条弧相连接,则称该图为完全有向图。在一个含有 n 个顶点的完全有向图中,有n(n-1)条边。

(5)稠密图、稀疏图:
若一个图接近完全图,称为稠密图;称边数很少(e<n\log n)的图为稀疏图。

(6)顶点的度、入度、出度:
顶点的度(degree)是指依附于某顶点v_i的边数,通常记为TD(v_i)。
在无向图中,所有顶点度的和是图中边的2倍。
在有向图中,要区别顶点的入度(Indegree)与出度(Outdegree)的概念。
顶点v_i的入度是指以顶点为终点的弧的数目,记为ID (v_i);
顶点v_i出度是指以顶点v_i为始点的弧的数目,记为 OD(v_i)。
顶点v_i的出度与入度之和称为v_i的度,记为TD(v_i)。即TD(v_i)=OD(v_i)+ID (v_i)。

(7)边的权、网图:
与边有关的数据信息称为权(weight)。在实际应用中,权值可以有某种含义。
边上带权的图称为网图或网络(network)。如果边是有方向的带权图,则就是一个有向网图。

(8)路径、路径长度:
对无向图,若从顶点v_i经过若干条边能到达v_j,则称顶点v_iv_j是连通的,又称顶点v_iv_j有路径。
对有向图,从顶点v_iv_j有有向路径,指的是从顶点v_i经过若干条有向边能到达v_j
路径上边或有向边(弧)的数目称为路径长度。

(9)简单路径、回路、简单回路:
在一条路径中,若没有重复相同的顶点,该路径称为简单路径。
第一个顶点和最后一个顶点相同的路径称为回路(环)。
除第一个顶点与最后一个顶点之外,其他顶点不重复出现的回路称为简单回路,或者简单环。

(10)子图和生成子图:
对于图 G=(V,E),G’=(V’,E’),若存在 V’是 V 的子集 ,E’是 E的子集,则称图 G’是 G 的一个子图;
若V’=V且E’是E的子集,则称图G’是G的一个生成子图。

(11)连通图、连通分量:
对无向图G=(V,E),若任意v_i,v_j 都是连通的,则称该图是连通图,否则称为非连通图。
若G是非连通图,则极大连通子图称为连通分量。
极大的含义:指的是对子图再增加图G中的其它顶点,子图就不再连通。
任何连通图的连通分量只有一个,即本身,而非连通图有多个连通分量。

(12)强连通图、强连通分量:
对于有向图来说,若图中任意一对顶点v_i,v_j均有从一个顶点v_i到另一个顶点v_j有路径,也有从v_jv_i的路径,则称该有向图是强连通图。
有向图的极大强连通子图称为强连通分量。
强连通图只有一个强连通分量,即本身。非强连通图有多个强连通分量。

(13)生成树:
一个连通图(无向图)的生成树是一个极小连通子图,它含有图中全部n个顶点和只有足以构成一棵树的n-1条边,称为图的生成树。

(14)生成森林:
有向树是只有一个顶点的入度为0,其余顶点的入度均为1的有向图。
有向图的生成森林是这样一个子图,由若干棵有向树组成,含有图中全部顶点。

3 图的存储结构和基本操作

(1)邻接矩阵法(Adjacency Matrix)
基本思想:对于有n个顶点的图,用一维数组vexs[n]存储顶点信息,用二维数组A[n][n]存储顶点之间关系的信息。该二维数组称为邻接矩阵。
在邻接矩阵中,以顶点在vexs数组中的下标代表顶点,邻接矩阵中的元素A[i][j]存放的是顶点i到顶点j之间关系的信息。

1)无向图的数组表示
①无向无权图的邻接矩阵
无向无权图其邻接矩阵是n阶对称方阵。
若两条边相连,A[i][j]=1; 若不相连A[i][j]=0。


无向图的邻接矩阵

②无向带权图的邻接矩阵
若两条边相连,A[i][j]=W_{ij},W为权值。
若两条边不相连,A[i][j]=\infty

带权无向图的邻接矩阵

③无向图邻接矩阵的特性
无向图的邻接矩阵一定是一个对称矩阵。因此,在具体存放邻接矩阵时只需存放 上(或下)三角矩阵的元素即可。
对于顶点v_i,其度数是第i行的非0元素(或非\infty元素)的个数。
无向图的边数是上(或下)三角形矩阵的非0元素(或非\infty元素)的个数。

2)有向图的数组表示
①有向无权图的邻接矩阵
若有向无权图G=(V,E)有n个顶点,则其邻接矩阵是n阶方阵:
若从v_iv_j有弧,A[i][j]=1;
若从v_iv_j没有弧,A[i][j]=0;

有向图的邻接矩阵

②有向带权图的邻接矩阵


有向带权图的邻接矩阵

③有向图邻接矩阵的特性
对于顶点v_i,第i行的非0元素(或非\infty元素)的个数是其出度OD(v_i);
第i列的非0元素(或非\infty元素)的个数是其入度ID(v_i);
邻接矩阵中非0元素(或非\infty元素)的个数就是图的弧的个数。

对于n个顶点e条边的无向图,邻接矩阵表示时有n\timesn个元素,2\timese个非0元素。
对于n个顶点e条边的有向图,邻接矩阵表示时有n\timesn个元素,e个非0元素。

3)图的邻接矩阵的操作
定义两个数组分别存储顶点信息(数据元素)和边或弧的信息(数据元素之间的关系) 。

#define INFINITY 65535//定义无穷
#define MAX_VEX 30 //最大顶点数目

typedef enum {
    DG, AG, WDG, WAG  //{有向图,无向图,带权有向图,带权无向图}
} GraphKind;

typedef struct {
    GraphKind kind; /* 图的种类标志 */
    int verxtexNum, arcNum; /* 图的当前顶点数和弧数 */
    char vexs[MAX_VEX]; //顶点集合
    int edges[MAX_VEX][MAX_VEX];//邻接矩阵
} MGraph;/* 图的结构定义 */

图的各种操作。
①图的创建

MGraph *createGraph(MGraph *G) {
    printf("请输入图的种类标志:");
    scanf("%d", &G->kind);
    G->verxtexNum = 0; /* 初始化顶点个数 */
    return (G);
}

②图的顶点定位
实际上是确定一个顶点在 vexs 数组中的位置(下标) ,其过程完全等同于在顺序存储的线性表中查找一个数据元素。

int LocateVex(MGraph *G, char ch) {
    for (int i = 0; i < G->verxtexNum; i++) {
        if (G->vexs[i] == ch) {
            return (i);
        }
    }
    return (-1); /*图中无此顶点*/
}

③向图中增加顶点
向图中增加一个顶点的操作,类似在顺序存储的线性表的末尾增加一个数据元素。

int addVertex(MGraph *G, char ch) {
    int j, k;
    if (G->verxtexNum >= MAX_VEX) {
        printf("Vertex overflow!\n");
        return (-1);
    }
    if (LocateVex(G, ch) != -1) {
        printf("Vertex has existed!\n");
        return (-1);
    }
    k = G->verxtexNum;
    G->vexs[G->verxtexNum++] = ch;
    if (G->kind == DG || G->kind == AG) {//不带权
        for (j = 0; j < G->verxtexNum; j++) {//新加一行和一列
            G->edges[j][k] = G->edges[k][j] = 0;
        }
    } else {
        for (j = 0; j < G->verxtexNum; j++) {
            G->edges[j][k] = G->edges[k][j] = INFINITY;
        }
    }
    return (k);
}

④向图中增加一条弧
根据给定的弧或边所依附的顶点,修改邻接矩阵中所对应的数组元素。

int addArc(MGraph *G, char ch1, char ch2, int weight) {
    int j = LocateVex(G, ch1);
    int k = LocateVex(G, ch2);
    if (k == -1 || j == -1) {
        printf("Vertex do not exist!\n");
        return (-1);
    }
    if (G->kind == DG || G->kind == WDG) {//有向图
        G->edges[j][k] == weight;
    } else {//无向图
        G->edges[j][k] == weight;
        G->edges[k][j] == weight;
    }
    return (1);
}

(2)邻接链表法
1)基本思想:类似于树的孩子链表法,就是对于图 G 中的每个顶点v_i,将所有邻接于v_i的顶点v_j链成一个单链表,这个单链表就称为顶点v_i的邻接链表,再将所有点的邻接表表头放到数组中,就构成了图的邻接链表。


一种是顶点表的结点结构,它由顶点域(data)和指向第一条邻接边的指针域(firstarc) 构成。
另一种是边表(即邻接表)结点,它由邻接点域(adjvex)和指向下一条邻接边的指针域(nextarc)构成。
对于网图的边表需再增设一个存储边上信息(如权值等)的域(info)。
无向图及其邻接表

对无向图,其邻接链表是唯一(按顺序链接)的;对有向图,其邻接链表有两种形式。

有向图及其邻接表

2)从图的邻接表存储方法容易看出,这种表示具有以下特点:
①表头向量中每个分量就是一个单链表的头结点,分量个数就是图中的顶点数目。
②在边稀疏的情况下,用邻接表表示图比邻接矩阵节省存储空间。
③在无向图的邻接表中,顶点v_i的度恰为第 i 个链表中的结点数。
④有向图可以建立一个正邻接表和逆邻接表,便于统计每个结点的出度和入度。
⑤在邻接表上容易找到任一顶点的第一个邻接点和下一个邻接点,但要判定任意两个顶点(v_iv_j)之间是否有边或弧相连,则需搜索第 i 个或第 j 个链表,因此,不及邻接矩阵方便。

对于n个顶点e条边的无向图,邻接表表示时有n个表头结点,2\timese个表结点。
对于n个顶点e条边的有向图,邻接表表示时有n个表头结点,表结点数不确定,但正邻接表加上逆邻接表表结点数为e。

3)表结点及其类型定义

#define MAX_VEX 30 /* 最大顶点数 */

typedef enum {
    DG, AG, WDG, WAG
} GraphKind;

typedef struct LinkNode {
    int adjvex;//邻接点在头结点数组中的位置(下标)
    int weight;//权值
    struct LinkNode *nextarc;//指向下一个表结点
} LinkNode;/*表结点类型定义 */

typedef struct VexNode {
    char data; // 顶点信息
    LinkNode *firstarc; // 指向第一个表结点
} VexNode;/* 顶点结点类型定义 */

typedef struct {
    GraphKind kind;/*图的种类标志 */
    int vexnum;//顶点数
    VexNode AdjList[MAX_VEX];//邻接表
} ALGraph;/* 图的结构定义 */

图的各种操作
①图的创建

ALGraph *createGraph(ALGraph *G) {
    printf("请输入图的种类标志:");
    scanf("%d", &G->kind);
    G->vexnum = 0; /*初始化顶点个数*/
    return (G);
}

②顶点定位
图的顶点定位实际上是确定一个顶点在 AdjList 数组中的某个元素的 data 域内容。

int LocateVex(ALGraph *G, char ch) {
    for (int i = 0; i < G->vexnum; i++) {
        if (G->AdjList[i].data == ch) {
            return (i);
        }
    }
    return (-1);/* 图中无此顶点*/
}

③向图中增加顶点
向图中增加一个顶点的操作,在 AdjList 数组的末尾增加一个数据元素。

int AddVertex(ALGraph *G, char ch) {
    int k;
    if (G->vexnum >= MAX_VEX) {
        printf("Vertex Overflow !\n");
        return (-1);
    }
    if (LocateVex(G, ch) != -1) {
        printf("Vertex has existed !\n");
        return (-1);
    }
    G->AdjList[G->vexnum].data = ch;
    G->AdjList[G->vexnum].firstarc = NULL;
    k = ++G->vexnum;
    return k;
}

④向图中增加一条弧
根据给定弧或边所依附的顶点,修改单链表,无向图修改两个单链表;有向图修改一个单链表。

int addArc(ALGraph *G, char ch1, char ch2, int weight) {
    int k, j;
    LinkNode *p, *q;
    k = LocateVex(G, ch1);
    j = LocateVex(G, ch2);
    if (k == -1 || j == -1) {
        printf("Arc’s Vertex do not exist!\n");
        return (-1);
    }
    p = (LinkNode *) malloc(sizeof(LinkNode));
    p->weight = weight;
    q = (LinkNode *) malloc(sizeof(LinkNode));
    q->weight = weight;
    if (G->kind == AG || G->kind == WAG) {//无向图,用头插入法插入到两个单链表
        p->nextarc = G->AdjList[k].firstarc;
        G->AdjList[k].firstarc = p;
        q->nextarc = G->AdjList[j].firstarc;
        G->AdjList[j].firstarc = q;
    } else {
        p->nextarc = G->AdjList[k].firstarc;
        G->AdjList[k].firstarc = p;   /*建立正邻接链表用 */

//        q->nextarc = G->AdjList[j].firstarc;
//        G->AdjList[j].firstarc = q;/*建立逆邻接链表用 */
    }
    return (1);
}

(3) 十字链表法
十字链表(Orthogonal List)是有向图的另一种链式存储结构,是将有向图的正邻接表和逆邻接表结合起来得到的一种链表。
在这种结构中,每条弧的弧头结点和弧尾结点都存放在链表中,并将弧结点分别组织到以弧尾结点为头(顶点)结点和以弧头结点为头(顶点)结点的链表中。这种结构的结点逻辑结构如图所示。


十字链表结点结构

data 域:存储和顶点相关的信息;
指针域 firstin:指向以该顶点为弧头的第一条弧所对应的弧结点,即逆邻接链表;
指针域 firstout:指向以该顶点为弧尾的第一条弧所对应的弧结点,即正邻接链表;
尾域 tailvex:指示弧尾顶点在图中的位置;
头域 headvex:指示弧头顶点在图中的位置;
指针域 hlink:指向弧头相同的下一条弧;
指针域 tlink:指向弧尾相同的下一条弧;
Info 域:指向该弧的相关信息,比如权值;
结点类型定义:

#define MAX_VEX 30 /* 最大顶点数 */
#define INFINITY 65535/* 最大值∞ */
#define MAX_VEX 30//最大顶点数
typedef struct ArcNode {
    int tailvex, headvex;//尾结点和头结点在图中的位置
    int  info;//权值
    struct ArcNode *hlink, *tlink;
} ArcNode;/* 弧结点类型定义 */

typedef struct VexNode {
    char data; // 顶点信息
    ArcNode *firstin, *firstout;
} VexNode;/* 顶点结点类型定义 */

typedef struct {
    int vexnum;
    VexNode xlist[MAX_VEX];
} OLGraph; /* 图的类型定义 */

下图所示是一个有向图及其十字链表(略去了表结点的 info 域)。实质就是先把图的正邻接链表(出度)画出来,然后再把firstin,firstout,hlink,tlink连起来。


有向图及其十字链表

(4)邻接多重表法
邻接多重表(Adjacency Multilist)是无向图的另一种链式存储结构。
邻接多重表的结构和十字链表类似,每条边用一个结点表示。
邻接多重表中的顶点结点结构与邻接表中的完全相同,而表结点包括六个域。


邻接多重表结点定义

data 域:存储和顶点相关的信息;
指针域 firstedge:指向依附于该顶点的第一条边所对应的表结点;
标志域 mark:用以标识该条边是否被访问过;
ivex 和 jvex 域:分别保存该边所依附的两个顶点在图中的位置;
info 域:保存该边的相关信息;
指针域 ilink:指向下一条依附于顶点 ivex 的边;
指针域 jlink:指向下一条依附于顶点 jvex 的边;

结点类型定义:

#define MAX_VEX 30 /* 最大顶点数 */

typedef enum {
    unvisited, visited
} Visitting;

typedef struct EdgeNode {
    Visitting mark; // 访问标记
    int ivex, jvex; // 该边依附的两个结点在图中的位置
    int  weight; //权值
    struct EdgeNode *ilink, *jlink;// 分别指向依附于这两个顶点的下一条边
} EdgeNode; /* 弧边结点类型定义 */

typedef struct VexNode {
    char data; // 顶点信息
    EdgeNode *firsedge; // 指向依附于该顶点的第一条边
} VexNode;/* 顶点结点类型定义 */

typedef struct {
    int vexnum;
    VexNode mullist[MAX_VEX];
} AMGraph;

邻接多重表与邻接表的区别:后者的同一条边用两个表结点表示,而前者只用一个表结点表示;除标志域外,邻接多重表与邻接表表达的信息是相同的,因此,操作的实现也基本相似。


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

推荐阅读更多精彩内容

  • 1. 图的定义和基本术语 线性结构中,元素仅有线性关系,每个元素只有一个直接前驱和直接后继;树形结构中,数据元素(...
    yinxmm阅读 5,404评论 0 3
  • 图(Graph)是数据结构中最复杂的一种结构,线性表描述的是一对一关系,树描述的是一对多关系,而图描述的是多对多关...
    大大纸飞机阅读 1,767评论 0 3
  • https://zh.visualgo.net/graphds 浅谈图形结构https://zh.visualgo...
    狼之独步阅读 4,075评论 0 0
  • 图的定义与术语 1、图按照有无方向分为无向图和有向图。无向图由顶点和边构成,有向图由顶点和弧构成。弧有弧尾和弧头之...
    unravelW阅读 396评论 0 0
  • 1)这本书为什么值得看: Python语言描述,如果学的Python用这本书学数据结构更合适 2016年出版,内容...
    孙怀阔阅读 12,391评论 0 15