数据结构与算法——图的讲解(二)(图的存储、图的遍历)
一、图的存储结构
人类对图的操作常常是有直观的平面图,比如地图上的相关的点、线。
而C/C++编码时是是无法识别相关的图的,这需要我们用特别的逻辑与存储结构结构(邻接表、邻接矩阵)来实现建图的的操作,那我们试着用邻接表、邻接矩阵对下图进行表示吧!
点和边的英语如图所示,方便大家阅读代码
采取不同的存储结构对图的相关运算操作的时间、空间复杂度有较大的影响,我们待会分析一下。
1. 邻接矩阵
1.邻接矩阵的定义
顾名思义,此方法就是采用矩阵记录点之间的对应关系。
结点数为n的图G需要用n × n的矩阵,若将G的各点用 v1,v2,v3…来表示,则有以下两种情况:
1.无权图:2.带权图
代码如下:
#define MAX_SIZE 1024
typedef char VextexType;//点的名称
typedef int EdgeType;//带权边的权值
typedef struct
{
VertexType Vex[MAX_SIZE];//顶点表
EdgeType Edge[MAX_SIZE][MAX_SIZE];//边表,也就是邻接矩阵
int vexnum,arcnum;//顶点数和弧数
}MGraph;
如下图:
口诀:入列出行
对第 j 行:本行累计和表示结点 Aj 的出度(A j都指向谁)
对第 i 列:本列累计和表示结点 Ai 的入度(都谁指向A i)
若是无向图没有指向关系,那么该矩阵一定是对称的,可以采用矩阵的压缩,参看我往期的文章。
2. 邻接表
1.邻接表的定义
采用数组表示所有的顶点,但是每个顶点作为链表的头节点,此链表表示所有依附于顶点i的边。
#define MAX_SIZE 1024
typedef struct ArcNode //对边的定义
{
int adjvex;//边指向的点
struct ArcNode *next;//以同一起点的下一条边
int weight;//边权重
}ArcNode;
typedef struct VNode//对点的定义
{
VertexType data;//顶点的名称
ArcNode* first;//点的第一条边
}VNode,AdjList[MAX_SIZE];
typedef struct {
AdjList vertices;邻接表
int vexnum,arcnum;
}ALGraph;
相信大家可能有点懵,那么我们来讨论一下
Q:边的结构里面的next呢,顶点的结构的first是指什么?
A:这是因为邻接表就是N行链表,每一行都相当于一个链表,N为顶点数量,链表头节点就是链表中的每一个顶点,first是指从该点出发的某一条边,next是指由该点出发的边 的另一条也从该顶点出发的边
Q:边表也就是链表,next指向的各边有没有先后顺序?
A:next的指向没有先后顺序,所以邻接表不唯一
tips:我们引入next的原因是在进行图的遍历时,能够遍历到所有结点,以及求单源最短距离时,当通过此边找不到时可以回溯到顶点,选择同顶点的下一条边进行遍历找最优解。
下图右边的邻接表:绿色的是顶点,黄色的是边,边里面的字符表示该边临接的顶点,也就是next
2.代码实现
< 1初始化邻接表
void Init_pic(ALGraph* G)
{
G->vertices = new AdjList[Maxsize];//开辟空间
G->arcnum = 0;//边数为0
G->vexnum = 0;//点数为0
}
< 2 进行命名转化操作
引入这一步骤的目的是我们的顶点都是有名字的 通常是char类型的,我们的数组里都是数字如a[n],还要进行转化操作
通过顶点对应的字符寻找顶点在图中的顶点,也就是邻接表的第一列
找到返回编号,找不到返回 -1
int Location(ALGraph* G, char c)//通过顶点对应的字符寻找顶点在图中的邻接点
{
for (int i = 0; i < G->vex; i++)
{
if (G->vertices[i].data == c)
{
return i;
}
}
return -1;
}
< 3 点线关联
注意我这里向链表插入同顶点的边时使用的是前插法
读者也可以使用后插法,改变的只是搜索顺序
void Create(ALGraph* G)
{
cout << "请输入该图的顶点数以及边数" << endl;
cin >> G->vexnum >> G->arcnum;
cout << "请输入相关节点" << endl;
for (int i = 0; i < G->vexnum; i++)
{
cin >> G->vertices[i].data;//不断输入顶点的字符(名称)
G->vertices[i].first = NULL;
}
char v1 = 0, v2 = 0;//保存输入节点的字符
int i1, i2;//保存顶点在数组的下标,i1是起始点
int weight = 0;//边的权重初始化为0
cout << "请输入有关联边的两个顶点和边的权重,按指向的顺序输入" << endl;
for (int i = 0; i < G->arcnum; i++)
{
cin >> v1 >> v2>>weight;
i1 = Location(G, v1);//定位到下标,字符转数字
i2 = Location(G, v2);
if (i1!=-1&&i2!=-1)//同时存在这两点时
{
ArcNode* tmp = new ArcNode;
tmp->adjvex = i2; //别忘了adjvex是该边指向的顶点序号
tmp->next = G->vertices[i1].first;//前插法
tmp->weight = weight;
G->vertices[i1].first = tmp;
}
}
}
< 4 输入并构建图
关于文章开始的那个图我们可以这样输入
3. 十字链表
4. 邻接多重表
二、图的遍历
图的遍历算法是指从图中的某一顶点出发,按照某种搜索方法沿着图中边对图中所有结点进行访问