数据结构 C6 图

文章目录

  • 6.1图的基本概念
      • 6.1.1图的定义(见王道书)
  • 6.2图的存储及其基本操作
    • 6.2.1邻接矩阵法
    • 6.2.2邻接表法
    • 6.2.3十字链表
    • 6.2.4邻接多重表
    • 6.2.5图的基本操作
  • 6.3图的遍历
    • 6.3.1广度优先搜索
    • 6.3.2深度优先算法
      • 与树的深度优先遍历之间的联系&& 算法实现
      • 复杂度分析
      • 深度优先生成树
      • 图的遍历和图的连通性
  • 6.4图的应用
    • 6.4.1最小生成树
      • 最小生成树的概念
      • Prim算法
      • Kruskal算法
    • 6.4.2最短路径
      • 单源最短路径-DFS算法解决单源问题
      • 单源最短路径-Dijkstra算法
      • 各顶点之间的最短路径- Floyd算法
      • 总结三种算法
    • 6.4.3有向无环图
      • 描述表达式(有向无环图的第一个应用)
    • 6.4.4 拓扑排序(有向无环图的第二个应用)
      • AOV网
      • 拓扑排序实现
      • 逆拓扑排序
      • 用DFS算法实现(逆)拓扑排序
    • 6.4.5关键路径

6.1图的基本概念

6.1.1图的定义(见王道书)


