1、邻接矩阵法:
//图边数多时,有向图/无向图/有向网/无向网都可用其来存储。
//不过当边数较少时不适合用此方法,会造成空间浪费
图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。
设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
看一个实例,下图左就是一个无向图。
从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。
从这个矩阵中,很容易知道图中的信息。
(1)要判断任意两顶点是否有边无边就很容易了;
(2)要知道某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和;
(3)求顶点vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点;
而有向图讲究入度和出度,顶点vi的入度为1,正好是第i列各数之和。顶点vi的出度为2,即第i行的各数之和。
若图G是网图,有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
这里的wij表示(vi,vj)上的权值。无穷大表示一个计算机允许的、大于所有边上权值的值,也就是一个不可能的极限值。下面左图就是一个有向网图,右图就是它的邻接矩阵。
下面是其代码实现:
#include
#include
#include
#define NUM 20;
#define BIG 22365;
typedef enum{DG,UNG,DN,UDN} graphtype; //图的类型
typedef int vrtype; //顶点类型
typedef int *infotype; //边的相关信息类型
//定义顶点关系类型
typedef struct Arccell{
int arckink; //边的类型,即顶点关系类型,无向图为1/0,表是否相邻。无向网为权值
//infotype info; //指向该边所有相关信息
}arccell;
//定义无向图/网
typedef struct{
graphtype gkind; //图的类型
vrtype vr[NUM]; //储存所有顶点
arccell arc[NUM][NUM]; //储存所有边
int vrnum,arcnum; //顶点和边的个数
}graph;
---------------------------------
//点位某个顶点在数组中的位置
int locate(graph G,vrtype v){
int i;
for(i=0;ivrnum;i++)
if(G->vr[i]==v)
break;
if(i>=G->vrnum)
return -1;
else
return i;
}
--------------------------------
//创建无向图/网
int createUDN(graph G){
scanf("%d %d ",&vrnum,&arnum ); // 输入顶点和边数量以及边相关信息数量
for(i=0;i
scanf("%d",&vr[i]);
for(i=0;i
for(j=0;j
arc[i][j]={BIG,NULL};
for(i=0;i
scanf("%d %d %d",&vr1,&vr2,&arckink); //输入顶点关系类型
j=locate(G,vr1); //定位俩顶点
k=locate(G,vr2); //输入顶点关系类型
if(i!=-1&&j!=-1){ //俩顶点均存在
G->arc[j][k]->arckink;
G->arc[j][i]->artype=G->arc[i][j]->artype; //将信息复制到其对称边上
G->arc[j][i]->info=G->arc[i][j]->info;
}
}
return 0;
}
----------------------------
//创建有向图/网。
与创建无向图/网类似,不过最后不用复制信息到其对称弧上。再次不作重复创建
int createDN(graph G){
return 0;
}
-------------------------
int creategraph(graph G){ //选择要储存的图的类型
switch(G->gtype){
case DG:createNG(G);
case UNG:createUNG(G);
case DN:createDN(G);
case UDN:createUDN(G);
default:return ERROR;
}
}
int main() //主函数。
{
graph G;
creategraph(G);
return 0;
}
--------------------------------------------------------------------------------------------------
2、邻表法:
//邻表法解决了邻接矩阵的缺点,但对于每个顶点而已,查找其出度容易,而要找入读却得遍历整个图才能找到。故邻表法不适合有向图/网存储。无向图/网可以其来存储,比十字链表法简单
邻接表的处理方法是这样的:
(1)图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。
(2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。
例如,下图就是一个无向图的邻接表的结构。
从图中可以看出,顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。
对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域,存储权值信息即可。如下图所示。
对于邻接表结构,图的建立代码如下。
#define BIG 26655;
#define NUM 20;
typedef char vrtype; //顶点为字符时不能是换行符,因为输出字符要换行
typedef int *infotype;
//定义顶点关系类型
typedef struct Arccell{
int tail;
int weight; //无向网为权值,无向图不需要。
//infotype info; //指向该边所有相关信息
struct Arccell *next;
}arccell;
//定义顶点类型
typedef struct {
vrtype data;
arccell *firstarc;
}vrcell;
//定义无向图/网类型
typedef struct{
vrcell vr[NUM]; //储存所有顶点
int vrnum,arcnum; //顶点和边的个数
}graph;
void CreateGraph(Graph g)
{
int i, j, k;
EdgeNode *e;
EdgeNode *f;
// printf("输入顶点数和边数:
");
scanf("%d,%d", &g->numVertexes, &g->numEdges);
for(i = 0; i < g->numVertexes; i++) //输入顶点信息
{
// printf("请输入顶点%d:
", i);
g->adjList[i].data = getchar();
g->adjList[i].firstedge = NULL; //将边表置为空表
while(g->adjList[i].data == '
')
{
g->adjList[i].data = getchar();
}
}
//建立边表
for(k = 0; k < g->numEdges; k++)
{
printf("输入边(vi,vj)上的顶点序号:
");
char p, q;
p = getchar();
while(p == '
')
{
p = getchar();
}
q = getchar();
while(q == '
')
{
q = getchar();
}
int m, n;
m = Locate(g, p);
n = Locate(g, q);
if(m == -1 || n == -1)
{
return;
}
//向内存申请空间,生成边表结点
e = (EdgeNode *)malloc(sizeof(EdgeNode));
if(e == NULL)
{
fprintf(stderr, "malloc() error.
");
return;
}
//邻接序号为j
e->adjvex = n;
//将e指针指向当前顶点指向的结构
e->next = g->adjList[m].firstedge;
//将当前顶点的指针指向e
g->adjList[m].firstedge = e;
f = (EdgeNode *)malloc(sizeof(EdgeNode)); //有向图/网则不需要进行这一步
if(f == NULL)
{
fprintf(stderr, "malloc() error.
");
return;
}
f->adjvex = m;
f->next = g->adjList[n].firstedge;
g->adjList[n].firstedge = f;
}
}
----------------------------------------------------------------------------------------
3、十字链表法:
十字链表(Orthogonal List)是有向图的一种存储方法,它实际上是邻接表与逆邻接表的结合,即把每一条边的边结点分别组织到以弧尾顶点为头结点的链表和以弧头顶点为头顶点的链表中。在十字链表表示中,顶点表和边表的结点结构分别如图8.13 的(a)和(b)所示。
在弧结点中有五个域:其中尾域(tailvex)和头(headvex)分别指示弧尾和弧头这两个顶点在图中的位置,链域hlink 指向弧头相同的下一条弧,链域tlink 指向弧尾相同的下一条弧,info 域指向该弧的相关信息。弧头相同的弧在同一链表上,弧尾相同的弧也在同一链表上。它们的头结点即为顶点结点,它由三个域组成:其中vertex 域存储和顶点相关的信息,如顶点的名称等;firstin 和firstout 为两个链域,分别指向以该顶点为弧头或弧尾的第一个弧结点。例如,图8.14(a)中所示图的十字链表如图8.14(b)所示。若将有向图的邻接矩阵看成是稀疏矩阵的话,则十字链表也可以看成是邻接矩阵的链表存储结构,在图的十字链表中,弧结点所在的链表非循环链表,结点之间相对位置自然形成,不一定按顶点序号有序,表头结点即顶点结点,它们之间而是顺序存储。有向图的十字链表存储表示的形式描述如下:
#define MAX_VERTEX_NUM 20
typedef struct ArcBox {
int tailvex,headvex;
struct ArcBox * hlink, tlink; /分别为弧头相同和弧尾相财的弧的链域*/
InfoType info;
}ArcBox;
typedef struct VexNode {
VertexType vertex:
ArcBox fisrin, firstout;
}VexNode;
typedef struct {
VexNode xlist[MAX_VERTEX_NUM];
int vexnum,arcnum;
}OLGraph;
下面给出建立一个有向图的十字链表存储的算法。通过该算法,只要输入n 个顶点的信息和e 条弧的信息,便可建立该有向图的十字链表,其算法内容如下。
void CreateDG(LOGraph **G)
{
scanf (&(*G->brcnum),&(*G->arcnum),&IncInfo);
for (i=0;i<*G->vexnum;++i){
scanf(&(G->xlist[i].vertex));
*G->xlist[i].firstin=NulL;*G->xlist[i].firstout =NULL;
}
for(k=0;k
{ scanf(&v1,&v2);
i=LocateVex(*G,v1); j=LocateVex(*G,v2);
p=(ArcBox*) malloc (sizeof(ArcBox));
*p={ i,j,*G->xlist[j].fistin,*G->xlist[i].firstout,NULL}
*G->xlist[j].fisrtin=*G->xlist[i].firstout=p;
if (IncInfo) Input( p->info);
}
}
在十字链表中既容易找到以为尾的弧,也容易找到以vi 为头的弧,因而容易求得顶点的出度和入度(或需要,可在建立十字链表的同时求出)。同时,由算法8.3 可知,建立十字链表的时间复杂度和建立邻接表是相同的。在某些有向图的应用中,十字链表是很有用的工具。