图的存储结构——邻接表

一.邻接表的存在意义

回忆邻接矩阵的顺序存储结构,其内存空间预先分配容易导致空间的溢出或者浪费。为了使增减结点方便,提高空间利用效率,引入链式存储法——邻接表。

二.邻接表的存储结构

邻接表的组成分为表头结点表边表,如下图所示:

图的存储结构——邻接表_第1张图片

由图可见,每一个边表(单链表)的表头结点存放在表头结点中。

存储结构分析

表头结点表采用顺序存储结构,数组的下标代表该顶点的编号。该表包含数据域data(如顶点信息)以及指针域firstarc,其指针域指向第一个与之邻接的顶点,若没有邻接点,则该指针置空,因此初始化时需将全部的顶点指针域置空

边表顾名思义采取链式存储结构,实际上是一个单链表。边链表中的结点包含邻接点域(adjvex),数据域(info)以及链域nextarc。其中,邻接点域指示与顶点v相邻接的点在图中的位置(表头结点表数组的下标位置);数据域指示该边的信息,如权值;链域指示下一个与顶点v相邻接的点

存储结构如图所示:

图的存储结构——邻接表_第2张图片

无向图的邻接表示意图:

图的存储结构——邻接表_第3张图片

特点:1.表头结点存储顶点数据以及单链表的表头结点;

2.单链表的结点链接方式不唯一,取决于算法的设计以及边的输入顺序

3.单链表的最后一个nextarc指向NULL

存储结构的优缺点

优点:

  • 便于增加或者删除单链表中的结点

  • 方便统计边数,只需要依次扫描各个单链表中的结点个数,计算其总和。

  • 空间利用效率高。若存储顶点数为n,边数为e的无向图或者无向网,那么空间复杂度为O(n+2*e);若存储顶点数为n,边数为e的有向图或者有向网,那么空间复杂度为O(n+e)

  • 方便统计顶点的出度,只需要扫描该顶点所在单链表的结点个数

缺点:

  • 难以判断两个顶点之间是否有边的存在

  • 对于有向图或网而言,难以统计顶点的入度,需要扫描其他顶点是否与该顶点相链接,时间复杂度很高O(n);若为了方便统计顶点的入度,可以采用逆邻接表的存储结构,与邻接表类似,只是单链表的邻接点域存储该弧尾顶点。

三.代码实现——邻接表的创建

以无向网为例,有向图或网的创建方式与之类似。

需要注意的是,以下顶点编号将从0开始。

数据类型定义

//文件名为:ALGraph.h
#pragma once
#include
using namespace std;

//规定最大顶点数
#define Maxvex 100
//定义顶点的数据域类型
typedef char VexType;
//定义边的数据域
typedef int OtherInfo;

//定义边表的数据类型
typedef struct ArcNode {
    int adjvex;  //该边所指向点所在图中的位置
    OtherInfo Info;  //存储边的数据
    struct ArcNode* nextarc;  //指向顶点的下一个邻接点
}ArcNode;

//定义表头结点表的数据类型
typedef struct VexNode {
    VexType data; //顶点的数据域
    struct ArcNode* firstarc; //指向第一个邻接点
}VexNode,AList[Maxvex];

//定义图的数据类型
typedef struct ALGraph {
    int vexnum, arcnum;//顶点与边的个数
    AList vertices;  //表头结点表类型
}ALGraph;

算法实现

//文件名为:ALGraph.cpp
#include"ALGraph.h"
//找顶点函数
int Findvex(const ALGraph& G, const VexType& v)
{
    for (int i = 0;i < G.vexnum;i++)
    {
        if (v == G.vertices[i].data)
        {
            return i;
        }
    }
    //表示没有找到
    return -1;
}

//创建函数的定义
bool CreateUDN(ALGraph& G)
{
    //输入顶点与边的个数
    cin >> G.vexnum >> G.arcnum;
    if (G.vexnum > Maxvex|| G.vexnum < 1)
    {
        cout << "输入有误,请重新创建" << endl;
        return false;
    }
    //输入顶点的数据域,注意顶点编号从下标0开始
    //并将指针域初始化
    for (int i = 0;i < G.vexnum;i++)
    {
        cin >> G.vertices[i].data;
        G.vertices[i].firstarc = NULL;
    }
    //输入边的数据
    for (int i = 0;i < G.arcnum;i++)
    {
        //输入一条边依附的两个顶点,通过其数据域找到该顶点的下标编号
        VexType v1, v2;
        OtherInfo w;  //边的信息
        cin >> v1 >> v2 >> w;
        int j = Findvex(G, v1);
        int k = Findvex(G, v2);
        //判断合法性
        if (j == -1 || k == -1)
        {
            i--;
            continue;
        }
        //链接顶点v1与v2
        //v1指向v2
        ArcNode* p1 = new ArcNode;
        p1->adjvex = k;
        p1->Info = w;
        p1->nextarc = G.vertices[j].firstarc;
        G.vertices[j].firstarc = p1;
        //v2指向v1
        ArcNode* p2 = new ArcNode;
        p2->adjvex = j;
        p2->Info = w;
        p2->nextarc = G.vertices[k].firstarc;
        G.vertices[k].firstarc = p2;
    }
    //创建成功
}

测试

文件名:test.cpp
#include"ALGraph.h"
//创建无向网G3并且遍历
void test02()
{
    ALGraph G;
    cout << "输入图的数据:>" << endl;
    CreateUDN(G);
    cout << "顶点链接情况;>" << endl;
    for (int i = 0;i < G.vexnum;i++)
    {
        ArcNode* p = G.vertices[i].firstarc;
        printf("%d", i);
        while (p)
        {
            int k = p->adjvex;
            printf("->%d", k);
            p = p->nextarc;
        }
        printf("\n");
    }
}

int main()
{
    test02();
    return 0;
}

输入G3的数据,输出顶点连接情况:

图的存储结构——邻接表_第4张图片

对无向图、有向图与有向网的存储

其实,存储三者的方式与存储无向网类似。

对于无向图而言,由于无需关心边的信息,在上述代码实现中删除OtherInfo Info类型即可;

对于有向图而言,同理删除OtherInfo Info类型,另外在确认两个依附顶点v1与v2后,若想创建只需要处理G.vertices[v1]的边结点信息。

对于有向网而言,在创建有向图的基础上,增加OtherInfo Info类型以及相关操作。

我的感悟

邻接表是线性表的一种推广,是一种顺序存储结构嵌套链式存储结构的数据存储结构。在本案例中,其顺序存储结构成分在于表头结点表,其空间内存预先分配,大小保持不变;其链式存储结构成分在于边表。由于单链表的表头结点存放在一维数组——表头结点表中,故谓之顺序存储结构嵌套链式存储结构。这种存储方式兼容了两者的优点,比如通过数组下标可以随机访问顶点信息。再者,在顶点个数固定的情况下,方便增删顶点间边的个数,只需要在相应的表头结点前插入新结点。当然,也继承了两者的缺点,比如表头结点表在已满的情况下无法增加新的顶点,而单链表的边信息又无法随机访问

你可能感兴趣的:(数据结构与算法,C++,算法,数据结构,链表)