图的基本定义(


有向图/无向图


简单图/完全图,数据结构只讨论简单图


完全图/子图


连通/连通图/连通分量/最大连通分量/最小连通分量/强连通/强连通图/强连通分量/最大强连通分量/最小强连通分量


生成树/生成森林


度/入度/出度


边的权和网


稠密图/稀疏图


路径/路径长度/回路/简单路径/简单回路/距离


有向树


6.2图的存储及其基本操作

6.2.1邻接矩阵法


邻接矩阵法:
图的存储结构 有向图 无向图
对于图,它分为顶点还有边;边就是两个顶点的邻接关系,所以说要把图这个东西储存的话,就一定要分别储存点还有边。
图一般意义分为有向图还有无向图;对于前者后者顶点存储方式相同;但是对于后者存储方式是不同的;
那么我们怎么储存呢;
对于点来说,他就是一个整形数据啥的,用一个一维数组就可以了;对于边来说用二维数组了。如下图1。左边有向 右边无向;

数据结构 C6 图_第1张图片


看一个有向图的存储方法:
数据结构 C6 图_第2张图片
最后的存储结构:
数据结构 C6 图_第3张图片


看一个无向图的存储矩阵:错了(第一列最后一个元素是1)
数据结构 C6 图_第4张图片

最后转化为:
数据结构 C6 图_第5张图片


网呢 储存结构
我们之前说了,如果给每条边一个数值作为该边的权重:就变成了网;
那么我们该如何存储网呢?同样是利用矩阵和数组;如果该边不存在则存放无穷
数据结构 C6 图_第6张图片
举个例子:
数据结构 C6 图_第7张图片


网或者图在语言中是如何定义的?

#define MaxVertexNum 100
typedef char VertexType;
typedef int EdgeType;
typedef struct{
	VertexType Vex[MaxVertexNum];
	EdgeType Edge[MaxVertexNum][MaxVertexnum];
int vexnum,arcnum;
}MGraph;

数据结构 C6 图_第8张图片


性质
数据结构 C6 图_第9张图片


运算:
数据结构 C6 图_第10张图片

数据结构 C6 图_第11张图片

6.2.2邻接表法

当一个图相对比较稠密的时候,他适合采用上面一种邻接矩阵的方法 ,但是把,如果说这个图相对比较稀疏的话,那么采用以上算法就是相对比较浪费了,那用现在介绍的这种方法。邻接表法
采用顶点用顺序结构 而边用链式结构存储
数据结构 C6 图_第12张图片


比如 咱们看一个有向图的例子:
kankan是怎么储存的
数据结构 C6 图_第13张图片


再看一个无向图的例子:
数据结构 C6 图_第14张图片


储存结构:

#define MaxVertNum 100
typedef struct ArcNode{
int adjvex;
struct ArcNode *next;
//InfoType info;//表示权重
}ArcNode;
typedef struct VNode{
	VertexType data;
	ArcNode *first;
}VNode,AdjList[MaxVertexNum];
typedef struct{
	Adjust vetices;//邻接表
int vexnum,arcnum;
}ALGraph;	

特点:
数据结构 C6 图_第15张图片


比较邻接矩阵和邻接表的异同:
数据结构 C6 图_第16张图片

6.2.3十字链表

对于连接表而言 找到出边是相对来说比较容易的 但是想找到入边来说 就是比较难的 他需要遍历整个表。
那么我们我们引申出一个新的结构

数据结构 C6 图_第17张图片


存储结构

#define MaxVertexNum 100;
typedef struct ArcNode{
int tailvex,headvex;
struct ArcNode *hlink,*tlink;
//infotype info;
}ArcNode;
typedef struct VNode{
	VertexType data;
	ArcNode *firststin,*firstout;
}VNode;
typedef struct{
	VNode xlist[MaxVertexNum];
int vexnum,arcnum;
}GLGraph;

6.2.4邻接多重表

对于无向图,我们之前使用了邻接表的形式:
数据结构 C6 图_第18张图片

你看一个对于这两个储存空间,如果删除它的时候,需要遍历,而且要删除两个,这样的话会是很麻烦的。于是引用了咱们今天学习的。
数据结构 C6 图_第19张图片


#define MaxVertNum 100;
typedef struct ArcNode{
int ivex ,jvex;
struct ArcNode *ilink,*jlink;
//infotype info;
//bool mark;
}ArcNode;
typedef struct VNode{	
	VertexType data;
	ArcNode *firstedge;
}VNode;
typedef struct{
	VNode adjmulist[MaxVertexnum];
int vexnum,arcnum;
}AMLGraph;	

十字链表 邻接多重表
他们都是邻接表的优化;十字链表针对的是有向图,解决了有向图中无法快速找到某一顶点入边的这一弊端。 邻接多重表解决了无向图中每一个边要用两个边表结构来存放的弊端。
数据结构 C6 图_第20张图片

6.2.5图的基本操作

Adjacent(G,x,y) 判断图G是否存在边<x,y>(x,y).

数据结构 C6 图_第21张图片
数据结构 C6 图_第22张图片


Neighbour(G,x) 列出图G中与结点x相邻的边

数据结构 C6 图_第23张图片
数据结构 C6 图_第24张图片


InsertVertex(G,x);在图G中插入顶点x

数据结构 C6 图_第25张图片
右边更好;


DeleteVertex(G,x)从图G中删除顶点x;

数据结构 C6 图_第26张图片
右边更好;


AddEdge(G,x,y);若无向边(x,y)或者<x,y>不存在,则向图中G中添加该边

数据结构 C6 图_第27张图片
左边更好


RemoveEdge(G,x,y)若无向边(x,y)或者有向边<x,y>存在,则在图G中删除该边;

数据结构 C6 图_第28张图片


FirstNeighbor(G,x);求图G中顶点X的第一点邻接点,若有则返回顶点号。若没有邻接点或图不存在x,则返回-1NextNeighbor(G,x,y) 假设图G中顶点y是顶点x的一个邻接点,返回除了y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1

Get_edge_value(G,x,y);获取图G中边(x,y)<x,y>对应的权值v;
Set_edge_value(G,x,y); 设置G中边(x,y)<x,y>
的权值为v;

在这里插入图片描述


6.3图的遍历

从图的某一点出发,按照某种算法,沿着图中的边,对图中所有的顶点进行一次访问(只访问一次)

6.3.1广度优先搜索

数据结构 C6 图_第29张图片

它和树的层次遍历很像,但是如果又按照层次遍历的算法来 ,一定会出现错误。如下
数据结构 C6 图_第30张图片那么怎么改善呢
在这里插入图片描述
数据结构 C6 图_第31张图片


算法代码如下:

bool visited[MAX_TREE_SIZE];//定义了一个bool型的辅助标记数组
void BFSTraverse(Graph G){
for(int i=0;i<G.vexnum;++i)
		visited[i]=FALSE;
InitQueue(Q);
for(int i;i<G.vexnum;++i)
if(!visited[i])
BFS(G,i);
}
void BFS(Graph G,int v){
visit(v);
	visited[v]=TRUE;
Enqueue(Q,v);
while(!isEmpty(Q)){
Dequeue(Q,v);
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
if(!visited[w]){
				visited[w];
				visited[w]=TRUE;
Enqueue(Q,w);
}
}
}

数据结构 C6 图_第32张图片
数据结构 C6 图_第33张图片


BTS算法的性能分析:
在这里插入图片描述
数据结构 C6 图_第34张图片


关于广度优先搜索解决最短路径问题:(不懂)


==广度优先生成树:=
数据结构 C6 图_第35张图片
数据结构 C6 图_第36张图片

6.3.2深度优先算法

与树的深度优先遍历之间的联系&& 算法实现

树的优先遍历

void preorder(treenode *R ){
if(R!=NULL){
visit(R);
while(R还有下一个子树T)
Preorder(T);
}
}

数据结构 C6 图_第37张图片
数据结构 C6 图_第38张图片
图的深度优先遍历:

bool visited[MAX_VERTEX_NUM];
void DFS(Graph G,int v){
visit(v);
	visited[v]=TRUE;
for(w=FirstNeighbor(G,v);w>=0;w=NextNeihbor(G,v,w))
if(!visited[w]){
DFS(G,w);
} 
}

这辅助了一个函数调用栈:利用栈的栈进栈出来看目前正在执行的那个函数,调用了几层函数;
数据结构 C6 图_第39张图片
咱们看一看这是连通图用上面的算法就可以了,但是对于非连通图来说,无法遍历所有的结点;
优化:

数据结构 C6 图_第40张图片

复杂度分析

数据结构 C6 图_第41张图片
数据结构 C6 图_第42张图片
手动求一下
数据结构 C6 图_第43张图片
在这里插入图片描述
数据结构 C6 图_第44张图片
数据结构 C6 图_第45张图片

深度优先生成树

数据结构 C6 图_第46张图片
数据结构 C6 图_第47张图片
森林呢?
数据结构 C6 图_第48张图片

数据结构 C6 图_第49张图片

图的遍历和图的连通性

数据结构 C6 图_第50张图片
对于有向图具体问题具体分析
数据结构 C6 图_第51张图片

6.4图的应用

6.4.1最小生成树

最小生成树的概念

什么是生成树?
数据结构 C6 图_第52张图片
数据结构 C6 图_第53张图片
怎么做呢?
数据结构 C6 图_第54张图片
有没有更便宜的修路方案呢?
有的话就叫做

数据结构 C6 图_第55张图片
数据结构 C6 图_第56张图片

Prim算法

数据结构 C6 图_第57张图片
数据结构 C6 图_第58张图片
换一种连法
数据结构 C6 图_第59张图片
同一个有多种最小生成树,但是时数值都是一样的

换一个顶点开始:从农场出发
数据结构 C6 图_第60张图片
数据结构 C6 图_第61张图片
机器实现:

数据结构 C6 图_第62张图片
数据结构 C6 图_第63张图片
更新
在这里插入图片描述
数据结构 C6 图_第64张图片
数据结构 C6 图_第65张图片

Kruskal算法

数据结构 C6 图_第66张图片
数据结构 C6 图_第67张图片
机器实现:
数据结构 C6 图_第68张图片
在这里插入图片描述
在这里插入图片描述
数据结构 C6 图_第69张图片

数据结构 C6 图_第70张图片
数据结构 C6 图_第71张图片

数据结构 C6 图_第72张图片

6.4.2最短路径

数据结构 C6 图_第73张图片

单源最短路径-DFS算法解决单源问题

只有一个源头
数据结构 C6 图_第74张图片
数据结构 C6 图_第75张图片

void BFS_MIN_Distance(Graph G,int u){
for(i=0;i<G.vexnum;++i){
		d[i]=MAX;
		path[i]=-1;
}
	d[u]=0;
	visited[u]=TRUE;
Enqueue(Q,u);
while(!isEmpty(Q)){
Dequeue(Q,u);
for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w))
if(!vosoted[w]){
				d[w]=d[u]+1;
				path[w]=u;
				visited[w]=TRUE;
Enqueue(Q,w);
}
}
}

