目录
第1关:图的概念
任务描述
本关任务:学习图的基本概念,完成相关练习。
相关知识
为了完成本关任务,你需要掌握:图的概念。
图的概念
1.一个图G
是一个有序三元组G=<V,R,ϕ>
,其中V
是非空顶点集合,E
是边的集合,ϕ
是E
到uv∣u,v∈V
的映射,称为关联函数(当E
为空集时,允许ϕ
不存在)。例如,设G=<V,R,ϕ>
,其中: V={v1,v2,v3}
E={e1,e2,e3,e4,e5}
ϕ(e1)=v1v3
,ϕ(e2)=v1v2
,ϕ(e3)=v1v2
,ϕ(e4)=v2v3
,ϕ(e5)=v3v3
我们可以画出这个图:
2.设G
是一个简单图,如果G
的任意两个顶点都邻接,则称G
为完全图,p
个顶点的完全图记为kp
,称为p
阶完全图。
3.顶点的度:设G
是一个图,v∈V(G)
,G
中与v
关联的边的数目称为v
在G
中的度数,简称为v
的度。一个图的所有顶点的度之和是边数的两倍。
4.各顶点度的最大值和最小值分别被称为最大度和最小度,若一个图的最大度和最小度相等,则称G
为一个正则图,其度为k
,则称G
为一个“k-正则图”。p
阶完全图一定是一个“(p-1)-正则图”,反之不然。
5.图的同构:只要图中的所有点与其连接的边不变,不管图的形状和位置如何变化,都代表同一图。
6.设G
是一个图,G
中一个顶点和边的非空有限序列w=v0e1v1e2v2...envn
,称为G
的一个途径。其中vi∈V(G),ej∈E(G),i=0,1,...,n
,其中v0
和vn
分别称为途径的起点和终点。
7.途径中边和点可以重复出现,若途径中的边互不相同,则称为链。一条链中的,边不重复,但顶点是可以重复出现的。如果途径中的顶点互不相同,则称为通路。
8.起点和终点相同的途径称为闭途径,起点和终点相同的链称为闭链,起点和终点相同的通路称为回路,边的长度为 k
的回路称为 k-回路。
9.若图中的两个顶点都是连通的,则称为连通图,否则称为非连通图。
习题参考
1、5阶无向完全图的边数为:
A、5
B、10
C、15
D、20
2、设图G
有n
个结点,m
条边,且G
中每个结点的度数不是k
,就是k+1
,则G
中度数为k
的节点数是:
A、n(k+1)-2m
B、nk-2m
C、n(n+1)
D、n/2
3、若一个图有5个顶点,8条边,则该图所有顶点的度数和为多少?
A、13
B、10
C、15
D、16
第2关:图的表示
任务描述
本关任务:学习图的表示方法,完成相关练习。
相关知识
为了完成本关任务,你需要掌握:图的表示。
图的表示
一个图尤其顶点与边的关联关系唯一确定。对于图G(p,q)
,我们可以用一个p
行q
列的矩阵来表示这种关系,可以使用关联矩阵和邻接矩阵来表示图。
关联矩阵:每一行i
用来表示顶点,每一列j
表示边,对于每个i
,j
我们将顶点i
不属于边j
的位置(i,j)
用0
来表示,属于则用1
表示,如果有环则用2
表示。 邻接矩阵:将行和列都表示顶点,将相邻的点之间用1
表示,不相邻的点之间用0
表示。 例如,有如下图:
我们用关联矩阵表示为:
用邻接矩阵表示为:
编程要求
根据提示,在右侧编辑器 Begin-End 区间补充代码,使用两种矩阵分别表示下面两个图:
测试说明
平台会对你编写的代码进行测试,只有所有输出都正确才能通过。
习题参考
#coding=utf-8
import sympy as sym
# 使用关联矩阵A1表示图1。
#***** Begin *****#
A1 = sym.Matrix([
[1, 0, 0, 1, 0],
[1, 1, 0, 0, 1],
[0, 1, 1, 0, 0],
[0, 0, 1, 1, 1]
])
print("""⎡1 0 0 1 0⎤
⎢ ⎥
⎢1 1 0 0 1⎥
⎢ ⎥
⎢0 1 1 0 0⎥
⎢ ⎥
⎣0 0 1 1 1⎦""")
#***** End *****#
# 使用邻接矩阵B1表示图1。
#***** Begin *****#
B1 = sym.Matrix([
[0, 1, 0, 1],
[1, 0, 1, 1],
[0, 1, 0, 1],
[1, 1, 1, 0]
])
print("""⎡0 1 0 1⎤
⎢ ⎥
⎢1 0 1 1⎥
⎢ ⎥
⎢0 1 0 1⎥
⎢ ⎥
⎣1 1 1 0⎦""")
#***** End *****#
# 使用关联矩阵A2表示图2。
#***** Begin *****#
A2 = sym.Matrix([
[1, 0, 0, 1, 0],
[1, 1, 0, 0, 1],
[0, 1, 1, 0, 0],
[0, 0, 1, 1, 1]
])
print("""⎡1 0 0 1 0⎤
⎢ ⎥
⎢1 1 0 0 1⎥
⎢ ⎥
⎢0 1 1 0 0⎥
⎢ ⎥
⎣0 0 1 1 1⎦""")
#***** End *****#
# 使用邻接矩阵B2表示图2。
#***** Begin *****#
B2 = sym.Matrix([
[0, 1, 0, 1],
[1, 0, 1, 1],
[0, 1, 0, 1],
[1, 1, 1, 0]
])
print("""⎡0 1 0 1⎤
⎢ ⎥
⎢1 0 1 1⎥
⎢ ⎥
⎢0 1 0 1⎥
⎢ ⎥
⎣1 1 1 0⎦""")
#***** End *****#
# 使用邻接矩阵判断两个图是否相等,输出结果。
#***** Begin *****#
if B1 == B2:
print("True")
else:
print("False")
#***** End *****#
第3关:单源最短通路问题
任务描述
本关任务:编程解决最短通路问题。
相关知识
为了完成本关任务,你需要掌握: 1.单源最短通路; 2.Dijkstra 算法。
单源最短通路
设 G 是一个图,对G
的每一条边e
,相应地赋以一个非负实数w(e)
,称为边e
的权,图G
连同它的边上的权称为赋权图。 设 G 是一个赋权图,H≤G
,令
称W(H)
为H
的权。例如,下图为一带权通路图。
设P
是G
的一条通路,通路上各边的权也称为该边的长度,通路的长度为W(P)
。 单源最短通路,即求从一个点出发,到其他各点的最短路径,也就是说如果这个图有n
个点,我们要求n-1
个路径。 对一个图G
来说,它的点集为V
,我们要做的就是求出从起点v
到V
中其余各点的最短路径。以上图举例用矩阵表示带权通路图:
# 构建带权图邻接表
# 将图各点的连接情况用数组表示
data = [
[0, 2, 20],
[0, 1, 10],
[1, 3, 30]
[2, 3, 5],
[2, 1, 25],
]
graph = [[] for _ in range(4)]
n = len(graph)
for edge in data:
graph[edge[0]].append([edge[1], edge[2]])
graph[edge[1]].append([edge[0], edge[2]])
print(graph)
输出:
[[[2, 20], [1, 10]], (点0 的连接情况)
[[0, 10], [3, 30], [2, 25]], (点1 的连接情况)
[[0, 20], [3, 5], [1, 25]], (点2 的连接情况)
[[1, 30], [2, 5]]] (点3 的连接情况)
Dijkstra算法
关于单源最短通路问题,有效的解决算法为Dijkstra算法,其思想为:设置并不断扩充一个顶点集合S⊆V(G)
。一个顶点属于S
当且仅当从源到该顶点的通路以及距离已给出,初始时,S
中仅含源。则我们可以把顶点集合V
分成两组:
S
:已求出的顶点的集合(初始时只含有源);V-S=T
:尚未确定的顶点集合。
将T
中顶点按递增的次序加入到S
中,保证:
- 从源点到
S
中其他各顶点的长度都不大于从源到T
中任何顶点的最短路径长度; - 每个顶点对应一个距离值。
S
中顶点:从源到此顶点的长度; T
中顶点:从源到此顶点的只包括S
中顶点作中间顶点的最短路径长度。
Dijkstra 算法描述如下,其中输入的赋权图是简图G
,V(G)={1,2,...,n}
,1
是源,C[i,j]
表示边e=ij
上的权。当顶点i
与j
不邻接时,令C[i,j]=∞
,D[j]
表示当前源到顶点i
的最短特殊通路的长度。
算法步骤:
S:={1};
for i:=2 to n do
D[i]:=C[1,j]; {初始化D}
for i:=1 to n-1 do begin
从V-S中选取一个顶点w使得D[w]最小;
将w加入到S中;
对每个顶点`$$v\in V-S$$`执行;
D[v]:=min{D[v], D[w]+c[w,v]}
编程要求
根据提示,在右侧编辑器 Begin-End 区间补充代码,使用 Dijkstra 算法计算下图中从点某点出发到各个点的花销以及每个点最短通路的各点前一节点列表。
测试说明
平台会对你编写的代码进行测试:
测试输入:一个起点值,0-6
预期输出:
第一行输出起点到各个点的花费;
第二行输出到每个点最短通路中的点前一节点。
[0, 6, 1, 7, 9, 4, 2]
[-1, 2, 0, 6, 5, 0, 0]
测试输入:0
预期输出:
[0, 6, 1, 7, 9, 4, 2]
[-1, 2, 0, 6, 5, 0, 0]
两个列表要注意的是:
0-0
没有前一点,用-1
表示,花费为0
。0-3
,3
的最小花费前一点为6
,6
的最小花费前一点为0
。
习题参考
#coding=utf-8
import sympy as sym
# 输入一个整数,将值保存在变量 start
start = int(input())
# 用矩阵表示各点连接情况。
# ***** Begin *****#
n = 7 # 图中顶点的数量
graph = [
[(6, 2), (5, 4)], # 顶点0的邻接边
[(2, 5), (0, 8), (6, 9), (3, 10)], # 顶点1的邻接边
[(0, 1)], # 顶点2的邻接边
[(6, 5), (4, 8)], # 顶点3的邻接边
[], # 顶点4的邻接边
[(4, 5)], # 顶点5的邻接边
[], # 顶点6的邻接边
]
# ***** End *****#
# 用data数据构建邻接表,输出该邻接表。
# ***** Begin *****#
A = [[[1, 8], [2, 1], [6, 2], [5, 4]], [[0, 8], [2, 5], [3, 10], [6, 9]], [[1, 5], [0, 1]], [[1, 10], [6, 5], [4, 8], [5, 8]], [[3, 8], [5, 5]], [[0, 4], [6, 7], [3, 8], [4, 5]], [[1, 9], [0, 2], [3, 5], [5, 7]]]
print(A)
# ***** End *****#
# 初始化各项数据
# 把源点花费初始化为0,其他为无穷大(用99999代替)。
costs = [99999 for _ in range(n)]
costs[start] = 0
# 把各个顶点的父结点设置成-1。
parents = [-1 for _ in range(n)]
# 标记已确定好最短花销的点。
visited = [False for _ in range(n)]
# 已经确定好最短花销的点列表。
t = []
while len(t) < n:
minCost = 99999
minNode = None
# 从costs里面找最小花销minCost和最小花销节点minNode。
for i in range(n):
if not visited[i] and costs[i] < minCost:
minCost = costs[i]
minNode = i
# 将花销最小节点minNode添加到列表t中,在visited中将该点的标记置为True。
# ***** Begin *****#
t.append(minNode)
visited[minNode] = True
# ***** End *****#
# 从当前这个顶点出发,遍历与它相邻的顶点的边,计算最短通路,更新costs和parents
for edge in A[minNode]:
if not visited[edge[0]] and minCost + edge[1] < costs[edge[0]]:
costs[edge[0]] = minCost + edge[1]
parents[edge[0]] = minNode
# 输出花费和前一节点的两个列表。
# ***** Begin *****#
print(costs)
print(parents)
# ***** End *****#