数据结构06——图(选修)

一、图的定义与相关概念

在线性结构中,数据元素以线性排列,元素间存在前驱与后继关系。在树形结构中,数据元素以树形结构(层次结构)排列,每个元素可以存在多个下层元素,但是只允许存在一个上层元素。

如果元素间存在比较复杂的关系,即每个元素都与多个元素存在关系,则元素间的关系可以用“图”来表示。

图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成的,通常表示为G(V,E),其中G表示图,V表示顶点的集合,E表示边的集合。

根据不同的使用方式,图的具体定义也分为以下几类:

无向图:若顶点Vi到Vj之间的边没有方向,则称为无向边。若图中任意的边都是无向边,则称该图为无向图。

有向图:若从顶点Vi到Vj之间的边有方向,则称这条边为有向边,也称为弧(Arc)。如果图中任意两个顶点之间的边都是有向边,则称该图为有向图。

在无向图中,如果任意两个顶点之间都存在边,则称为无向完全图。含有n个顶点的无向完全图有n(n-1)/2条边。

在有向图中,如果任意两个顶点之间都存在方向互反的两条弧,则称为有向完全图。含有n个顶点的有向完全图有n(n-1)条边。

有很少条边或弧的图称为稀疏图,反之称为稠密图。

有些图的边或弧具有与之相关的数字,这些数字称为权(Weight)。这些权值可以表示从一个点到另一个点的距离或耗费。这种带权图也常称为网(Network)。

对于无向图,顶点v的度(Degree)是与v相关联的边的数目。对于有向图,度分为入度(InDegree)和出度(OutDegree)两种,其中以v为尾的弧的数目称为v的出度,而以v为头的弧的数目称为v的入度。

从一个顶点v到另一个顶点u的顶点序列称为路径(Path),其路径长度是路径上边或弧的数目。

二、图的存储结构

1、邻接矩阵

考虑到图是由顶点和边或弧两部分构成,因此合并存储比较困难,因此我们采用分开存储的方式。

图的邻接矩阵(Adjacency Matrix)存储方式是用一个二维数组来表示图的存储方式。存储方式为:

arc[i][j]= 1(若vi与vj可以连通)

0(反之,或者i=j)

若图内的边或弧存在权值,则存储方式为:

arc[i][j]= 该边/弧权值(若vi与vj可以连通)

0(若i=j)

∞(反之)

无向图的邻接矩阵是一个对称的矩阵,而有向图可以在邻接矩阵内得到入度与出度。

//见附图

邻接矩阵可以看做是图的顺序存储结构。

2、邻接表

邻接矩阵是一种不错的存储图的方式,但是我们也发现,若图的边或弧相对顶点数目较少,则在矩阵中会大量出现无用数据,这对存储空间是极大的浪费。此时,我们可以引入链式存储结构来避免空间浪费的问题。将数组与链表结合起来,用于表示图的存储方法称为邻接表(Adjacency List)。

邻接表的存储方式如下:

1.图中顶点使用一维数组存储(当然也可以使用链表存储,不过使用数组可以更加方便读取顶点信息),并且每个数组的元素都有一个指向邻接顶点的指针

2.途中每个顶点vi的所有邻接顶点构成一个线性表,由于邻接点的个数不确定,因此使用单链表存储。

3.对于带权图,在邻接表内增加一个存储权值weight的数据域。

//见附图

三、最小生成树(Minimum Cost Spanning Tree)

在带权图中,使用n-1条边,将n个顶点连接起来,并且得到的权值和最小,则这样的结构称为该图的最小生成树。

//例如,为n个村镇搭建通信网络,每个村庄之间的距离不同,则为了得到最低成本,我们应沿着最小生成树的方式搭建网络。

构建最小生成树有两种算法:Prim算法与Kruskal算法

//最小生成树图例见附图

1、Prim算法

该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(Vojtěch Jarník)发现,并在1957年由美国计算机科学家罗伯特·普里姆(Robert C. Prim)独立发现。

Prim算法的步骤如下:

//第0步表示准备工作,下同

0.设全部顶点集合为U,已选取顶点集合为V

1.选取任意点作为起始点,将其加入V中

2.在集合U-V中,选取能与集合V内的顶点连通的顶点且权值最小的边,并将这个顶点加入V中

