数据结构(六)图

1 定义

有向图、无向图、相邻的、出度、入度、路径、简单路径、子图、母图、支撑子图、连通、AOV网、拓扑序列、AOE网、关键路径、源点、汇点

2 图的储存结构

2.1 邻接矩阵

矩阵i行j列的数字是<v i _i i,v j _j j>的权值,如果是无权图就是1与0,且a i i _{ii} ii永远为0。

如果是无向图,那就是对称矩阵。

2.2 邻接表

每一个点作为一个链表头【下标,指针】,后面跟着这个点指向的所有结点【下标,距离,指针】,即通过存储n个头结点记录n行链表。

3 图的遍历方法

3.1 深度优先遍历

深度优先遍历是优先向更深的地方遍历。为了防止有环出现导致图的遍历永远无法停止,所以要用visited数组记录该结点是否被遍历过。

3.1.1 递归

void dfs(int i){		//i是标号
	p = Head[i]->next;	//遍历邻接表中第i行链表
	while(p){			//遍历以i为头结点的链表
		if(!visited[p->data]){		//如果没访问过这个结点
			visited[p->data]=1;		//标记访问结点
			dfs(p->data);			//递归访问这个结点
		}
		p = p->next;
	}
}

3.1.2 非递归

用堆栈实现,将最开始的结点入栈。

每次弹出一个结点,并将其所有指向的结点都入栈,入栈时要记得更新visited防止结点重复入栈,直到栈空遍历结束。

3.2 广度优先遍历

用队列实现,将最开始的结点入队。

每次弹出一个结点,并将其所有指向的结点都入队,入队时要记得更新visited防止结点重复入队,直到队空遍历结束。

(这三行是从3.1.2复制过来改的)

4 拓扑排序

是指,有向图的边表示任务的先后关系,并且要一个序列,满足其所有前后关系。

思路:找到入度为0的结点,将这个结点(有多个结点随便选一个即可)放入序列,并且在图中删除这个结点,删除这个结点指向其他结点的线。一直删没。如果还剩余结点,那就证明图中有环。(判断图中是否有环的方式)

5 关键路径

在AOV网的基础上,把每条线赋值为活动需要的时间,得到AOE网。

需要注意的是,在AOV网那一节中,每个结点是一个活动,而在AOE网这节中,每条线是一个活动,其值为活动的时间。

5.1 画图的思想

一、计算每个活动<a,b>的最早开始时间,即计算所有他的前置活动都需要做完的最晚时刻(多个值中的最大值)。

如果无法理解,那么如下图的例子中:T2的到达时间是6,做完a4到达T5的时间最早是7,T3的到达时间是4,做完a5到达T5的时间最早是5。而T5需要让a4和a5都做完之后才能做,所以即使a5做完时刻是5,也要等a4做完,于是a8和a9的最早开始时间T5都为7。

如下图(从左至右计算,我没画箭头,这是一个有向图,理解就好):
在这里插入图片描述
二、计算每个活动<a,b>的最晚开始时间,这要借助刚刚通过计算时刻找到的到达汇点的时刻,并且向前推出,每个活动的最晚开始时间。
在这里插入图片描述
三、对比两个图,找到两个图中,活动的开始时刻一样的点(例a4的开始时刻为T2上橙色的数字6),所有点连成的路线即为这个图的关键路径。(可以有多条关键路径)

5.2 代码的思想

1.在读取图中的线的时候,就记录每个点的入度和出度。

2.找一个入度为0的点,计算以该点为起点的每条线结束后到达下一个点的值,记为新值,并且删除这条线,如果新值比已有值更大则更新。(入度的思路和拓扑排序有借鉴之处)

例: 最开始T1=0,计算出T2=6,T3=4,T4=5。找到此时入度为0的T3,计算出T5=5;找到入度为0的T2,计算出T5=7>5,更新T5=7。此时T5入度才为0,才可以确定T5最终的值为7,以T5为起点继续计算。

3.反过来的时候就是,找一个出度为0的点,计算该点作为终点的前一个点的值,找每个点的最小值。

4.以上两个用两个数组存即可,比较两个数组中哪个下标的值相同即为关键路径。

6 最短路径问题

6.1 无权最短路径问题

从一个点广度优先遍历,记录遍历层,获得该点需要从起始点走几步。

如果需要输出路径,可以通过寻找,该结点记录的步数为n,寻找终点为该节点,起点的步数记录为n-1的线,递归找到起点,反向输出路径。

6.2 正权最短路径问题(dijkstra)

这里是说是正权,无论图是有向还是无向,都可以用dijkstra算法。

visited记录是否已经遍历过该点,dist记录起点到达该点的路径长度,path记录这个点的路径长度是由哪个点走过来的。

1.全部初始化visited=0,dist=-1,path=-1。
2.首先找到起点,dist记录为0。

3.循环
【找到dist最小的一个点a,更新这个点的visited=0。
遍历a的所有连线<a,b>中,另一个点b的visited=0的,计算b.dista.dist+<a,b> 谁小(dist=-1算作无穷大),如果后者更小,那么更新b.dist,以及b.path=a】

4.当找不到dist != -1的点时,循环结束。

5.如果想找到从起点到某点的路径,就一直找path值直到某次path=-1为止。

6.3 每对顶点之间最短路径

dijkstra可以求得一个点到其他所有点的最短距离和路径,如果寻找全部点需要以每个点开头运行一遍,但接下来这个算法可以寻找所有点之间的最短距离和路径。

遍历n个点,寻找是否这个点可以作为中间点缩短两个点之间的距离。即,在关注点k的时候,寻找是否存在点i和j,a[i][k]+a[k][j]<a[i][j],如果存在就更新i到j的路线。

这里比较不好理解的是path,是指i到j这一整条路径上j的前一个点,比如i->j的路线为i->h->k->j,则path[i][j]=k。

for(int k=0;k<n;k++){				//第k次更新 
	for(int i=0;i<n;i++){			//第i行 
		if(i!=k){
			for(int j=0;j<n;j++){	//第j列 
				if(j!=k&&j!=i&&a[i][k]!=-1&&a[k][j]!=-1			//可以计算
				&&(a[i][j]==-1||a[i][k]+a[k][j]<a[i][j])){		//比之前的短
					a[i][j]=a[i][k]+a[k][j];	//更新i到j的距离
					path[i][j]=path[k][j];		//path[i][j]存储i到j路径上j前面那个点
}}}}}

6.4 满足约束的最短路径

不想更这个了,感觉用处不大。

7 最小支撑树

7.1 普利姆算法(一个集合)

第一次找到最短的边,边的两点加入集合,每次寻找距离集合最短的点(比如集合中存在i,j两点,i->k=3,j->k=2,那么k到集合的距离就是2),直到所有的点进入集合。

7.2 克鲁斯卡尔算法(多个集合)

最开始每个点都是一个集合,每次都寻找最短的边,如果边的两点不属于同一个集合,就把两个并为同一个集合(并查集),直到图中只剩下一个集合。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值