我们先来了解一下啥是图,图是一个什么样的东西:
图:图(Graaph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
无向图:若顶点vi到vj之间的边没有方向,则称这条边为无向边(Edge),用无序偶对(vi,vj)来表示。如果图中任意两个顶点之间的边都是无向边,则称该图为无向图(Undirected graphs)。如下图所示则为一个无向图:
有向图:若顶点vi到vj之间的边有方向,则称这条边为有向边,也称为弧(Arc),用有序偶<vi,vj>来表示。vi称为弧尾,vj称为弧头。如果图中任意两个顶点之间的边都是有向边,则称该图为有向图(Directed graphs)。如下图所示则为一个有向图:
简单图:在图中,若不存在顶点到其自身的边,且同一条边不重复出现,则称这样的图为简单图。
权:有些图的边或弧具有与它相关的数字,这种与图的边或弧相关的数叫做权(Weight)。
网:带权的图通常称为网(Network)。
第一个顶点到最后一个顶点相同的路径称为简单路径。除了第一个顶点和最后一个定点之外,其余顶点不重复出现的回路,成为简单回路或简单环。
在无向图G中,如果从顶点v到顶点 v’有路径,则称v和 v’是连通的。如果对于图中任意两个顶点vi,vj∈E,vi和vj都是连通的,则称G是连通图(Connected Graph)。
理解了图的概念,我们下来了解一下图是怎样存到计算机里面的,用计算机的语言表示它?
图不想树,有惟一的前驱,它可能与很多个前驱和后继。
在这里我说一种存储方法:邻接矩阵 。
图的邻接矩阵(Adjacency Maatrix)存储方式是用两个数组来表示图。一个一维数组存储图顶点信息 ,一个二维数组(成为邻接矩阵)存储图中的边或弧的信息。
我们来看一个实例,看一下怎样用图或者语言表示:
先来看一下要表示的图:
我们这里先用一个数组表示一下它的顶点信息,称为顶点数组:
v0 | v1 | v2 | v3 |
---|
在表示一下边的二位数组,称为边数组:
v0 | v1 | v2 | v3 | |
---|---|---|---|---|
v0 | 0 | 1 | 1 | 1 |
v1 | 1 | 0 | 1 | 0 |
v2 | 1 | 1 | 0 | 1 |
v3 | 1 | 0 | 1 | 0 |
着里连通表示1,不连通表示0,若此图为有向图,从弧尾到弧头表示为1,弧头到弧尾表示为0,若为带权值的图,则用权值来表示1,对于以后的计算会更加方便。设置这样两个数组,我们就可以有效的对一个图进形表示了。
下来我们对图需要的数组进行创建,此处为结构体的代码:
typedef char VertexType;/*顶点char型*/
typedef int EdgeType;/*边上的权值*/
#define MAXVEX 100/*最大顶点数*/
#define INFINITY -1/*用-1来表示无穷大,即不连通*/
typedef struct
{
VertexType vexs[MAXVEX];/*顶点表*/
EdgeType arc[MAXVEX][MAXVEX];/*邻接矩阵,可看作边表*/
VCoordinate pos[MAXVEX];
int numVertexes, numEdges;/*图中当前的顶点数和边数*/
}MGraph;
下面我们来用代码表示一个图怎样输入:
void CreateMGraph(MGraph *G)/*输入图*/
{
FILE* fp;
fp = fopen("\Graph.txt", "r");
int i, j, k, w;
printf("输入顶点数和边数:\n");
fscanf(fp,"%d,%d\n",&G->numVertexes,&G->numEdges);
/*fflush(stdin);*/
for (i = 0; i < G->numVertexes; i++)
{
fscanf(fp,"%c\n",&G->vexs[i]);
}
G->vexs[G->numVertexes] = '\0';
for (i = 0; i < G->numVertexes; i++)
{
for (j = 0; j < G->numVertexes; j++)
{
G->arc[i][j] = INFINITY;
}
}
for (k = 0; k < G->numEdges; k++)
{
printf("输入边(vi,vj)上的下标i,下标j和权w:\n");
/*fflush(stdin);*/
fscanf(fp,"%d,%d,%d\n", &i, &j, &w);
G->arc[i-1][j-1] = w;
G->arc[j-1][i-1] = G->arc[i-1][j-1];
}
邻接矩阵是一种很好的用来表示图在计算机上存储的方式,比较容易理解,但是我们发现,对于边数相对顶点较少的图,这种结构的存储方式对,空间的浪费比较大,所以下面我们介绍一种新的存储方式:邻接表
邻接表:我们把数组与链表相结合的存储方式称为邻接表(Adjacency List)。
邻接表的存储方式是这样的:
- 图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过数组可以较容易地读取顶点信息,更加方便。另外 ,对于顶点数组,每个数据元素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息。
- 图中每个顶点Vi的所有领接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储,无向图称为顶点V~i ~的边表,有向图则称为顶点 Vi作为弧尾的出边表。
例如下图所示的就是一个无向图和他的邻接表结构:
由图可知,顶点表的各个节点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个 结点,即此顶点的第一个邻接顶点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。
下面我们定义一下邻接表的结构体:
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 CreatALGraph(CreatALGraph* 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*/
}
}
对于有向图,邻接表是有问题的,当他关心了出度问题,想了解入度就必须要遍历整个图才行,这样就大大增加了时间复杂度。
下面说两种储存方式:
- 十字链表(Orthogonal List)
它顶点表的结构如下表所示:
data | firstin | firstout |
---|
其中firstin表示入边表头指针,指向该顶点的入边表中第一个结点,firstout表示出边表头指针,指向该顶点的出边表中的第一个节点。
定义他的边表结点结构如下表所示:
tailvex | headvex | headlink | taillink |
---|
其中tailvex是指弧起点在顶点表的下标,headvex是指弧终点在顶点表中的下标,headlink是指入边表指针域,指向终点相同的下一条边,taillink是指边表指针域,指向起点相同的下一条边。
下面我们用图来表示一下:
十字链表的好处就是因为把邻接表和逆邻接表整合在一起,这样即容易找到以vi为尾的弧,也容易找到以vi为头的弧,因而容易求得顶点的出度和入度。
2. 邻接多重表
上面我们说了对有向图的优化,下面说一下无向图的存储优化结构。
在上面说的存储结构中,我们如果关注的重点是顶点,那么邻接表很不错,但如果我么更注重边的操作,比如删除某一条边,或增添等操作,就意味着,需要找到这两条边的两个边结点进行操作,还是比较麻烦的。
因此我们对狮子链表进行了一些改进,成为了我们现在的邻接多重表,其边表结点结构如下表所示:
ivex | inlink | jvex | jlink |
---|
其中ivex和jvex是与某条边依附的两个顶点表中下标。inlink指向依附顶点ivex的下一条边,jlink指向依附顶点jvex的下一条边。这就是邻接多重表结构。
下面用图表示一下:
今天先写这么多,图的剩余知识,下次在写。
可能有一些地方还不完美,我后续还会继续改进,也希望大家指出不当之处,以便改进,共同进步,谢谢赏阅!!!