数据结构复习(七)图

一、图的概念及表示

1.图的定义

图(Graph): 由两个集合V(G)和E(G)组成的,记为G=(V,{E})
其中:V是顶点的有穷非空集;E是边(弧)的有限集

约定符号:
V:顶点有穷非空集合
VR:顶点关系的集合
E:边或弧的集合
n:图中顶点数目
e:边或弧的数目
G:图
N:网

有向完全图:n个顶点、有n(n-1)条弧的有向图
无向完全图:n个顶点、有n(n-1)/2 条边的无向图

一个有n个顶点和e条边或弧的图,满足:(图中顶点的总度数等于边数的2倍)
总度数等于边数的2倍

无向图的连通性:

  • v到w的路径:在无向图G=(V,{E})中由顶点v经无向边至w的顶点序列
  • v和w是连通的:顶点v和w之间有路径存在
  • 连通图:无向图的任意两点之间都连通
  • 连通分量:无向图的极大连通子图

有向图的连通性:

  • v到w的路径:在有向图G=(V,{E})中由顶点v经有向弧至w的顶点序列
  • v和w是连通的:顶点v到w以及w到v都有路径存在
  • 强连通图:有向图 G 的任意两点之间都连通
  • 强连通分量:有向图的极大强连通子图

简单路径: 路径序列中顶点不重复出现
回路或环:第一个顶点和最后一个顶点相同的路径
简单回路或简单环:除第一个顶点和最后一个顶点外,其余顶点不重复出现的回路。

无向连通图的生成树: 无向连通图的极小连通子图。包含图的全部n个顶点和足以构成一棵树的n-1条边。在生成树中添加一条边之后,必定会形成回路或环

有向树:如果一个有向图恰有一个顶点入度为0,其余顶点入度均为1,则是有向树。
有向图的生成森林:由若干有向树组成,含有图中全部顶点,但只有足以构成若干棵不相交的有向树的弧。

稀疏图:有很少条边或弧(e<nlog2n)的图。
稠密图:有很多条边或弧的图。
边或弧的权值:与弧或边相关的数。可以表示从一个顶点到另一个顶点的距离、花费的代价、所需的时间等。
网: 边或弧上带权的图。

2.图的存储结构

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

优点 易判定任两个顶点之间是否有边或弧存在。 适合存储有向图、无向图、有向网、无向网
缺点: 在边稀疏(e<<n(n-1)/2)时,浪费存储空间

时间复杂度讨论
有n个顶点和e条边的图 邻接矩阵初始化的时间复杂度为O(n2)
输入顶点编号,建立邻接矩阵的时间复杂度为O(e)
输入顶点值,建立邻接矩阵的时间复杂度为O(ne)
总的时间复杂度为:O(n2+n
e)

用一个1-D数组存放顶点的元素信息
用一个二维数组存储顶点之间的关系,其中:
A[i][j]=1,i到j有弧且i≠j;
A[i][j]=0,其他情况

有向图
第i个顶点的出度OD(i)=第i行元素值之和
第i个顶点的入度ID(i)=第i列元素值之和
无向图
第i个顶点的度=第i行或第i列元素值之和
(无向图的邻接矩阵是对称阵)

例子:在这里插入图片描述
在这里插入图片描述

  • 邻接表表示法

是图的一种链式存储结构,适用于有向图和无向图。对图中每个顶点建立一个单链表,单链表中的结点表示依附于该顶点的边(对有向图来说是以该顶点为弧尾的弧)

优点:易找到任一顶点的第一个邻接点和下一个邻接点
适合存储有向图(网)、无向图(网)
缺点:难以直接判定任意两个顶点之间是否有边或弧相连。需搜索第i和第j个单链表。不及邻接矩阵方便。
时间复杂度的讨论:
邻接表头结点初始化的时间复杂度为O(n)
输入顶点编号,建立邻接点链表的时间复杂度为O(e)
输入顶点值,建立邻接点链表的时间复杂度为O(ne)
总的时间复杂度为:O(n+n
e)

有向图邻接表示例
在这里插入图片描述

