图的存储——邻接表

点击打开在线编译器,边学边练

1. 邻接表概念

邻接表(Adjacency List)顾名思义,就是通过链表或者利用数组模拟链表的方式将图的相连接关系表示的一种方法,存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。

如图:

383.png

(如图为一个图关系)

例图展示了一个图状关系,结点1指向2和3,2可以指向4和5,而3指向4……这样的指向表示该图是单向的,意思是只允许1到2而不允许2到1(除非有两个箭头相互指向),那么,依据这个指向关系,可以得到邻接表如下:

384.png

(如图为该图所表示的邻接表)

这里必须要特别注意邻接表的结尾以空或者一个特殊的标记,表示到达结尾。在一些需要快速表达概念的场合,可以将空结点的指向忽略不表达。

那么如果是双向图,意思是1与2联通,可以由1走向2,同时也可以由2走向1的情况时,这张表又是如下表示

385.png


可以见的,这双向图和单向图的邻接表表达方式是不一样的,双向图还要将联通方式表达,1联通2,在1结点的连接关系中要将2结点加入以表达1能够走向2,此外还需要再2结点中将1结点加入以表达2能够走向1,这可能会稍微显得麻烦但是确是值得的代价。

具体表达就是:n个顶点e条边的无向图的邻接表表示中有n个顶点表结点和2e个边表结点。(换句话说,每条边(i,j)在邻接表 中出现两次:一次在关于i的邻接表中,另一次在关于j的邻接表中)。

观察上面两个图,连接到空结点的那一条边不算,那么双向图的边正好就是单向图的两倍。

2. 邻接表的特点

在表达邻接表的适用情况时,我们首先要与邻接矩阵进行相互比较

邻接矩阵存在以下缺点

a) 浪费空间—— 存稀疏图(点很多而边很少)有大量无效元素

b) 浪费时间—— 统计稀疏图中一共有多少条边

恨明显,使用矩阵的方式,仅仅是让我们人类更加直观的观察图的关系而已,对于稀疏图(即结点很多但是边很少的图,或者表达为弱连接图)而言时间和空间的浪费还是很大的,邻接表就相当于是一种改良,对于稀疏图能够很好的适应,使用较少的空间和时间就能表达。

在常规情况下,邻接表是O(n+e)的复杂程度(n表示节点数,e表示边长),临界矩阵则是O(n^2)的复杂程度。

3. 代码构成

设计思路并不唯一,甚至你可以利用结构体数组进行模拟,本代码仅提供一份适用指针的单向图C++设计代码,思路是:将节点的adjvex赋值为v,指向下一条边的指针赋值为NULL输入之后判断图中的起始顶点指向的第一条边的指针是否为空假如为空那么将当前顶点的firstarc指针指向这个节点加入不为空那么调用插入到链表中的insertNode方法,遍历链表将节点插入到链表的最后面。

#include<stdio.h>
#include<iostream>
#include<malloc.h>
#define maxSize 1000         
using namespace std;
typedef struct ArcNode {
    int adjvex;
    struct ArcNode *nextarc;
} ArcNode;
 
typedef struct {
    int data;
    ArcNode *firstarc;
} Vnode;
 
//可以利用结构体整体结构,也可以拆分结构体变为单独搜索
typedef struct {
    Vnode adjlist[maxSize];
    int n, e;
} AGraph;
 
AGraph *graph;
 
//插入链表末尾
void  insertNode(ArcNode *node, ArcNode *newNode) {
    ArcNode *p = node;
    while(p->nextarc != NULL) {
        p = p->nextarc;
    }
    p->nextarc = newNode;
}
 
void create() {
    graph = (AGraph*)malloc(sizeof(AGraph));
    cout << "输入顶点的数目: " << endl;
    cin >> graph->n;
    cout << "输入图中边的数目: " << endl;
    cin >> graph->e;
 
    int u = -1, v = -1;
    for(int i = 0; i < graph->n; i++) {
        graph->adjlist[i].firstarc = NULL;
    }
 
    ArcNode *node;
    for(int i = 0; i < graph->e; i++) {
        cout<<"请输入联通点A与B"<<endl;
        cin >> u >> v ;
        node = (ArcNode *)malloc(sizeof(ArcNode));
        node->adjvex = v;
        node->nextarc = NULL;
        graph->adjlist[u].data = u;
        if(graph->adjlist[u].firstarc == NULL) {
            //边
            graph->adjlist[u].firstarc = node;
        } else {
            //插入边
            insertNode(graph->adjlist[u].firstarc, node);
        }
    }
}
 
void  travseTree() {
    for(int i = 0; i < graph->n; i++) {
        if(graph->adjlist[i].firstarc != NULL) {
            cout <<"与"<< i << "连接的点有:";
            ArcNode *p = graph->adjlist[i].firstarc;
            while(p != NULL) {
                cout << p->adjvex <<  " ";
                p = p->nextarc;
            }
            cout << endl;
        }
    }
}
 
int main(void) {
    create();
    travseTree();
    return 0;
}


第一章 数据结构入门
第二章 链表
第三章 栈
第四章 队列
第五章 从C语言到C++
第六章 串,数组,矩阵,广义表
第七章 树
第八章 图
第九章 算法—查找
第十章 算法—排序
第十一章 算法&竞赛,思维培养
第十二章 后记