数据结构——图的存储

1 存储

1.1 数组表示法(邻接矩阵)

1.1.1 说明

图没有顺序存储结构,但可以借助二维数组来表示元素间的关系。
首先,建立一个顶点表(记录各个顶点信息)。
设图A=(V,E)有n个顶点,则顶点表Vex[n]如下:
顶点表
其次,建立一个邻接矩阵(表示各个顶点之间关系)。
图的邻接矩阵是一个二维数组A.arcs[n][n],定义为:

A.arcs[ i ][ j ] = {
1,如果<i, j> ∈ E或者(i, j) ∈ E
0,否则
}

1.1.2 特点

(1)无向图的邻接矩阵是对称的,有向图的邻接矩阵可能是不对称的。
(2)无向图顶点i的度=第i行(列)中1的个数;有向图顶点的出度=第i行元素之和,入度=第i列元素之和,顶点的度=第i行元素之和+第i列元素之和。
(3)完全图的邻接矩阵中,对角元素为0,其余1。
优点
(1)直观、简单、好理解;
(2)方便检查任意一对顶点间是否存在边;
(3)方便找任一顶点的“度”(从该点发出的边数为“出度”,指向该点的边数为“入度”)
缺点
(1)不便于增加和删除顶点;
(2)浪费空间:存稀疏图有大量无效元素,存稠密图就比较nice;
(3)浪费时间:统计稀疏图中一共有多少边。O(n2)

1.1.3 样例

邻接矩阵存储无向网
无向网
如,上图可用邻接矩阵存储为如下格式:
无向网
算法思想:
(1)输入总顶点数和总边数。
(2)依次输入点的信息存入顶点表中。
(3)初始化邻接矩阵,使每个权值初始化为最大值。
(4)构造邻接矩阵。
存储无向图、有向网和有向图同理。
如A-level7003,利用邻接矩阵存储无向图的具体代码实现如下:

#include <iostream>
#include <cstring>
using namespace std;
#define MAXNUM 100

struct graph {
    string vex[MAXNUM];	// 顶点表
    int arc[MAXNUM][MAXNUM];	// 邻接矩阵
    int vexnum, arcnum;		// 顶点数和边数
};

// 获取顶点在顶点表中所对应的下标
int locatev(graph *h, string v) {
    int index = -1;
    if (!h)
    {
        return index;
    }
    
    for (int i = 0; i < h->vexnum; i++)
    {
        if (v == h->vex[i])
        {
            index = i;
            break;
        }
    }
    
    return index;
}

void printg(graph *h) {
    if (!h)
    {
        return ;
    }
    
    for (int i = 0; i < h->vexnum; i++)
    {
        for (int j = 0; j < h->vexnum; j++)
        {
            cout << h->arc[i][j] << ' ';
        }
        cout << endl;
    }   
}

void creatg(graph *h) {
    string v1, v2;
    int idx, idy;
    if (!h)
    {
        return ;
    }
    
	// 确定顶点数和边数
    cin >> h->vexnum >> h->arcnum;
    for (int i = 0; i < h->vexnum; i++)
    {
        cin >> h->vex[i];	// 填充顶点信息
    }
    
    for (int i = 0; i < h->arcnum; i++)
    {
        cin >> v1 >> v2;
        idx = locatev(h, v1);
        idy = locatev(h, v2);
        if (idx == -1 || idy == -1)
        {
            return ;
        }
		// 通过获取到的下标值确定邻接矩阵对应位置
        h->arc[idx][idy] = h->arc[idy][idx] = 1;
    }  
}

int main() {
    graph g;
    memset(&g, 0, sizeof(graph));
    creatg(&g);
    printg(&g);
    return 0;
}

1.2 链式表示法(邻接表)

1.2.1 说明

无向图
怕大家脑补不过来,画图举例,如上图所示。
首先,利用结构体数组创建顶点表,将顶点信息填充至结构体中对应数据成员。

// 顶点结构体
struct vnode {
	int data;		// 顶点信息
	arcnode * head;	// 邻接表指针
} vlist[MAXNUM];