vi的出度=第i个单链表中的结点数目
vi的入度=整个邻接表中其邻接点值域为i的结点个数

无向图邻接表示例
在这里插入图片描述

顶点vi的度TD=第i个单链表中的结点数目

二、图的遍历

定义:从图中某个顶点出发遍访图中其余顶点,且使每个顶点仅被访问一次的过程.
意义:对非线性结构线性化。遍历算法是求解图的连通性、拓扑排序、求关键路径等算法的基础。

遍历算法中辅助向量visited[]的使用
① 使用原因:
因为图中任意顶点都可能和其余顶点相邻接,所以在访问了某顶点后,可能沿另外的某条路径搜索,而后又回到此顶点上,为了避免同一顶点被多次访问,在遍历图的过程中,必须记下每个已访问过的顶点。
② 使用方法:
设置一个辅助数组visited[0…n-1],它的初始值置为“假”,表示顶点未被访问过,一旦访问了顶点i,就置visited[i]的值为“真”或者被访问时的次序号。
Boolean visited[MAX_VERTEX_NUM] ;

  • 深度优先遍历DFS (Depth First Search)
    类似于树的先序遍历,是其推广

算法思想
①从图中某个顶点v出发,访问此顶点;
②依次从v的各个未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到
③若图中还有顶点未被访问(非连通图),则另选图中一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

Boolean visited[MAX_VERTEX_NUM]; 
Status (*VisitFunc)(int v); //全局函数指针变量

void DFSTraverse( Graph G, Status (*Visit)(int v))
 { //对图G进行深度优先遍历  
   VisitFunc=Visit;
   for ( v=0; v <G.vexnum; ++v )  visited[v] = FALSE;
   for ( v=0; v <G.vexnum; ++v )
     if ( !visited[v] ) DFS(G,v); 
 }//DFSTraverse
 
  void DFS( Graph G, int v) 
 { //从v出发深度优先遍历图G
    visited[v] = TRUE; 
    VisitFunc(v); //访问顶点v
    for(w=FirstAdjVex(G,v); w>=0; w=NextAdjVex(G,v,w)) 
      if ( !visited[w] )  DFS(G,w)} //DFS

在这里插入图片描述

在遍历图时,对每个顶点至多调用一次DFS函数,因为一旦某个顶点被标志成已被访问,就不再从它出发进行搜索。
遍历图的实质上是对每个顶点查找其邻接点的过程。其耗费的时间取决于所采用的存储结构。
用邻接矩阵存储图时,查找所有顶点的邻接点需要O(n2);
用邻接表存储图时,查找所有顶点的邻接点需要O(e);
深度优先遍历图的算法的时间复杂度与采用的存储结构有关
邻接矩阵做图的存储结构时,深度优先遍历的时间复杂度为O(n2)
邻接表做图的存储结构时,深度优先遍历的时间复杂度为 O(n+e)

  • 广度优先遍历BFS (Breadth First Search)
    类似于树的层序遍历,是其推广

广度优先遍历的实质是以v为起点,由近及远,依次访问和v有路径相通且路径长度为1、2、……的顶点

算法思想
①从图中的某个顶点v出发,访问此顶点;
②依次访问v的所有未被访问过的邻接点,之后按这些邻接点被访问的先后次序依次访问它们的邻接点,直至图中所有和v有路径相通的顶点都被访问到;
③若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

void BFSTraverse(Graph G,Status(*Visit)(int v)) 
{ //对图G进行广度优先遍历
   for(v=0;v<G.vexnum;++v)  visited[v] = FALSE;  
   InitQueue(Q);
   for( v=0;v<G.vexnum;++v )
	if(!visited[v]) //v没有被访问 
	{ visited[v]=TRUE;  	Visit(v);      //访问v
      EnQueue(Q,v);   //v入队列
	  while(!QueueEmpty(Q))
	  {  DeQueue(Q,u); //队头元素u出队列
	     for(w=FirstAdjVex(G,u); w>=0; w=NextAdjVex(G,u,w)) 
		if(!Visited[w])  
		{  visited[w]=TRUE; Visit(w); //访问w
		   EnQueue(Q,w); //w入队列
		} //if
	  } //while
     } //if
} //BFSTraverse 

