数据结构(17.2)图之邻接表存储

本文介绍图的邻接表存储方式,每个顶点是一个单链表,包含顶点结点和边结点。讨论了图的初始化、顶点和边的插入与删除操作,特别强调了无向图在邻接表中的实现细节,如边结点存储另一端顶点地址,并分析了删除操作中的注意事项和优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

在上一篇文章中,已经详细介绍了图的四种存储结构的区别,这里我们就不再过多说明了。

图的邻接表存储里,每一个顶点类似于一个单链表,头结点即顶点结点,其余的结点即边结点;每一个顶点又需要存储到一个列表中,就形成了一个邻接表。

img_1

无论是头结点还是边结点,都有数据域和指针域两个部分。顶点结点的指针域指向其第一个边结点,而边结点的指针域指向本顶点的下一个边结点,类似于单链表的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) {
   
   
        v = s;
        s 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值