数据结构 C6 图_第76张图片

单源最短路径-Dijkstra算法

BFS算法只能解决无权图 或者权值都相同的图
数据结构 C6 图_第77张图片

数据结构 C6 图_第78张图片
数据结构 C6 图_第79张图片
数据结构 C6 图_第80张图片
数据结构 C6 图_第81张图片
机器实现以及时间复杂度
数据结构 C6 图_第82张图片
当为负权值的边的图 该算法不适用

数据结构 C6 图_第83张图片

各顶点之间的最短路径- Floyd算法

利用的是动态规划的思想
数据结构 C6 图_第84张图片

数据结构 C6 图_第85张图片
在这里插入图片描述
数据结构 C6 图_第86张图片
数据结构 C6 图_第87张图片
数据结构 C6 图_第88张图片
数据结构 C6 图_第89张图片
数据结构 C6 图_第90张图片
数据结构 C6 图_第91张图片
数据结构 C6 图_第92张图片

代码实现:

//....准备工作,根据图的信息初始化矩阵A和path(如上图)
for(int k=0;k<n;k++){
for(int i=0;i<n;i++){
for(int j=0;j<nj++){
if(A[i][j]>A[i][k]+A[k][j]){
				A[i][j]=A[i][k]+A[k][j];
				path[i][j]=k;
}
}
}
}

