20210509

GS2-660-C4-51~57 ,176 | 数据结构-图(一)| 恋词U13

Table of Contents

GS2-660-C4-51~57

不得不说660的题目太综合了,这几道不定积分的计算题综合性都比较强,里面很多点值得取深究。 并且涉及到很多基本公式得掌握,要多体会体会!

数据结构-图(一)

逻辑结构和定义

图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中G表示一个图,V是图G中顶点的集合,E是图G中边的集合


无向图 有向图
每条边都没有方向 每条边(弧)都有方向
边(A1,A3) 弧(A1,A3)
顶点A1的度为3 顶点A1的入度为1,出度为2,度为3

一些概念

简单图

在图中,若不存在某顶点到其自身的边,且不存在重复的边,则称之为简单图。下面都不是简单图。

完全图


带权图

在图中,若边含有权值,则一般称之为网(本课程中统称为图)。


子图

下图图黄色部分为其所在图的子图(Subgraph)。


路径

路径是一个顶点到另一个顶点的顶点序列,路径长度是路径上边的个数。


第一个顶点到最后一个顶点相同的路径称之为回路或者环。


简单路径

序列中顶点不重复出现的路径称之为简单路径。

下面的就不是简单路径!,因为A3出现了2次。

路径序列:A1、A3、A6、A7、A4、A3、A5、A2

image.png

简单环

序列中除了第一个和最后一个顶点,其余顶点都不重复出现的环称之为简单环。下面的就不是简单环!

路径序列:A1、A3、A6、A7、A4、A3、A5、A2、A1

image.png

连通图

无向图中,如果$v_i$$v_j$有路径,则称$v_i$$v_j$连通;如果图中任意一对顶点连通,则此图为连通图。

image.png

连通分量

无向图中的极大连通子图称之为连通分量。

image.png

强连通图

有向图中,每一对$v_i$$v_j$,从$v_i$$v_j$,和从$v_j$$v_i$都存在路径则称强连通图。

image.png

强连通分量

有向图中的极大连通子图称之为强连通分量。

image.png

例题

image.png
image.png

图的顺序存储结构-邻接矩阵存储法

图的邻接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息

有向无权图

设图G有n个顶点,则邻接矩阵是一个n×n的方阵,定义为:

image.png
image.png
image.png

有了这个矩阵,很容易知道图中的信息:

  1. 判定任意两顶点是否有边无边
  2. 要知道某个顶点的度,就是这个顶点$v_i$在邻接矩阵中第i行(或第i列)的元素之和。
  3. 求顶点$v_i$的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点
image.png

顶点数组为$verte[4]={v_0,v_1,v_2,v_3}$,弧数组arc[4][4]为右图的一个矩阵。主对角线上数值依然为0。但因为是有向图,所以此矩阵并不对称

有向图讲究入度与出度,顶点$v_1$的入度为1,正好是第$v_1$列各数之和。顶点$v_1$的出度为2,即第$v_1$行的各数之和

判断顶点$v_i$$v_j$是否存在弧,只需要查找矩阵中arc[i][j]是否为1。求的所有邻接点就是将矩阵第i行元素扫描一遍,查找arc[i][j]为1的顶点


有向有权图

设图G是网图,有n个顶点,则邻接矩阵是一个n×n的方阵,定义为:

image.png
image.png

$w_{ij}$表示$(v_i,v_j)$$<v_i,v_j>$上的权值∞表示一个计算机允许的、大于所有边上权值的值,也就是一个不可能的极限值

实现带权图c语言描述:

image.png
float MGraph[5][5];
for (int i = 0; i <5 ; ++i)
for(int j = 0; j<5;++j)
MGraph[i][j] = MAX;

但是如果顶点不是连续的或者顶点根本不是数字呢?可以专门搞一个顶点数组,则二维数组的行标或者列标恰好和一维数组的下标对应

image.png
char vertex[5] = ['A','B,'C',D','E'];
float Edge[5][5];
for (int i=o; i<5 ;++i)
for(int j=0; j<5; ++j)
Edge[i][j] = MAX;

