如果看不懂下面关于图的术语或者压根就不知道图。建议看一下严蔚敏版的《数据结构》中对图的介绍
图的构建方法有好几种,如邻接矩阵法,邻接表法,十字链表法,邻接多重表。今天介绍邻接表法。
邻接表(Adjacency List) 是图的一种链式存储结构,在邻接表中,对图中每个顶点建立一个单链表,第i个单链表中的节点表示依附于顶点Vi的边(对有向图是以顶点Vi为尾的弧)。每个节点由3个域组成,其中邻接点域(adjvex)指示与顶点vi邻接的点在图中的位置,链域(nextarc)指示下一条边或弧的节点;数据域(info)存储和边或弧相关的信息,如权值等。每个链表上附设一个表头节点。在表头节点中,除了设有链域(firstarc)指向链表中的第一个节点之外,还设有存储顶点Vi的名或者其他有关信息的数据域(data)。
如图:
头节点(存储顶点信息)
data
|
firstarc
|
表(边)节点(存储两个顶点之间边的信息)
adjvex
|
nextarc
|
info
|
这些表头节点(可以链相接)通常以顺序结构的形式存储,以便随机访问任一个顶点的链表。
上面这一段是严蔚敏版数据结构那本书中对用邻接表法构建图的介绍,说的过于理论化,我第一次看的时候也是不知所云,但是仔细阅读,并且阅读了书中的伪代码,便明白了作者的意思。现在做出简要总结:
在构建图时,需要两个结构体,一个存储图中节点的信息,便是上面介绍的头节点,一个是两个节点之间边的信息,便是上面的表(边)节点。 采用一个一维数组存储头结点信息,然后为每个头结点建立一个链表,让头结点作为这个链表的表头节点(具体实现方法便是让头结点内的指针firstarc 指向链表的首地址),链表中存储的正是和这个头结点相关联的表节点(边的信息),这些边都是尾部和头结点相连。表节点中的adjvex存储的是与这条边关联的另一个头结点在数组中的索引(边的箭头所关联的头节点),表节点中的nextarc则是存储的该链表表头相关联的另一条边的信息。
如下面左边的为原始的图结构,
图1
下图是上图的邻接表:
图2
以第一行为例:V1是图中的头结点,V1后面的链表是所有与V1相连的边
此处需要普及一个概念:我们平时口语所说的边,例如上面的图1中V1 指向 V2的那条边,在图论里面成为“弧”,对于弧来说V1叫“弧尾”,V2是该弧的“弧头”。(虽然不知道名字的来源故事,但是至少人家是专业术语,说出去,让人一听,感觉好高大上的样子,装逼必备利器)。
下面是代码:
#include<iostream> #include<string> #include<queue> using namespace std; #define ERROR 1 #define MAX_VERTEX_NUM 100 typedef struct ArcNode{ //表(边)节点,存储边信息的结构体 int adjvex; //存储与该边相连的另一个节点的索引 struct ArcNode *nextarc; //存储与表头节点相连的其他表节点的指针 string info; //存储边的信息,例如权值 }ArcNode;
typedef struct VNode{<span style="white-space:pre"> </span>//存储头节点信息的结构体 char date; //存储头节点包含的数据,例如头节点的名字 ArcNode * firstarc; //指向链表的指针 }VNode,AdjList[MAX_VERTEX_NUM];
typedef struct{ AdjList vertices;<span style="white-space:pre"> </span>//存储头节点的数组<span style="white-space:pre"> </span> int vexnum,arcnum; //当前图的vexnum顶点数和arcnum弧数 int kind;<span style="white-space:pre"> </span>//图的种类, }ALGraph;
int LocateVex(ALGraph &G,char &v1) //查找节点V1在图G的存储节点数组中的索引位置 { int i; for(i=0;i<G.vexnum;i++) { if(G.vertices[i].date==v1) //如果数组中有这个节点,返回该节点在数组中的索引 return i; } if(i>=G.vexnum) return ERROR; else return 0; }
//创建图 void CreateDG(ALGraph &G) { ArcNode *p,*q; char v1,v2; char v; int i,j,k,n; cout<<"请输入图的顶点数和弧数:"<<endl; cin>>G.vexnum; //输入图的顶点数量 cin>>G.arcnum; //输入图的弧(边)的数量 cout<<"请输入顶点:"<<endl; for(i=0;i<G.vexnum;i++) //创建头结点 { cin>>v; //输入顶点名字 G.vertices[i].date=v; G.vertices[i].firstarc=NULL; } cout<<"请输入弧尾和弧头:"; for(k=0;k<G.arcnum;k++) //创建边,并连接头结点 { cin>>v1; //v1为弧尾 cin>>v2; //v2为弧头 i=LocateVex(G,v1);j=LocateVex(G,v2); if(G.vertices[i].firstarc==NULL) //如果链表为空新建一个表节点,让头节点的指针指向该表节点 { p=(ArcNode *)new ArcNode; G.vertices[i].firstarc=p; q=G.vertices[i].firstarc; } else //链表的插入 { q=G.vertices[i].firstarc;//获取头结点的表头指针 for(n=0;n<G.arcnum;n++,q=q->nextarc)//将q指针移动至链表的尾巴处 { if(!q->nextarc) break; } p=(ArcNode *)new ArcNode; q->nextarc=p; //将该边(弧)加入到链表中 q=q->nextarc; } q->adjvex=j; //记录弧头的索引 q->nextarc=NULL; } cout<<"图构建成功!"; }