看一个难一点的例子:
数据结构 C6 图_第93张图片

数据结构 C6 图_第94张图片
数据结构 C6 图_第95张图片
数据结构 C6 图_第96张图片
数据结构 C6 图_第97张图片
最终得到:
数据结构 C6 图_第98张图片

如何寻找路径:
数据结构 C6 图_第99张图片
上面有说过D算法解决不了含有负数权的图,但是呢,Floyd 是可以的
在这里插入图片描述
数据结构 C6 图_第100张图片

总结三种算法

数据结构 C6 图_第101张图片

6.4.3有向无环图

有向无环图:若一个有向图中不存在环,则称为有向无环图,简称DAG图。
如下图
数据结构 C6 图_第102张图片
如下图右,有环路不能称为有向无环图。
数据结构 C6 图_第103张图片

描述表达式(有向无环图的第一个应用)

刚才讲解了什么?关于有向无环图如何描述表达式。他们之间的转化
首先是如何通过表达式转化为(有向无环)图。
方法:
step1:把各个操作数不重复的排成一排。
step2:标出各个运算符的生效顺序(先后顺序有点出入无所谓)
step3:按顺序加入运算符,注意分层。
step4:从底向上逐层检查同层的运算符时候可以合体。

举个例子:
在这里插入图片描述
把上图转化为有向无环图:(不会看-图章节视频12)
答案如下:
数据结构 C6 图_第104张图片
咱们看下一题:
在这里插入图片描述
本题按照这个顺序去实现上述操作方法。再按照下述顺序操作一遍。

在这里插入图片描述
得到的结果是什么————自己操作,不会看视频。
得到结果如下:
数据结构 C6 图_第105张图片
数据结构 C6 图_第106张图片
你会发现同一个表达式,按照不同的顺序排序,得到的结果是不唯一的。
所以得出结论:
同一个表达式,可以用不同的图去表示

6.4.4 拓扑排序(有向无环图的第二个应用)

AOV网

AOV网使用DAG图表示一个工程。注意A是表示一个活动。也就是顶点表示活动,有向边表示活动Vi进行必须先于Vj进行。如果出现了环路就一定不是AOV网。看如下不是一个AOV网,去掉红色箭头就属于一个AOV网;
数据结构 C6 图_第107张图片

拓扑排序实现

定义:在图中,有一个有向无环图的顶点组成的序列。当且仅当满足下列条件时候称之为该图的一个拓扑排序。
1.每个顶点出现且出现一次。
2.若顶点A在排序中排在顶点B的前面,则在图中不存在从顶点B到顶点A的路径。

或者定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得若存在一条从顶点A到顶点B的路径,则在排序中顶点B出现在顶点A的后面。每个AOV图都有一个或者多个拓扑排序序列。

以上是官方定义:也就是说拓扑排序就是排序而已。而且他是只能有向无环图,从前指向后的排序。排序结果可能有多种。
实现:
1.从AOV网中选择一个没有前驱(入度为0)的顶点输出。
2.从网中删除该顶点和所有以他为顶点的有向边。
重复1,2直到AOV为空为止,或者网中不存在无前驱的顶点(说明没有回路)。

那咱们看看对上述工程进行排序:下图是按照顺序从前往后的一种顺序(过程不会看视频图13)
数据结构 C6 图_第108张图片
如果图中存在回路怎么办,那么就拓扑排序不下去了,如下图
数据结构 C6 图_第109张图片


代码实现:
对于如下一个图,左边是图的结构,右边是图的储存结构。如何用代码实现如下的排序呢?
数据结构 C6 图_第110张图片

首先你需定义一个数组indegree[]表示当前各个顶点的度。定义一个输出后的排序数组print[]来记录拓扑排序。再用一个栈或者队列来按一定顺序保存和弹出相关活动获得顺序。
如下:
数据结构 C6 图_第111张图片

代码实现

bool topologicalsort(Graph G){//定义了一个bool型号的函数,看返回值返回1就是排序成功
initstack(S);
for(int i=0;i<G.vexnum;i++)
if(indegree[i]==0)
push(S,i);
push(S,i);
int count=0;//其实count表示print[]的数组序号
while(IsEmpty(S)){
Pop(S,i);
		print[count++]=i;
for(p=G.vertices[i].firstarc;p=p->nextarc){//复杂在他是一个邻接表的储存结构没有弄懂。作用是i指向的顶点入度减一,并且将入读为01的顶点压入栈。
			v=p->adjvex;
if(!(--indegree[v]))
push(S,v);
}
}
if(count<G.vexnum)
return false;
else
return ture;
}