在这里插入图片描述

  • 遍历的应用

利用图的遍历算法来判定一个无向图是否是连通图
对于无向连通图, 从任一顶点出发,进行深度/广度优先遍历,就可访遍图中所有顶点;
对于非连通图, 它有几个连通分量,就需要从几个顶点出发进行遍历

 修改DFSTraverse和BFSTraverse中的语句,
 可以判断无向图的连通分量个数。如:
            sum=0;
            for ( v=0; v <G.vexnum; ++v )
              if ( ! visited[v] ) 
               { sum++;
                 .......
                }

利用图的遍历算法来得到生成树或生成森林
对无向连通图G进行遍历时,由遍历过程中历经的边的集合和所有顶点的集合一起构成了G的一棵生成树。
由深度优先遍历得到的为深度优先生成树
由广度优先遍历得到的为广度优先生成树
对于非连通图,它的每个连通分量的生成树就组成了非连通图的生成森林

深度优先生成树在这里插入图片描述
广度优先生成树在这里插入图片描述
深度优先生成森林在这里插入图片描述
广度优先生成森林在这里插入图片描述

三、最小生成树

定义:生成树中各边权值(代价)之和最小的树。
最小生成树的应用:
在n个城市之间选取n-1条线路架设连通的通信网使总费用最低问题 在n个顶点的连通网上构造最小生成树
MST性质:设N=(V,{E})是连通网,U是V的一个非空子集。若(u,v)是所有满足u∈U, v∈V-U的边中代价最小的边,则必存在一棵包括边(u, v)的最小生成树。 (可用反证法证明)
应用MST性质构造最小生成树的算法:
Prim算法
Kruscal算法

  • Prim算法
    时间复杂度:O(n2) , 只与顶点数有关, 与网中的边数无关
    适用于求边稠密的网的最小生成树

设N=(V,{E})是连通网,T=(V,{TE})表示N的最小生成树,TE为最小生成树的边集,初始为空集。
则Prim算法的执行过程:

Step1: 令U={u},u∈V(u是网中任意一个顶点),TE={};
Step2: 在u∈U,v∈V-U的边(u,v)∈E中寻找一条代价最小的边(u,v)并入TE,同时将顶点v并入U;
Step3: 重复Step2,直至U=V,此时TE中必有n-1条边,而T={V,{TE}}是N的一棵最小生成树
在这里插入图片描述

  • Kruscal算法
    时间复杂度:O(e*loge),只与边数有关, 与网中的顶点数无关
    适合于求边稀疏的网的最小生成树

要使最小生成树边权值之和最小,树的每一条边权值应尽量小

假设连通网N=(V,{E}),T=(V,{TE})表示N的最小生成树,TE为最小生成树上边的集合。初始时令TE为空集。
步骤:

Step1:令最小生成树T的初态为只有n个顶点的非连通图T=(V,{TE}),TE={}。
Step2:从权值最小的边(u,v)开始,若该边依附的两个顶点落在T的不同连通分量上,则将此边加入到TE中,即TE=TE∪(u,v),否则舍弃此边,选择下一条代价最小的边。
Step3:重复Step2,直至TE所有顶点在同一连通分量上。此时T=(V,{TE})就是N的一棵最小生成树。
在这里插入图片描述

四、拓扑排序及求关键路径等

  • 拓扑排序

偏序: 若集合 X 上的关系R是传递的、自反的、反对称的,则称R是集合X上的偏序关系。可指集合中部分成员之间可比较。
全序:若关系R 是集合 X 上的偏序关系,如果对于属于X的每个x,y,必有xRy 或yRx ,则称R是集合X上的全序关系。可指集合中全部成员之间可比较。
拓扑排序: 由集合上的偏序得到该集合上的全序的操作。这个全序被称为拓扑有序。

拓扑排序步骤