邻接矩阵例题

  1. 408,13年07题
image.png

【分析】:观察矩阵元素,可知为有向图,因为矩阵不对称。矩阵中对于每一个顶点都有它的一行和一列,对于它所属的那一行,其中1的个数代表它所发出的边的条数;而对于它所在的列,其中1的个数代表指向它的边的条数,而对于一个顶点,它的度数就是入度和出度的总和,即它发出的边和指向它的边的总和,因此对于每一个顶点在邻接矩阵中,数一下它所在行列的1的个数就是它的度。如下图,可知答案为C。

  1. 综合题
image.png
  1. 【分析】,这是一个无向无权图。且行列下标都是从0开始,那么很容易写出这么一个矩阵。
image.png
  1. 【分析】。先求出A^2。如下图,那么0行3列是怎么得来的?可知是A的第零行与A的第三列相乘的得到,即把所有的$A_{0,k}$$A_{k,3}$相乘的结果相加。$A_{0,k}$代表顶点0到顶点k有没有边,为0就是没有边,为1就是有边;$A_{k,3}$就是从顶点k到顶点3有没有边,那这个乘积什么时候为1,很明显两边都为1才为1,此时代表从0到3可以经过中间结点k到达,那把所有这些$A_{0,k}$$A_{k,3}$相乘的结果相加则代表,图中从顶点0到顶点3只有一个中间顶点的条数为有3条
image.png
image.png
  1. 【分析】很明显为第二问的一般情况。则为
image.png

图的链式存储结构-邻接表

数组与链表相结合的存储方法称为邻接表(Adjacency List)

邻接表的处理办法:

  1. 图中顶点用一个一维数组存储,对于顶点数组中,每个数据元素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息
  2. 图中每个顶点$v_i$的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储,无向图称为顶点$v_i$的边表,有向图则称为顶点$v_i$作为弧尾的出边表。
image.png

顶点表的各个结点由data 和 firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此结点的第一个邻接点

边表结点由adjvex和next 两个域组成,adjvex 是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针

要想知道某个顶点的度,就去查找这个顶点的边表中结点的个数。若要判断顶点$v_i$$v_j$是否存在边,只需要测试顶点$v_i$的边表中adjvex是否存在结点$v_j$的下标j就行了。若求顶点的所有邻接点,其实就是对此顶点的边表进行遍历,得到的adjvex域对应的顶点就是邻接点

若是有向图,第一幅图的邻接表就是第二幅图。有向图由于有方向,是以顶点为弧尾来存储边表的,很容易得到每个顶点的出度。有时为了便于确定顶点的入度或以顶点为弧头的弧,可以建立一个有向图的逆邻接表,即对每个顶点$v_i$都建立一个链接为$v_i$为弧头的表

image.png

对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域存储权值信息

结点定义的代码:

typedef char VertexType; //顶点类型,由用户定义
typedef int EdgeType;
typedef struct EdgeNode //边表结点
{
int adjvex; //邻接点域,存储该顶点对应的下标
EdgeType weight; //用于存储权值,对于非网图可以不需要
struct EdgeNode *next;//链域,指向下一个邻接点
}EdgeNode;
typedef struct VertexNode //顶点表结点
{
VertexType data; //顶点域,存储顶点信息
EdgeNode *firstedge; //边表头指针
}VertexNode,AdjList[MAXVEX];
typedef struct
{
AdjList adjList;
int numVertexes,numEdges; //图中当前顶点数和边数
}GraphAdjList;

无向图的邻接表创建代码:

