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

前言

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

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

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)<
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
邻接表是一种常见的存储方式,可以用于实现的基本操作。下面是使用邻接表存储的一个无向的示例: ![邻接表存储示例](https://img-blog.csdnimg.cn/20210306201453559.png) 在邻接表中,每个顶点都对应一个链表,链表中存储与该顶点相邻的所有顶点。对于无向来说,由于每条边都可以看做是两个端点之间的连接,因此每条边都需要在两个对应的链表中存储。 下面是使用C++代码实现上述邻接表存储: ```c++ #include <iostream> #include <vector> using namespace std; // 邻接表存储的节点结构体 struct Node { int val; // 节点的值 vector<int> edges; // 与该节点相邻的所有节点的索引 }; // 邻接表存储的结构体 struct Graph { vector<Node> nodes; // 所有节点的数组 // 添加一条边 void addEdge(int u, int v) { nodes[u].edges.push_back(v); nodes[v].edges.push_back(u); } }; int main() { Graph g; g.nodes.resize(6); // 初始化6个节点 // 添加边 g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 4); g.addEdge(3, 4); g.addEdge(3, 5); g.addEdge(4, 5); // 输出邻接表 for (int i = 0; i < g.nodes.size(); i++) { cout << i << ": "; for (int j = 0; j < g.nodes[i].edges.size(); j++) { cout << g.nodes[i].edges[j] << " "; } cout << endl; } return 0; } ``` 运行上述代码,输出结果为: ``` 0: 1 2 1: 0 2 3 2: 0 1 4 3: 1 4 5 4: 2 3 5 5: 3 4 ``` 可以看到,每个节点的链表中存储了与该节点相邻的节点的索引,从而实现了存储
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值