3.不断重复步骤2,直至V与U数量相等。此时被选取的边构成最小生成树。

以图例的无向有权图为例:

0.设该图全部顶点集合为U={v0,v1,v2,v3,v4,v5,v6,v7,v8},已选取顶点集合为V={}

1.设起点为v0,将v0加入V,此时V={v0}

2.在剩下的顶点中,选取能与V中节点v0连通的顶点,且需要权值最小,此时选取边(v0,v1),同时将v1加入V。此时V={v0,v1}

3.在剩下的顶点中,选取能与V中节点{v0,v1}连通的顶点,且需要权值最小,此时选取边(v0,v5),同时将v5加入V。此时V={v0,v1,v5}

4.在剩下的顶点中,选取能与V中节点{v0,v1,v5}连通的顶点,且需要权值最小,此时选取边(v1,v8),同时将v8加入V。此时V={v0,v1,v5,v8}

5.在剩下的顶点中,选取能与V中节点{v0,v1,v5,v8}连通的顶点,且需要权值最小,此时选取边(v2,v8),同时将v2加入V。此时V={v0,v1,v2,v5,v8}。由于v2已经加入V,因此边(v1,v2)作废

6.在剩下的顶点中,选取能与V中节点{v0,v1,v2,v5,v8}连通的顶点,且需要权值最小,此时选取边(v1,v6),同时将v6加入V。此时V={v0,v1,v2,v5,v6,v8}。由于v6已经加入V,因此边(v5,v6)作废。

7.在剩下的顶点中,选取能与V中节点{v0,v1,v2,v5,v6,v8}连通的顶点,且需要权值最小,此时选取边(v6,v7),同时将v7加入V。此时V={v0,v1,v2,v5,v6,v7,v8}

8.在剩下的顶点中,选取能与V中节点{v0,v1,v2,v5,v6,v7,v8}连通的顶点,且需要权值最小,此时选取边(v4,v7),同时将v4加入V。此时V={v0,v1,v2,v4,v5,v6,v7,v8}。由于v4已经加入V,因此边(v4,v5)作废。

9.在剩下的顶点中,选取能与V中节点{v0,v1,v2,v4,v5,v6,v7,v8}连通的顶点,且需要权值最小,此时选取边(v3,v7),同时将v3加入V。此时V={v0,v1,v2,v3,v4,v5,v6,v7,v8}。由于v3已经加入V,因此边(v2,v3),(v3,v4),(v3,v6),(v3,v8)作废。

10.此时,所有顶点都已在V中,即U=V,算法结束。得到最小生成树。

Prim算法的时间复杂度为O(n^2)

//经过优化的Prim算法实际为O(nlogn),这里不做介绍

2、Kruskal算法

Prim算法以顶点作为目标去构建最小生成树,而Kruskal算法则是以边作为目标去构建最小生成树。

Kruskal算法算法针对边进行展开,边数较少时效率会非常高,因此非常适用于稀疏图。而Prim算法针对顶点进行展开,因此稠密图时效率高于Kruskal算法。

Kruskal算法的步骤如下:

0.令该图的初始状态为n个顶点而无边的非连通图,其中每一个顶点自成一个连通分量。同时设定一个边的集合T。

1.每次选取代价最小的边,若该边的两端依附于不同的连通分量,则将其加入T中,并且将两个独立的连通分量合并;否则舍弃该边。

2.依次类推,直至所有顶点都在同一连通分量上为止,此时T内的边就构成了最小生成树。

以图例的无向有权图为例:

0.令图内v0~v8所有顶点互相独立,每个顶点自成一个连通分量。设定边集合T

1.选取权值最小的边(v4,v7),此时将v4与v7合并成同一连通分量,并将边(v4,v7)加入T中

2.选取权值最小的边(v2,v8),此时将v2与v8合并成同一连通分量,并将边(v2,v8)加入T中

3.选取权值最小的边(v0,v1),此时将v0与v1合并成同一连通分量,并将边(v0,v1)加入T中

4.选取权值最小的边(v0,v5),此时将v0、v1、v5合并成同一连通分量,并将边(v0,v5)加入T中

5.选取权值最小的边(v1,v8),此时将v0、v1、v2、v5、v8合并成同一连通分量,并将边(v1,v8)加入T中

