图是由顶点和边或弧两部分来组成,所以分两个结构来分别存储。
1、邻接矩阵
图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
无向图的边数组是一个对称矩阵。有了这个矩阵,我们就可以知道无向图的信息:
(1)要判断任意两个顶点是否有边无边,只需要查找arc[i][j]或arc[j][i]是否为1即可。
(2)要知道某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行(或第i列)的元素之和。
(3)求顶点vi的所有邻接点就是将矩阵中第i行的元素扫描一遍,arc[i][j]为1就是邻接点。
有向图讲究出度和入度,顶点vi的入度是是第i列的各数之和,顶点vi的出度是第i行的各数之和。
对于网来说,把权值存储在arc[i][j]中。如果边或弧不存在,用一个不可能的值来代表不存在。
//邻接矩阵的结构定义
typedef char VertexType;
typedef int EdgeType;
#define MAXVEX 100
#define INFINITY 65535
typedef struct
{
VertexType vexs[MAXVEX];
EdgeType arc[MAXVEX][MAXVEX];
int numVertexes, numEdges;
} MGraph;
//建立无向网的邻接矩阵表示
void CreateMGraph(MGraph *G)
{
int i, j, k, w;
printf("输入顶点数和边数:\n");
scanf("%d, %d", &G->numVertexes, &G->numEdges);
for (i = 0; i < G->numVertexes; i++) //读入顶点信息建立顶点表
scanf(&G->vexs[i]);
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");
scanf("%d, %d, %d", &i, &j, &w);
G->arc[i][j] = w;
G->arc[j][i] = G->arc[i][j];
}
}
2、邻接表
邻接表的处理办法:
(1)图中顶点用一个一维数组存储。对于顶点数组中每个数据元素还需要存储指向第一个邻接点的指针,以便查找该 顶点的边信息。顶点表中的各个结点由data和firstedge两个域表示,data是数据域,存储顶点信息,firstedge是指针域,指向边表的第一个顶点,即此顶点的第一个邻接点。
(2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。边表姐弟由adjvex和next两个域组成。adjvex是邻接点域,存储某个顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。
对于这样的结构,我们要获得相关信息是方便的:
(1)要想知道某个顶点的度,就去查找这个顶点的边表中结点的个数。
(2)判断顶点vi和vj是否存在边,只需要测试顶点vi的边表中adjvex是否存在结点vj的下标。
(3)求顶点的所有邻接点,其实就是对此顶点的边表进行遍历,得到的adjvex域对应的顶点就是邻接点。
以顶点为弧尾来存储边表的,可以很容易得到每个顶点的出度。有时为了便于确定顶点的入度或以顶点为弧头的弧,可以建立一个有向图的逆邻接表,即对每个顶点vi都建立 一个链接为vi为弧头的表。
对于带权值的网图,可以在边表结点定义中在增加一个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);
e = (EdgeNode *)malloc(sizeof(EdgeNode));
e->adjvex = j;
e->next = G->adjList[i].firstedge;
G->adjList[i].firstedge = e;
e = (EdgeNode *)malloc(sizeof(EdgeNode));
e->adjvex = i;
e->next = G->adjList[j].firstedge;
G->adjList[j].firstedge = e;
}
}
3、十字链表(针对有向图的)
顶点表结点结构:data存储顶点信息;firstin表示入边表的头指针,指向该顶点的入边表的第一个结点;firstout表示出边表头指针,指向该结点的出边表中的第一个结点。
边表结点结构:tailvex是指弧起点在顶点表的下标;headvex是指弧终点在顶点表中的下标;headlink是指入边表指针域,指向终点相同的下一条边;taillink是指边表指针域,指向起点相同的下一条边,如果是网,还可以再增加一个weight域来存储权值。
十字链表的好处就是因为吧邻接表和逆邻接表整合在一起,这样既容易找到以vi为尾的弧,也容易找到以vi为头的弧,因此容易求得顶点的出度和入度。
4、邻接多重表
与邻接表相比,重新定义边表结点的结构:ivex和jvex是与某条边依附的两个顶点在顶点表中的下标。ilink指向依附顶点ivex的下一条边,jlink指向依附顶点jvex的下一条边。
邻接多重表语邻接表的差别仅仅是在于同一条边在邻接表中用两个阶段表示,而在邻接表中只有一个结点。
5、边集数组
边集数组是由两个一维数组构成。一个是存储顶点的信息;另一个是存储边的信息,这个边数组每个数据元素由一条边的起点下标,终点下标和权组成。
显然边集数组关注的是边的集合,在边集数组中查找一个顶点的都,需要遍历整个边数组,效率不高。