关于时间复杂度:上述使用了邻接表,几乎每一个点和边都需要遍历一遍。故邻接表的时间复杂度如下,而矩阵存储不一样。每个点都要遍历。
数据结构 C6 图_第112张图片

逆拓扑排序

选一个出度为0的顶点输出。其他和上述三步法一模一样,我就不重复了。
对于上面的工程用,逆排序结果也不是唯一的。有回路也不能排序成功。下面是其中一种顺序排法。
数据结构 C6 图_第113张图片


关于代码:
题目:用逆拓扑排序解决下图:左边是图,右边是储存结构

数据结构 C6 图_第114张图片
代码如下:
数据结构 C6 图_第115张图片思考:

如果使用逆邻接表(每个顶点后面链接的是入边,如上图的逆邻接表如下)邻接矩阵对时间复杂度的影响如何?
数据结构 C6 图_第116张图片

用DFS算法实现(逆)拓扑排序

数据结构 C6 图_第117张图片思考:
1.如果存在环路…
2.用DFS实现拓扑排序

6.4.5关键路径

1、AOV:有向图中,用顶点表示事件,用有向边表示活动之间开始的先后顺序,则称这种有向图为AOV(Activity On Vertex)网络;AOV网络可以反应任务完成的先后顺序(拓扑排序)。
2、AOE:在实际应用中,活动除了先后关系外,还需考虑时间上的约束
在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,边上的权值表示活动的持续时间,称这样的有向图叫做边表示活动的网,简称AOE网

如图:
数据结构 C6 图_第118张图片
3、AOE网中只有一个入读为零的顶点称为始点(或源点),只有一个出度为零的顶点称为终点(或汇点)。
4、性质:
⑴ 只有在某顶点所代表的事件发生后,从该顶点出发的各活动才能开始;
⑵ 只有在进入某顶点的各活动都结束,该顶点所代表的事件才能发生。


5、完成一个工程所需要的最短时间是从源点到汇点的最长路径长度
关键路径:具有最长长度的路径,这里的路径长度是路径上的各权值之和

注意:关键路径不一定只有一条。

关键活动:关键路径上的活动称为关键活动。(需要满足 ee [i]=el [i] )

!! 关键路径的长度是整个工程所需要的最短工期,要缩短整个工期,必须要加快关键活动的进度


二、求关键活动所需要的参量
⑴  事件的最早发生时间ve[k] - 从前往后,前驱结点到当前结点所需时间,取最大值。
ve[k]是指从始点开始到顶点vk的最大路径长度。这个长度决定了 所有从顶点vk发出的活动能够开工的最早时间。
数据结构 C6 图_第119张图片
⑵ 事件的最迟发生时间 vl[k] - 从后往前,后继结点的最迟发生时间-边权值,取最小值。
vl[k]是指在不推迟整个工期的前提下, 事件vk允许的最晚发生时间。 数据结构 C6 图_第120张图片
⑶ 活动的最早开始时间e [i]
若活动ai是由弧 表示,则活动 ai的最早开始时间应等于事件vk的最早发生时间。
因此,有:e[i]=ve[k]

⑷ 活动的最晚开始时间 l [i]
活动ai的 最晚开始时间是指,在不推迟整个工期的前提下, ai 必须开始的最晚时间。若ai由弧表示,则ai的最晚开始时间要保证事件 vj 的最迟发生时间不拖后。
一般等于l(i)=vl(j)-weight(vk,vj)

数据结构 C6 图_第121张图片时间余量:当时间余量为0时候,说明这是一个关键活动。


求解路径的步骤:
数据结构 C6 图_第122张图片
举个例子:
上面是求解路径的的方法:那咱们看一道题目来操作一哈,如下
数据结构 C6 图_第123张图片
第一步:
数据结构 C6 图_第124张图片
第二步:
数据结构 C6 图_第125张图片
第三步:
数据结构 C6 图_第126张图片
第四步:
数据结构 C6 图_第127张图片
第五步:
数据结构 C6 图_第128张图片
==》找到关键路径

数据结构 C6 图_第129张图片


1 若关键活动的耗时增加,则整个工程的工期将加长;
2 缩短 关键活动的时间,可以缩短整个工程的工期
3.当缩短到一定程度时候,关键活动可能变成非关键活动
4 可能有多条关键路径,只提高一条关键路径的关键活动速度并不能缩短整个工程的工期,只有加快那些在所有关键路径上的关键活动才能达到缩短工期的目的

你可能感兴趣的:(数据结构)