前言
在上一篇文章中,已经详细介绍了图的四种存储结构的区别,这里我们就不再过多说明了。
图的邻接表存储里,每一个顶点类似于一个单链表,头结点即顶点结点,其余的结点即边结点;每一个顶点又需要存储到一个列表中,就形成了一个邻接表。
无论是头结点还是边结点,都有数据域和指针域两个部分。顶点结点的指针域指向其第一个边结点,而边结点的指针域指向本顶点的下一个边结点,类似于单链表的next指针。注意:边结点的数据域存储的是另一端顶点的地址,只是因为在数组中,因此是int类型;假设存储顶点的列表也是单链表,则其类型应该是顶点的指类型。
//边的结构
typedef struct Edge{
//顶点的下标
//顶点列表为单链表:struct Vertex* dest;
int dest;
//下一条边
struct Edge* Link;
}Edge;
//顶点的结构
typedef struct Vertex{
//顶点
T data;
//边
Edge *adj;
}Vertex;
//图本身的结构
typedef struct GraphLnk{
//最大顶点数量
int MaxVertices;
//当前顶点数量
int NumVertices;
//当前边的数量
int NumEdges;
//顶点的列表
Vertex *NodeTable;
}GraphLnk;
本次我们实现的是无向图。
图的初始化
初始化包括两个部分,即数据的初始化和空间的开辟。在邻接表表示法中,还需要注意顶点的列表开辟完空间后,也需要初始化里面的数据。
//初始化图
void InitGraph(GraphLnk *g){
//初始化值
g->MaxVertices = DEFAULT_VERTEX_SIZE;
g->NumVertices = g->NumEdges = 0;
//开辟空间
g->NodeTable = (Vertex *)malloc(sizeof(Vertex) * g->MaxVertices);
assert(g->NodeTable != NULL);
//初始化
for (int i = 0; i < g->MaxVertices; i ++) {
g->NodeTable[i].data = '\0';
g->NodeTable[i].adj = NULL;
}
}
顶点和边的插入
顶点的插入
这里顶点的列表和邻接矩阵表示法一样,是一个数组。因此插入顶点只需要将顶点存入即可。
//插入顶点
void InsertVertex(GraphLnk *g,T v){
if (g->NumVertices >= g->MaxVertices) {
printf("顶点数量已达到最大\n");
return;
}
g->NodeTable[g->NumVertices ++].data = v;
}
边的插入
插入边即在顶点的边列表中存入边的信息,因此首先需要找到顶点。
//找到顶点在列表中的位置
int GetVertexPos(GraphLnk *g,T v){
for (int i = 0; i < g->NumVertices; i ++) {
if (g->NodeTable[i].data == v) {
return i;
}
}
return -1;
}
找到顶点后,需要创建一个边结点,然后将该结点存入链表中。因为我们实现的是无向图,所以两个顶点的链表都需要修改,也就是说需要创建两个边结点。
//插入边
void InsertEdge(GraphLnk *g,T v1, T v2){
int p1 = GetVertexPos(g, v1);
int p2 = GetVertexPos(g, v2);
if (p1 == -1 || p2 == -1) {
printf("有顶点不存在\n");
return;
}
//创造边结点1
Edge *s1 = (Edge *)malloc(sizeof(Edge));
assert(s1 != NULL);
s1->dest = p2;
//插入
s1->Link = g->NodeTable[p1].adj;
g->NodeTable[p1].adj = s1;
//创造边结点2
Edge *s2 = (Edge *)malloc(sizeof(Edge));
assert(s2!= NULL);
s2->dest = p1;
//插入
s2->Link = g->NodeTable[p2].adj;
g->NodeTable[p2].adj = s2;
}
顶点和边的删除
边的删除
边的删除思路上很简单:要删除两个顶点之间的边,首先得到要顶点的边列表,遍历这个链表找到要删除的边结点,然后删除即可。
由于是在单链表上做删除,还需要一个指针(v)来指向要删除结点(s)的前驱,这样删除即 v->Link = s->Link;
。可能还有人记得,如果单链表是没有头结点的结构,做删除时需要对第一个顶点特判(因为第一个顶点没有前驱)。
同时,我们做的是无线图,因此两个结点的边列表都要删除,假如我们在遍历第一个结点的边列表时就发现没有要删除的边,可以直接退出函数。
//删除边
void RemoveEdge(GraphLnk *g,T v1,T v2){
int p1 = GetVertexPos(g, v1);
int p2 = GetVertexPos(g, v2);
if (p1 == -1 || p2 == -1) {
printf("有结点不存在\n");
return;
}
Edge *s = g->NodeTable[p1].adj;
//指向s的前驱
Edge *v = NULL;
while (s != NULL && s->dest != p2)<