图
今天学习最后一节内容,这一节内容我只介绍图的表示、图的建立以及图的遍历,而一些图的经典算法,我准备在算法那一专栏进行解释,至于图节点的插入,删除等更高深的操作,需要在拥有以上的基础下,并且足够熟悉才可以
文章目录:
原创点:Harsha老哥似乎只有图的表示这一内容,并且没有代码,所以这一节图的表示的代码和其他内容都由秋秋亲自完成。
1.图的介绍
这里我只说明学习图的前提:你需要学习线性代数、离散数学、以及高等数学等知识;
而这一节的基本内容只有图的一小部分,你可以理解为图的基础;
至于图的进阶内容,我更希望你们取学习图论,图的高阶知识作为一本书。
(1)图有关术语:
邻接与关联、顶点的度、简单图、完全图、权值与网、子图、路径与回路、连通图、强连通图、生成树;这些内容需要读者有概念及基础,不然这节课会很吃力。
(2)图的基本运算:
CrerateGraph:创建图
DestoryGraph:销毁图
LocateVertex:查找顶点
FirstAdjVertex:查找某顶点的邻接顶点
InsertVertex:插入一个顶点
DeleteVertex:删除一个顶点
InsertArc:插入一条边
DeleteArc:删除一条边
TraverseGraph:遍历图
2. 图的表示
(1)边列表表示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bchPGuXs-1662384057540)(:/692d74d6b9b94402a17c50911d577f01)]
结构体的定义
struct Edge
{
char* startV;
char* endV;
int weight;
};
char vertex_list[MAXSIZE];
struct Edge edge_list[MAXSIZE];
在C++中,你可以用 string* startV ;
(2) 邻接矩阵表示图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N8N9RyAa-1662384057542)(:/9e06481755244d3e88418159d5f1b08f)]
上图关于主对角线对称,当然这不符合有向图
结构体的定义:
#define MAXLEN 100 //图的最大顶点数
typedef struct vertex
{ int num; //用于描述顶点的序号信息,此处整数
··· //剩下的信息暂时忽略
}Datatype;
struct Mgraph
{
int n,e; //记录顶点数n和边的个数e
int edges[MAXLEN+1][MAXLEN+1]; //邻接矩阵,下表从1开始
Datatype vexs[MAXLEN+1]; //储存顶点的数组,下标从1开始
};
对于稀疏图来说,邻接矩阵不够简洁,因为会有大量的零,但是这并不是重要的元素;
对于稠密图来说,邻接矩阵可能是很好的表达方式了。
创建图的邻接矩阵:
//创建一个n顶点,e条无向边的无向图G的邻接矩阵
void CreateGraph(struct Mgraph* g,int n,int e)
{
int i,j,k,v1,v2;
g->n = n;
g->e = e;
for(i=1;i<g->n;i++) //序号从1开始
g->ves[i].num = i;
for(i=1;i<=g->n;i++) //初始化邻接矩阵
for(i=1;j<=g->n;i++)
g->edges[i][j] = 0;
for(k=1;k<=g->e;k++)
{
scanf("%d%d",&v1,&v2);
g->edges[v1][v2] = 1; //无向图都要执行
g->edges[v2][v1] = 1; //有向图不执行该语句
}
}
其时间复杂度为O(N^2).
(3)邻接表表示图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rZiTpBHX-1662384057543)(:/4bd18a7f827c4af2bd98b492ec7f7538)]
结构体
#define MAXLEN 100
struct EdgeNode //边链表节点类型
{
int adjvex;
struct EdgeNode* next;
};
struct VertexNode //表头节点类型
{
int vertex;
struct EdgeNode* firstedge;
};
struct ALGraph //图的邻接表类型
{
struct VertexNode Adjlist[MAXLEN+1];
int n,e;
};
建立图的邻接表
//创建一个n顶点,e条无向边的无向图G的邻接表
void CreateAdjList(struct ALGraph* g,int n, int e)
{
struct EdgeNode* ptr;
int k,v1,v2;
g->n = n;
g->e = e;
for(k=1;k<=g->n;k++)
{
g->adglist[k].vertex=k; //假设各个顶点对应的序号为1~n
g->adjlist[k].firstedge = NULL; //边链表初始化
}
for(k=1;k<=g->e;k++) //按边数循环e次
{
scanf("%d%d",&v1,&v2); //输入一条边所对应的两个顶点
ptr = (struct EdgeNode*)malloc(sizeof(struct EdgeNode));
ptr->vertex = v2; //写入数据
ptr->next = g->adjlist[v1].firstedge; //头插法插入v1对应的边链表
g->adjlist[v].firstedge = ptr;
// 有向图不执行以下语句
ptr = (struct EdgeNode*)malloc(sizeof(struct EdgeNode));
ptr->adgvex = v1;
ptr->next = g->adglist[v2].firstedge;
g->adglist[v2].firstedge = ptr;
}
}
对于稀疏图来说,邻接表确实是存储数据的一个优良算法,因为其时间复杂度要比邻接矩阵的要小,为:O(N+e).
3.图的遍历
图的遍历在最初代码实现的时候,总有这两个问题:重复经过和顶点遗漏;
针对第一个问题:设置一个数组,若值为零则未访问,值为一则已访问;
针对第二个问题,在遍历一遍后,对数组进行检查。
本节带来两个算法:图的深度优先遍历和广度优先遍历。
两个算法的最大区别:
深度优先建立在递归上,使用的是递归的思想;
广度优先建立在队列上,使用的是队列的思想;
(1)深度优先遍历
就是一条路走到黑,知道走不动了,遍历完了,选择下一个邻接节点进行重新遍历;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4RcxqJ2G-1662384057543)(:/a7499732037f4e54bf160b63d9b84666)]
<1> 邻接矩阵的深度优先遍历
int visited[MAXLEN+1]
void DFS(ALGraph* g,int i) //算法
{
int j;
pritnf("%3d",g->vexs[i].num);//输出该信息
visited[i] = 1; //修改标志
for(j=1;j<=g->n;j++)
if((g->edges[i][j] == 1)&&!visited[j]) //等价于if ( visited[i]==0 )
DFS(g,j)
}
void DFStraverse(MGraph* g) //只需要传递指针,进行前期准备工作
{
int i;
for(i=1;i<=g->n;i++)
visited[i]=0; //初始化
for(i=1;i<=g->n;i++) //确保每一个都进行遍历
if(!visited[i]) //visited函数进行排查,确保每一个节点都遍历
DFS(g,i); //递归
}
<2> 邻接表的深度优先遍历
int visited[MAXLEN+1]
void DFS(ALGraph* g,int i) //算法
{
EdgeNode* p;
printf("%3d",g->adglist[i].vertex); //输出该信息
visited[i] = 1; //修改标志
for(p=g->Adjlist[i].firstedge;p!=NULL;p=p->next)
if(!visited[p->adjvex]) //等价于if ( visited[i]==0 )
DFS(g,p->adjvex);
}
void DFStraverse(ALGraph* g) //只需要传递指针,进行前期准备工作
{
int i;
for(i=1;i<=g->n;i++)
visited[i]=0; //初始化
for(i=1;i<=g->n;i++) //确保每一个都进行遍历
if(!visited[i])
DFS(g,i);
}
(2)广度优先遍历
从上到下,从左到右,依次遍历。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P2XJteW4-1662384057544)(:/15da9ee8c09542ab888d685fab0d108a)]
<1> 邻接矩阵的广义优先遍历
void BFS(MGraph* g,int v) //从顶点v开始
{
int j;
SeqQueue* q; //开创一个队列
InitQueue(q); //队列初始化
visited[v] = 1; //标记,表示已访问
EnQueue(q,v); //入队
while(!QueueEmpty q)
{
DeQueue(q,&v); //出队
for(j=1;j<=g->n;j++)
if(g->edges[v][j]==1&&!visited[j])
{
visited[j]==1;
EnQueue(q,j); //入队
}
}
}
<2> 邻接表的广义优先遍历
void BFS(ALGraph* g,int v) //从顶点v开始
{
struct EdgeNode* p;
SeqQueue* q; //开创一个队列
InitQueue(q);
visited[v] = 1; //标记,表示已访问
EnQueue(q,v);
while(!QueueEmpty q)
{
DeQueue(q,&v);
p=g->adjlist[v].firstedge;
while(p!=NULL)
{
if(!visited[p-adjvex])
{
visited[p-adjvex]==1;
EnQueue(q,p-adjvex);
}
p = p->next;
}
}
}
- 最后以上内容就是这么多,希望读者多加练习,会很容易了解数据结构的。
Harsha Suryanarayana的熟肉
同时也感谢up主fengmuzi2003的翻译。 - 这就是本专栏的全部内容了,确实比预计的少了很多,希望读者可以理解秋秋。