6.选取权值最小的边(v1,v6),此时将v0、v1、v2、v5、v6、v8合并成同一连通分量,并将边(v1,v6)加入T中

7.选取权值最小的边(v3,v7),此时将v3、v4、v7合并成同一连通分量,并将边(v3,v7)加入T中

8.选取权值最小的边(v5,v6),由于v5与v6已在同一连通分量中,选取该边会造成环路,因此舍弃

9.选取权值最小的边(v1,v2),由于v1与v2已在同一连通分量中,选取该边会造成环路,因此舍弃

10.选取权值最小的边(v6,v7),此时将v0、v1、v2、v3、v4、v5、v6、v7、v8合并成同一连通分量,并将边(v6,v7)加入T中

11.此后的选边均会造成环路,因此选取结束。最终在集合T中的边构成最小生成树

Kruskal算法的时间复杂度为O(n^2)

//经过优化的Kruskal算法实际为O(nlogn),这里不做介绍

四、最短路径算法——Dijkstra算法

对于有权图,最短路径指的是两顶点之间经过的边上权值之和最小的路径。使用Dijkstra算法可以求得任意一个顶点到所有其他顶点的最短路径。

Dijkstra算法的步骤如下:

0.1.将所有顶点分成两个集合:已知最短路径集合P和位置最短路径集合Q。算法开始时,集合P内只有一个起点。

0.2.设定一个存储起点到其他顶点最短路径预估值的数组dist[],其中数组下标代表顶点编号

1.选取dist[]内最小的元素(已加入集合P的不再选取),以该顶点作为起点计算所有与之连通的顶点的路径长度,并将该顶点从集合Q加入集合P

2.若通过该顶点算出的某其他顶点路径长度小于预估值,则更改该顶点的预估值(此过程称为“松弛”)。

3.重复步骤1和2,直至所有顶点加入集合P(或集合Q为空)

以图例的无向有权图为例:

0.选取起点为v0,并建立数组dist[]={0,1,5,∞,∞,∞,∞,∞,∞}。由于算法还未开始运行,因此v0只有到v1和v2的预估值,而其余顶点预估值未知。将v0加入集合P。

1.选取dist[]内最小的值:v1=1,以该点作为起点计算所有可以连通的顶点的路径长度。其中:

(v0,v1)回到v0,不考虑

(v1,v2)长度为3,若通过v1到达v2,则路径长度为1+3=4,小于预估值5,因此将数组内v2的值更新为4

(v1,v3)长度为7,若通过v1到达v3,则路径长度为1+7=8,因此将数组内v3的值更新为8

(v1,v4)长度为5,若通过v1到达v4,则路径长度为1+5=6,因此将数组内v4的值更新为6

经过该步后,dist[]={0,1,4,8,6,∞,∞,∞,∞},并将v1加入集合P,接下来不再选取

2.选取dist[]内最小的值:v2=4,以该点作为起点计算所有可以连通的顶点的路径长度。其中:

(v0,v2)回到v0,不考虑

(v1,v2)回到v1,不考虑

(v2,v4)长度为1,若通过v2到达v4,则路径长度为4+1=5,小于预估值6,因此将数组内v4的值更新为5

(v2,v5)长度为7,若通过v2到达v5,则路径长度为4+7=11,因此将数组内v5的值更新为11

经过该步后,dist[]={0,1,4,8,5,11,∞,∞,∞},并将v2加入集合P,接下来不再选取

3.选取dist[]内最小的值:v4=5,以该点作为起点计算所有可以连通的顶点的路径长度。其中:

(v1,v4)回到v1,不考虑

(v2,v4)回到v2,不考虑

(v3,v4)长度为2,若通过v4到达v3,则路径长度为5+2=7,小于预估值8,因此将数组内v3的值更新为7

(v4,v5)长度为3,若通过v4到达v5,则路径长度为5+3=8,小于预估值11,因此将数组内v5的值更新为8

(v4,v6)长度为6,若通过v4到达v6,则路径长度为5+6=11,因此将数组内v6的值更新为11

(v4,v7)长度为9,若通过v4到达v7,则路径长度为5+9=14,因此将数组内v7的值更新为14

经过该步后,dist[]={0,1,4,7,5,8,11,14,∞},并将v4加入集合P,接下来不再选取