顶点结构体数组
其次,构建邻接表,将表头填充至结构体中对应指针,以确保每个顶点可以访问其所邻接的顶点。

// 边结构体
struct arcnode {
	int vindex;	// 邻接点在顶点表中对应的数组下标
	arcnode *next;	// 下一个邻接点
	int value;	// 边的附属信息,如权值
};

由图可知,各顶点的邻接点为:
a:b、c
b:a、d、e
c:a、d
d:b、c
e:b
可以对比顶点表得出每个顶点其邻接点的对应下标,然后存入边结构体中,如下表所示:
注:可能,有人会问,为什么不直接存储邻接点?搞这么麻烦去存储邻接点下标!实际上还是看具体需求,如果我们后面要基于图做深度优先搜索,那么存储下标就方便我们每次直接深入搜索,否则,你每找到一个邻接点都需要确定一下它的下标,然后根据下标获取到该顶点的邻接表。既然是一劳永逸之事,何乐而不为呢?
邻接表
最后将邻接表头结点地址复制给顶点表对应顶点的head指针即可,构建流程如下图所示:
在这里插入图片描述

1.2.2 特点

(1)无向图邻接表不唯一;
(2)若无向图中有n个顶点、e条边,则其邻接表需n个头结点和2e个表结点。适宜存储稀疏图。
(3)无向图中顶点vi的度为第i个单链表中的结点数。
(4)对于邻接表(存储出度边)来说,有向图顶点vi的出度为第i个单链表中的结点个数;顶点vi的入度为整个单链表中邻接点域值是i-1的结点个数。逆邻接表(存储入度边)则相反。

1.2.3 样例

邻接表存储无向图
算法思想:
(1)输入总顶点数和总边数。
(2)建立顶点表:依次输入点的信息存入顶点表中,使每个表头结点的指针域初始化为NULL。
(3)创建邻接表:依次输入每条边依附的两个顶点,确定两个顶点的序号i和j,建立边结点,将此边结点分别插入到vi和vj对应的两个边链表的头部。
代码实现:

#include <iostream>
using namespace std;
#define MAXNUM 100

// 边结构体
struct arcnode {
    int index;
    arcnode * next;
};

// 点结构体
typedef struct vnode {
    char value;
    arcnode *firstarc;
} vnode, vlist[MAXNUM];

// 图结构体
struct graph {
    int vexnum, arcnum;
    vlist vertices;
};

// 获取邻接点所对应的数组下标
int locatev(graph *g, char v) {
    int index = -1;
    if (!g)
    {
        return index;
    }
    
    for (int i = 0; i < g->vexnum; i++)
    {
        if (v == g->vertices[i].value)
        {
            index = i;
            break;
        }
    }
    
    return index;
}

// 存储图
void creatg(graph *g) {
    char v1, v2;
    int idx, idy;
    if (!g)
    {
        return ;
    }
    
	cin >> g->vexnum >> g->arcnum;
	// 构建顶点表
    for (int i = 0; i < g->vexnum; i++)
    {
        cin >> g->vertices[i].value;
        g->vertices[i].firstarc = NULL;
    }
    
	// 构建邻接表
    for (int i = 0; i < g->arcnum; i++)
    {
        cin >> v1 >> v2;
        idx = locatev(g, v1);
        idy = locatev(g, v2);
		// 单链表头插法,实现v1->v2
        arcnode *p1 = new arcnode;
        p1->index = idy;
        p1->next = g->vertices[idx].firstarc;
        g->vertices[idx].firstarc = p1;
       
		// 单链表头插法,实现v2->v1
        arcnode *p2 = new arcnode;
        p2->index = idx;
        p2->next = g->vertices[idy].firstarc;
        g->vertices[idy].firstarc = p2;
    }  
}

int main() {
    graph g;
    memset(&g, 0, sizeof(graph));
    creatg(&g);
    return 0;
}

2 应用

作业练习:
点击链接:安庆程果少儿编程
A-level5002(链表知识点回顾)
A-level7003
在这里插入图片描述

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值