/* 建立图的邻接表结构 */
void CreateALGraph(GraphAdjList *G)  
{  
    int i,j,k;  
    EdgeNode *e;  
    printf("输入顶点数和边数:\n");  
    scanf("%d,%d",&G->numVertexes,&G->numEdges);//输入顶点数和边数  
    for(i=0;i<G->numVertexes,i++)  
    {  
        scanf(&G->adjList[i].data);//输入顶点信息  
        G->adjList[i].firstedge=NULL;//将边表置位空表  
    }  
    for(k=0;k<G->numEdges;k++)  
    {  
        printf("输入边(vi,vj)上的顶点序号:\n");  
        scanf("%d,%d",&i,&j);//输入边(vi,vj)上的顶点序号  
        e=(EdgeNode *)malloc(sizeof(EdgeNode));/*向内存申请空间,生成边表结点*/  
        e->adjvex = j;//邻接序号为j  
        e->next = G->adjList[i].firstedge;//将e指针指向当前顶点指向的结点  
        G->adjList[i].firstedge = e;//将当前顶点的指针指向e  

        e=(EdgeNode *) malloc(sizeof(EdgeNode));/*向内存申请空间,生成边表结点*/  

        e->adjvex = i;//邻接序号为i  
        e->next=G-adjList[j].firstedge;//将e指针指向当前顶点指向的结点               
        G->adjList[j].firstedge = e;//将当前顶点的指针指向e  
    }  
}

对于无向图,一条边对应都是两个顶点,所以在循环中,一次就针对i和j分别进行了插入。本算法的时间复杂度,对于n个顶点e条边来说是$O(n+e)$

邻接表的缺陷:


图的链式存储结构-十字链表

十字链表(Orthogonal List):把邻接表和逆邻接表结合起来的存储方式

重新定义顶点表结点结构如下:

data firstin firstout

firstin表示入边表头指针,指向该顶点的入边表中第一个结点,firstout表示出边表头指针,指向该顶点的出边表中的第一个结点


重新定义的边表结点结构如下:

tailvex headvex headlink taillink

顶点依然是存入一个一维数组$v_0,v_1,v_2,v_3$,实线箭头指针的图示邻接表相同。以顶点$v_0$来说,firstout指向的是出边表中的第一个结点$v_3$。所以边表结点的headvex=3,而tailvex其实就是当前顶点$v_0$的下标0,由于$v_0$只有一个出边顶点,所以headlink和taillink都是空.

image.png

虚线箭头的含义此图的逆邻接表的表示。$v_0$有两个顶点$v_1$$v_2$的入边。因此$v_0$的firstin指向顶点$v_1$的边表结点中headvex为0的结点,如图中的①。接着由入边结点的headlink指向下一个入边顶点$v_2$,如图中的②。对于顶点,它有一个入边顶点,所以它的firstin指向顶点$v_2$的边表结点中headvex为1的结点,如图中的③。顶点$v_2$$v_3$也是同样有一个入边顶点,如图中④和⑤

十字链表的好处:


图的链式存储结构-邻接多重表

重新定义的边表结点结构如下:

ivex ilink jvex jlink

ivex和jvex是与某条边依附的两个顶点在顶点表中的下标。ilink指向依附顶点ivex的下一条边,jlink指向依附顶点jvex的下一条边。这就是邻接多重表结构

首先连线的①②③④就是将顶点的firstedge指向一条边,顶点下标要与ivex的值相同,接着,由于顶点$v_0$$(v_0, v_1)$边的邻边有$(v_0, v_3)$$(v_0, v_2)$。因此⑤⑥的连线就是满足指向下一条依附于顶点v 的边的目标,ilink指向的结点的jvex一定要和它本身的ivex的值相同。连线⑦就是指$(v_1, v_0)$这条边,相当于顶点v指向$(v_1, v_2)$边后的下一条。$v_2$有三条边依附,所以在③之后就有了⑧⑨。连线@⑩的就是顶点$v_3$在连线④之后的下一条边。左图一共有5条边,所以右图有10条连线。

邻接多重表和邻接表的区别:同一条边在邻接表中用两个结点表示,在邻接多重表中只有一个结点。若要删除左图$(v_0,v_2)$的这条边,只需要将右图的⑥⑨的链接指向改为∧即可。


恋词U13