Step1:在有向图中选一个无前驱的顶点输出之;
Step2:从有向图中删去此顶点及所有以它为尾的弧;
Step3: 重复前2步,直到图中顶点全部输出,此时图中无环;或图不空但找不到无前驱的顶点,此时图中有环。

拓扑排序算法还是求关键路径的基础。

  • 关键路径

AOE-网(Activity On Edge):一个有向无环网,顶点表示事件,弧表示活动,弧上权值表示活动持续的时间。
通常用来估算工程完成时间
路径长度:AOE网中路径上各活动持续时间之和。
关键路径:从源点到汇点路径长度最长的路径。

设活动ai在有向边<j,k>上,有:在这里插入图片描述

活动ai的最早开始时间e(i):是从源点v0到vj的最长路径长度。
活动ai的最迟开始时间l(i):是不推迟工程完成的前提下,该活动允许的最迟开始时间。
e(i)= Ve(j)
l(i)=Vl(k)-dut(<j,k>)

活动ai时间余量:l(i)-e(i)
关键活动:l(i)=e(i)的活动。
关键路径上的活动都是关键活动

设有向无环图G的有向边<j,k>上:
事件vk的最早发生时间Ve(k)=从源点v0到vk的最长路径长度
Ve(0)=0;
Ve(k)=Max{Ve(j)+dut(<j,k>), <j,k>∈T,所有j}

事件vj的最迟开始时间Vl(j):保证汇点vn-1在Ve(n-1)时刻完成的前提下,事件vj最迟允许开始的时间。
Vl(j)=Min{Vl(k)-dut(<j,k>), <j,k>∈T,所有k}

从Ve(0)=0向汇点方向递推,顶点vk的最早开始时间
ve(k)=Max{Ve(j)+dut(<j,k>)},在拓扑有序的前提下进行
从vl(n-1)=ve(n-1)向源点方向推,顶点vj的最迟开始时间
vl(j)=Min{vl(k)-dut(<i,j>)},在逆拓扑有序前提下进行
即最早发生时间从前往后求,取最大; 最迟开始时间从后往前求,取最小

示例
在这里插入图片描述

  • 最短路径

最短路径问题:从图中某一顶点到达另一顶点的路径可能不止一条,求其中一条路径使得沿此路径上各弧上的权值总和最小。称路径的第一个顶点为源点,最后一个顶点为终点。

从某一源点到其余各顶点的最短路径(Dijkstra算法)
示例

在这里插入图片描述

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
为了复习数据结构和Java编程语言,你可以按照以下步骤进行: 1. 确保你已经掌握了Java SE的基础知识。这包括Java的语法、数据类型、流程控制语句、面向对象编程等。如果你需要巩固这些基础知识,可以通过阅读相关的教材或参加在线课程来加强你的理解。 2. 接下来,你可以进一步了解JDBC(Java Database Connectivity)。JDBC是Java用于与数据库进行交互的标准API。你可以学习如何连接到数据库、执行SQL查询和更新操作,并了解如何处理结果集。有关JDBC的更多信息,可以查阅Java官方文档或参考相关的教程。 3. 数据结构中的树形结构是非常重要的一部分。确保你对树的概念和常见的树结构(如二叉树、AVL树、B树等)有一个清晰的理解。你可以阅读关于树的教材或参考在线资源来加深你对树的理解。 4. 如果你想进一步学习数据库建模和设计,可以初步了解PowerDesigner数据库建模工具的使用。这个工具可以帮助你设计和管理数据库模型,并生成相应的DDL脚本。 5. 最后,了解UML(Unified Modeling Language)统一建模语言也是非常有帮助的。UML是一种用于描述和设计软件系统的标准化语言。了解UML的基本概念和常用的建模形(如类、时序、用例等)将使你能够更好地理解和交流软件系统的设计。 通过按照上述步骤进行复习,你将能够深入了解数据结构和Java编程,并为进一步的学习和实践打下坚实的基础。记得多进行实践和练习,以加深对知识的理解和应用。祝你学习顺利!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全糖去冰不加料

打赏一块钱💰也是钱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值