4.选取dist[]内最小的值:v3=7,以该点作为起点计算所有可以连通的顶点的路径长度。其中:

(v1,v3)回到v1,不考虑

(v3,v4)回到v4,不考虑

(v3,v6)长度为3,若通过v3到达v6,则路径长度为7+3=10,小于预估值11,因此将数组内v6的值更新为10

经过该步后,dist[]={0,1,4,7,5,8,10,14,∞},并将v3加入集合P,接下来不再选取

5.选取dist[]内最小的值:v5=8,以该点作为起点计算所有可以连通的顶点的路径长度。其中:

(v2,v5)回到v2,不考虑

(v4,v5)回到v4,不考虑

(v5,v7)长度为5,若通过v5到达v7,则路径长度为8+5=13,大于预估值12,舍弃

经过该步后,dist[]={0,1,4,7,5,8,10,14,∞},并将v5加入集合P,接下来不再选取

6.选取dist[]内最小的值:v6=10,以该点作为起点计算所有可以连通的顶点的路径长度。其中:

(v3,v6)回到v3,不考虑

(v4,v6)回到v4,不考虑

(v6,v7)长度为2,若通过v6到达v7,则路径长度为10+3=12,小于预估值14,因此将数组内v7的值更新为12

(v6,v8)长度为7,若通过v6到达v8,则路径长度为10+7=17,因此将数组内v8的值更新为17

经过该步后,dist[]={0,1,4,7,5,8,10,12,17},并将v6加入集合P,接下来不再选取

7.选取dist[]内最小的值:v7=12,以该点作为起点计算所有可以连通的顶点的路径长度。其中:

(v4,v7)回到v4,不考虑

(v5,v7)回到v5,不考虑

(v6,v7)回到v6,不考虑

(v7,v8)长度为4,若通过v7到达v8,则路径长度为12+4=16,小于预估值17,因此将数组内v8的值更新为16

经过该步后,dist[]={0,1,4,7,5,8,10,12,16},并将v7加入集合P,接下来不再选取

8.选取dist[]内最小的值:v8=16,以该点作为起点计算所有可以连通的顶点的路径长度。其中:

(v6,v8)回到v6,不考虑

(v7,v8)回到v7,不考虑

经过该步后,dist[]={0,1,4,7,5,8,10,12,16},并将v8加入集合P,接下来不再选取

9.所有顶点都已加入集合P,则算法结束,最终数组内的值dist[]={0,1,4,7,5,8,10,12,16}表示从v0出发到达其他所有顶点的最短路径长度

需要注意的是,到达不同顶点的最短路径可能不是同一条,例如该范例中,v0到达v5的路径与到达其他顶点的路径不相同。

以某顶点作为起点计算所有最短路径长度的Dijkstra算法的时间复杂度为O(n^2),若需要得到所有顶点的最短路径,则还需要一次循环,即时间复杂度为O(n^3)

//经过堆优化的Dijkstra算法实际为O(nlogn),这里不做介绍

学生信息管理系统是一个用来管理学生的基本信息和选课信息的系统。在数据结构中,我们可以使用一些算法和数据结构来设计这个系统。在这个系统中,我们需要考虑以下几个功能: 1. 添加学生信息:当有新的学生加入时,我们需要输入他们的姓名,班级,性别,学号等基本信息,并将其存储在系统中。 2. 删除学生信息:当学生离开学校或者不再使用该系统时,我们需要删除他们的基本信息以及他们在选课系统中的选课信息。 3. 添加课程信息:当有新的课程开设时,我们需要输入课程的名称,教师姓名,上课时间等信息,并将其存储在系统中。 4. 删除课程信息:当课程不再开设或者不再使用该系统时,我们需要删除该课程的所有信息,使得在系统中无法找到该课程的相关信息。 5. 修改学生信息:如果学生的基本信息发生变化,我们需要提供一个功能来修改他们的信息,例如修改班级或者联系方式。 6. 查询学生信息:我们需要提供一个功能来查询学生的基本信息和选课信息,以便管理员或教师能够查看学生的相关信息。 7. 查询课程信息:我们需要提供一个功能来查询课程的基本信息和选修该课程的学生信息,以便管理员或教师能够查看课程的相关信息。 以上是学生信息管理系统中一些常见的功能和操作,我们可以使用合适的数据结构和算法来设计和实现这个系统。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值